Updated on 2026-08-14
This commit is contained in:
parent
5ff859d0a9
commit
0a6264c1f9
20 changed files with 736 additions and 105 deletions
|
|
@ -10,7 +10,6 @@ private val payIdSupported = EnumSet.of(
|
|||
Blockchain.Litecoin,
|
||||
Blockchain.Stellar,
|
||||
Blockchain.StellarAsset,
|
||||
Blockchain.Binance,
|
||||
Blockchain.Cardano,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.BitcoinCash,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,22 @@ public class ServerApiPayId {
|
|||
switch (blockchain) {
|
||||
case Ripple: return "application/xrpl-mainnet+json";
|
||||
case Bitcoin: return "application/btc-mainnet+json";
|
||||
case Litecoin: return "application/ltc-mainnet+json";
|
||||
case Cardano: return "application/ada-mainnet+json";
|
||||
case Ducatus: return "application/duc-mainnet+json";
|
||||
case BitcoinCash: return "application/bch-mainnet+json";
|
||||
case Ethereum:
|
||||
case Token:
|
||||
return "application/eth-mainnet+json";
|
||||
case Stellar:
|
||||
case StellarAsset:
|
||||
return "application/xlm-mainnet+json";
|
||||
case Binance:
|
||||
case BinanceAsset:
|
||||
return "application/bnb-mainnet+json";
|
||||
case Rootstock:
|
||||
case RootstockToken:
|
||||
return "application/rsk-mainnet+json";
|
||||
default: throw new InvalidParameterException("PayID is not supported for " + blockchain.getOfficialName());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ public abstract class CoinData {
|
|||
if (B.containsKey("sentTransactionsCount")) {
|
||||
sentTransactionsCount = B.getInt("sentTransactionsCount");
|
||||
}
|
||||
|
||||
if (B.containsKey("ResolvedPayIdAddress")) resolvedPayIdAddress = B.getString("ResolvedPayIdAddress");
|
||||
else resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
public void saveToBundle(Bundle B) {
|
||||
|
|
@ -76,6 +79,8 @@ public abstract class CoinData {
|
|||
B.putString("validationNodeDescription", validationNodeDescription);
|
||||
|
||||
B.putInt("sentTransactionsCount", sentTransactionsCount);
|
||||
|
||||
if (resolvedPayIdAddress != null) B.putString("ResolvedPayIdAddress", resolvedPayIdAddress);
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
|
|
@ -155,6 +160,7 @@ public abstract class CoinData {
|
|||
rate = 0f;
|
||||
rateAlter = 0f;
|
||||
sentTransactionsCount = 0;
|
||||
resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
// private AtomicInteger failedBalanceRequestCounter;
|
||||
|
|
@ -212,4 +218,15 @@ public abstract class CoinData {
|
|||
public void incSentTransactionsCount() {
|
||||
sentTransactionsCount++;
|
||||
}
|
||||
|
||||
private String resolvedPayIdAddress = null;
|
||||
|
||||
public String getResolvedPayIdAddress() {
|
||||
return resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
public void setResolvedPayIdAddress(String resolvedPayIdAddress) {
|
||||
this.resolvedPayIdAddress = resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import java.io.IOException;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.text.DecimalFormat;
|
||||
|
|
@ -392,4 +393,18 @@ public abstract class CoinEngine {
|
|||
|
||||
public int pendingTransactionTimeoutInSeconds() { return 30; }
|
||||
|
||||
protected boolean validatePayId(String payId) {
|
||||
String[] addressParts = payId.split("\\$");
|
||||
|
||||
if (addressParts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
String addressURL = "https://" + addressParts[1] + "/" + addressParts[0];
|
||||
try {
|
||||
new URL(addressURL).toURI();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,14 @@ import android.util.Log;
|
|||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.network.ServerApiBlockchair;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.model.BlockchairAddressData;
|
||||
import com.tangem.data.network.model.BlockchairAddressResponse;
|
||||
import com.tangem.data.network.model.BlockchairStatsResponse;
|
||||
import com.tangem.data.network.model.BlockchairTransactionResponse;
|
||||
import com.tangem.data.network.model.BlockchairUnspentOutput;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.reader.CardProtocol;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
|
|
@ -130,6 +133,10 @@ public class BtcCashEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public boolean validateAddress(String address) {
|
||||
if (address != null && address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
return CashAddr.isValidCashAddress(address);
|
||||
}
|
||||
|
||||
|
|
@ -373,8 +380,16 @@ public class BtcCashEngine extends CoinEngine {
|
|||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
String srcLegacyAddress = convertToLegacyAddress(ctx.getCoinData().getWallet());
|
||||
String destLegacyAddress = convertToLegacyAddress(targetAddress);
|
||||
String destLegacyAddress = convertToLegacyAddress(destination);
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
|
||||
|
||||
final ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
|
|
@ -576,8 +591,38 @@ public class BtcCashEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
int calcSize = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
checkFee(blockchainRequestsCallbacks, calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
checkFee(blockchainRequestsCallbacks, calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, int calcSize) {
|
||||
coinData.minFee = null;
|
||||
coinData.maxFee = null;
|
||||
coinData.normalFee = null;
|
||||
|
|
@ -618,6 +663,42 @@ public class BtcCashEngine extends CoinEngine {
|
|||
serverApiBlockchair.getStats(statsObserver);
|
||||
}
|
||||
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
|
||||
final ServerApiBlockchair serverApiBlockchair = new ServerApiBlockchair(ctx.getBlockchain());
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ import com.tangem.data.Blockchain;
|
|||
import com.tangem.data.network.BinanceApi;
|
||||
import com.tangem.data.network.Server;
|
||||
import com.tangem.data.network.ServerApiBinance;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.model.BinanceFees;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.reader.CardProtocol;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
|
|
@ -44,6 +47,10 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
|
@ -147,6 +154,10 @@ public class BinanceEngine extends CoinEngine {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
try {
|
||||
Crypto.decodeAddress(address);
|
||||
} catch (Exception e) {
|
||||
|
|
@ -350,6 +361,14 @@ public class BinanceEngine extends CoinEngine {
|
|||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
String amount;
|
||||
|
||||
if (IncFee) {
|
||||
|
|
@ -368,7 +387,7 @@ public class BinanceEngine extends CoinEngine {
|
|||
Transfer transfer = new Transfer();
|
||||
transfer.setCoin("BNB");
|
||||
transfer.setFromAddress(ctx.getCoinData().getWallet());
|
||||
transfer.setToAddress(targetAddress);
|
||||
transfer.setToAddress(destination);
|
||||
transfer.setAmount(amount);
|
||||
|
||||
TransactionOption options = TransactionOption.DEFAULT_INSTANCE;
|
||||
|
|
@ -466,6 +485,27 @@ public class BinanceEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
checkFee(blockchainRequestsCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
checkFee(blockchainRequestsCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
try {
|
||||
String baseUrl;
|
||||
|
||||
|
|
@ -519,6 +559,42 @@ public class BinanceEngine extends CoinEngine {
|
|||
}
|
||||
}
|
||||
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
|
||||
// RequestBody requestBody = TransactionRequestAssemblerExtSign.createRequestBody(txForSend);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ public class BtcData extends CoinData {
|
|||
//for blockchain.info
|
||||
private boolean hasUnconfirmed = false;
|
||||
|
||||
private String resolvedPayIdAddress = null;
|
||||
|
||||
public Unspents getUnspentInputsDescription() {
|
||||
try {
|
||||
int gatheredUnspents = 0;
|
||||
|
|
@ -92,8 +90,6 @@ public class BtcData extends CoinData {
|
|||
}
|
||||
if (B.containsKey("UseBlockcypher")) useBlockcypher = B.getBoolean("UseBlockcypher");
|
||||
if (B.containsKey("HasUnconfirmed")) useBlockcypher = B.getBoolean("HasUnconfirmed");
|
||||
if (B.containsKey("ResolvedPayIdAddress")) resolvedPayIdAddress = B.getString("ResolvedPayIdAddress");
|
||||
else resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -111,7 +107,6 @@ public class BtcData extends CoinData {
|
|||
if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed);
|
||||
if (useBlockcypher) B.putBoolean("UseBlockcypher", true);
|
||||
if (hasUnconfirmed) B.putBoolean("HasUnconfirmed", true);
|
||||
if (resolvedPayIdAddress != null) B.putString("ResolvedPayIdAddress", resolvedPayIdAddress);
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
|
|
@ -124,7 +119,6 @@ public class BtcData extends CoinData {
|
|||
balanceUnconfirmed = null;
|
||||
unspentTransactions = null;
|
||||
hasUnconfirmed = false;
|
||||
resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
|
||||
|
|
@ -166,12 +160,4 @@ public class BtcData extends CoinData {
|
|||
public void setHasUnconfirmed(boolean hasUnconfirmed) {
|
||||
this.hasUnconfirmed = hasUnconfirmed;
|
||||
}
|
||||
|
||||
public String getResolvedPayIdAddress() {
|
||||
return resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
public void setResolvedPayIdAddress(String resolvedPayIdAddress) {
|
||||
this.resolvedPayIdAddress = resolvedPayIdAddress;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,18 +161,7 @@ public class BtcEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
if (address.contains("$")) { // PayID
|
||||
String[] addressParts = address.split("\\$");
|
||||
|
||||
if (addressParts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
String addressURL = "https://" + addressParts[1] + "/" + addressParts[0];
|
||||
try {
|
||||
new URL(addressURL).toURI();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
if (address.startsWith("1") || address.startsWith("2") || address.startsWith("3") || address.startsWith("n") || address.startsWith("m")) {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@ import com.tangem.App;
|
|||
import com.tangem.Constant;
|
||||
import com.tangem.data.local.PendingTransactionsStorage;
|
||||
import com.tangem.data.network.ServerApiAdalite;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.model.AdaliteResponse;
|
||||
import com.tangem.data.network.model.AdaliteResponseUtxo;
|
||||
import com.tangem.data.network.model.AdaliteTxData;
|
||||
import com.tangem.data.network.model.AdaliteUtxoData;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
import com.tangem.tangem_card.util.Util;
|
||||
|
|
@ -46,6 +50,10 @@ import co.nstant.in.cbor.model.Array;
|
|||
import co.nstant.in.cbor.model.ByteString;
|
||||
import co.nstant.in.cbor.model.DataItem;
|
||||
import co.nstant.in.cbor.model.UnsignedInteger;
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
import static com.tangem.wallet.Base58.decodeBase58;
|
||||
import static com.tangem.wallet.Base58.encodeBase58;
|
||||
|
|
@ -143,6 +151,10 @@ public class CardanoEngine extends CoinEngine {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
byte[] decAddress = Base58.decodeBase58(address);
|
||||
|
||||
if (decAddress == null || decAddress.length == 0) {
|
||||
|
|
@ -399,6 +411,14 @@ public class CardanoEngine extends CoinEngine {
|
|||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
String myAddress = ctx.getCoinData().getWallet();
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKey();
|
||||
List<CardanoData.UnspentOutput> utxoList = coinData.getUnspentOutputs();
|
||||
|
|
@ -457,7 +477,7 @@ public class CardanoEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
//1st output
|
||||
DataItem targetAddressItem = new CborDecoder(new ByteArrayInputStream(decodeBase58(targetAddress))).decode().get(0);
|
||||
DataItem targetAddressItem = new CborDecoder(new ByteArrayInputStream(decodeBase58(destination))).decode().get(0);
|
||||
outputsArray
|
||||
.addArray()
|
||||
.add(targetAddressItem)
|
||||
|
|
@ -697,9 +717,40 @@ public class CardanoEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
// int calcSize = calculateEstimatedTransactionSize(targetAddress, amount);
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
int calcSize = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount);
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
calculateFee(calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
int calcSize = calculateEstimatedTransactionSize(targetAddress, amount);
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
calculateFee(calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateFee(int calcSize) {
|
||||
final double A = 0.155381;
|
||||
final double B = 0.000043946;
|
||||
int calcSize = calculateEstimatedTransactionSize(targetAddress, amount);
|
||||
|
||||
double fee = A + B * calcSize;
|
||||
Amount feeAmount = new Amount(new BigDecimal(fee).setScale(getDecimals(), RoundingMode.UP), getFeeCurrency());
|
||||
|
|
@ -707,8 +758,42 @@ public class CardanoEngine extends CoinEngine {
|
|||
coinData.minFee = feeAmount;
|
||||
coinData.normalFee = feeAmount;
|
||||
coinData.maxFee = feeAmount;
|
||||
}
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ import android.util.Log;
|
|||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.network.ServerApiBitcore;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.model.BitcoreBalanceAndUnspents;
|
||||
import com.tangem.data.network.model.BitcoreSendResponse;
|
||||
import com.tangem.data.network.model.BitcoreUtxo;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.reader.CardProtocol;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
|
|
@ -37,7 +40,9 @@ import java.security.NoSuchProviderException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
public class DucatusEngine extends BtcEngine {
|
||||
|
|
@ -131,6 +136,10 @@ public class DucatusEngine extends BtcEngine {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
if (address.length() < 25) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -397,6 +406,14 @@ public class DucatusEngine extends BtcEngine {
|
|||
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
String myAddress = ctx.getCoinData().getWallet();
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKey();
|
||||
|
||||
|
|
@ -430,7 +447,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
final byte[][] bodyHash = new byte[unspentOutputs.size()][];
|
||||
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
|
||||
txForSign[i] = BTCUtils.buildTXForSign(myAddress, destination, myAddress, unspentOutputs, i, amount, change);
|
||||
bodyHash[i] = Util.calculateSHA256(txForSign[i]);
|
||||
bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]);
|
||||
}
|
||||
|
|
@ -483,7 +500,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
|
||||
}
|
||||
|
||||
byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
byte[] txForSend = BTCUtils.buildTXForSend(destination, myAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
notifyOnNeedSendTransaction(txForSend);
|
||||
return txForSend;
|
||||
}
|
||||
|
|
@ -535,14 +552,76 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
int calcSize = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
calculateFee(calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
calculateFee(calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateFee(int calcSize) {
|
||||
coinData.minFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000089)), ctx.getBlockchain().getCurrency()); //fee for byte from Ducatus wallet for android
|
||||
coinData.normalFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000144)), ctx.getBlockchain().getCurrency());
|
||||
coinData.maxFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000350)), ctx.getBlockchain().getCurrency());
|
||||
}
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ public class EthData extends CoinData {
|
|||
private BigInteger countConfirmedTX = null;
|
||||
private BigInteger countUnconfirmedTX = BigInteger.valueOf(0);
|
||||
|
||||
private String resolvedPayIdAddress = null;
|
||||
|
||||
public BigInteger getConfirmedTXCount() {
|
||||
if (countConfirmedTX == null) {
|
||||
countConfirmedTX = BigInteger.valueOf(0);
|
||||
|
|
@ -48,7 +46,6 @@ public class EthData extends CoinData {
|
|||
public void clearInfo() {
|
||||
super.clearInfo();
|
||||
balance = null;
|
||||
resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
|
||||
|
|
@ -60,14 +57,6 @@ public class EthData extends CoinData {
|
|||
balance = value;
|
||||
}
|
||||
|
||||
public String getResolvedPayIdAddress() {
|
||||
return resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
public void setResolvedPayIdAddress(String resolvedPayIdAddress) {
|
||||
this.resolvedPayIdAddress = resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFromBundle(Bundle B) {
|
||||
super.loadFromBundle(B);
|
||||
|
|
@ -83,8 +72,6 @@ public class EthData extends CoinData {
|
|||
countConfirmedTX = new BigInteger(B.getString("confirmTx"), 16);
|
||||
if (B.containsKey("unconfirmTx"))
|
||||
countUnconfirmedTX = new BigInteger(B.getString("unconfirmTx"), 16);
|
||||
if (B.containsKey("ResolvedPayIdAddress")) resolvedPayIdAddress = B.getString("ResolvedPayIdAddress");
|
||||
else resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -98,7 +85,6 @@ public class EthData extends CoinData {
|
|||
|
||||
B.putString("confirmTx", getConfirmedTXCount().toString(16));
|
||||
B.putString("unconfirmTx", getUnconfirmedTXCount().toString(16));
|
||||
if (resolvedPayIdAddress != null) B.putString("ResolvedPayIdAddress", resolvedPayIdAddress);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
|
|
|
|||
|
|
@ -145,18 +145,7 @@ public class EthEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
if (address.contains("$")) { // PayID
|
||||
String[] addressParts = address.split("\\$");
|
||||
|
||||
if (addressParts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
String addressURL = "https://" + addressParts[1] + "/" + addressParts[0];
|
||||
try {
|
||||
new URL(addressURL).toURI();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
if (!address.startsWith("0x") && !address.startsWith("0X")) {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ import com.tangem.App;
|
|||
import com.tangem.data.local.PendingTransactionsStorage;
|
||||
import com.tangem.data.network.Server;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.model.BlockcypherFee;
|
||||
import com.tangem.data.network.model.BlockcypherResponse;
|
||||
import com.tangem.data.network.model.BlockcypherTx;
|
||||
import com.tangem.data.network.model.BlockcypherTxref;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.reader.CardProtocol;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
|
|
@ -42,6 +45,11 @@ import java.security.NoSuchProviderException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
public class LtcEngine extends BtcEngine {
|
||||
private static final String TAG = LtcEngine.class.getSimpleName();
|
||||
public BtcData coinData = null;
|
||||
|
|
@ -133,6 +141,10 @@ public class LtcEngine extends BtcEngine {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
if (address.length() < 25) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -403,6 +415,14 @@ public class LtcEngine extends BtcEngine {
|
|||
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
String myAddress = ctx.getCoinData().getWallet();
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKey();
|
||||
|
||||
|
|
@ -436,7 +456,7 @@ public class LtcEngine extends BtcEngine {
|
|||
final byte[][] bodyHash = new byte[unspentOutputs.size()][];
|
||||
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
|
||||
txForSign[i] = BTCUtils.buildTXForSign(myAddress, destination, myAddress, unspentOutputs, i, amount, change);
|
||||
bodyHash[i] = Util.calculateSHA256(txForSign[i]);
|
||||
bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]);
|
||||
}
|
||||
|
|
@ -489,7 +509,7 @@ public class LtcEngine extends BtcEngine {
|
|||
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
|
||||
}
|
||||
|
||||
byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
byte[] txForSend = BTCUtils.buildTXForSend(destination, myAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
notifyOnNeedSendTransaction(txForSend);
|
||||
return txForSend;
|
||||
}
|
||||
|
|
@ -632,8 +652,38 @@ public class LtcEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
int calcSize = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
checkFee(blockchainRequestsCallbacks, calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
|
||||
checkFee(blockchainRequestsCallbacks, calcSize);
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, int calcSize) {
|
||||
coinData.minFee = null;
|
||||
coinData.maxFee = null;
|
||||
coinData.normalFee = null;
|
||||
|
|
@ -692,6 +742,42 @@ public class LtcEngine extends BtcEngine {
|
|||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_FEE, "", "");
|
||||
}
|
||||
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
|
||||
final String txStr = BTCUtils.toHex(txForSend);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import android.net.Uri;
|
|||
import android.util.Log;
|
||||
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.ServerApiRootstock;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.wallet.BTCUtils;
|
||||
import com.tangem.wallet.CoinEngine;
|
||||
import com.tangem.wallet.EthTransaction;
|
||||
|
|
@ -15,6 +18,10 @@ import com.tangem.wallet.eth.EthEngine;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
public class RskEngine extends EthEngine {
|
||||
|
||||
private static final String TAG = RskEngine.class.getSimpleName();
|
||||
|
|
@ -132,6 +139,7 @@ public class RskEngine extends EthEngine {
|
|||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
ServerApiRootstock serverApiRootstock = new ServerApiRootstock();
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
// request requestData gasPrice listener
|
||||
ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() {
|
||||
@Override
|
||||
|
|
@ -162,7 +170,11 @@ public class RskEngine extends EthEngine {
|
|||
Log.i(TAG, "normal fee: " + coinData.normalFee.toString());
|
||||
Log.i(TAG, "max fee : " + coinData.maxFee.toString());
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
if (serverApiRootstock.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -173,6 +185,47 @@ public class RskEngine extends EthEngine {
|
|||
};
|
||||
serverApiRootstock.setResponseListener(responseListener);
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
|
||||
SingleObserver<PayIdResponse> observer = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals("RSK") &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
} else {
|
||||
ctx.setError("Unknown address format in PayID response");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ctx.setError("Unknown response format on PayID request");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
if (serverApiRootstock.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
ctx.setError("PayID error:" + e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), observer);
|
||||
}
|
||||
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import android.net.Uri;
|
|||
import android.util.Log;
|
||||
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.ServerApiRootstock;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.wallet.BTCUtils;
|
||||
import com.tangem.wallet.CoinEngine;
|
||||
import com.tangem.wallet.EthTransaction;
|
||||
|
|
@ -15,6 +18,10 @@ import com.tangem.wallet.token.TokenEngine;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
public class RskTokenEngine extends TokenEngine {
|
||||
|
||||
private static final String TAG = RskTokenEngine.class.getSimpleName();
|
||||
|
|
@ -158,6 +165,7 @@ public class RskTokenEngine extends TokenEngine {
|
|||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
ServerApiRootstock serverApiRootstock = new ServerApiRootstock();
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
// request requestData gasPrice listener
|
||||
ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() {
|
||||
@Override
|
||||
|
|
@ -193,7 +201,11 @@ public class RskTokenEngine extends TokenEngine {
|
|||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
if (serverApiRootstock.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -204,6 +216,47 @@ public class RskTokenEngine extends TokenEngine {
|
|||
};
|
||||
serverApiRootstock.setResponseListener(responseListener);
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
|
||||
SingleObserver<PayIdResponse> observer = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals("RSK") &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
} else {
|
||||
ctx.setError("Unknown address format in PayID response");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ctx.setError("Unknown response format on PayID request");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
if (serverApiRootstock.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
ctx.setError("PayID error:" + e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), observer);
|
||||
}
|
||||
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import android.text.InputFilter;
|
|||
import android.util.Log;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.ServerApiStellar;
|
||||
import com.tangem.data.network.StellarRequest;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
import com.tangem.tangem_card.util.Util;
|
||||
|
|
@ -31,6 +34,11 @@ import org.stellar.sdk.responses.operations.OperationResponse;
|
|||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
/**
|
||||
* Created by dvol on 7.01.2019.
|
||||
* <p>
|
||||
|
|
@ -143,6 +151,10 @@ public class XlmAssetEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public boolean validateAddress(String address) {
|
||||
if (address != null && address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
try {
|
||||
KeyPair kp = KeyPair.fromAccountId(address);
|
||||
// TODO is it possible to check address testNet or not
|
||||
|
|
@ -379,6 +391,14 @@ public class XlmAssetEngine extends CoinEngine {
|
|||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
if (coinData.isAssetBalanceZero() && IncFee) {
|
||||
amountValue = new Amount(amountValue.subtract(feeValue), amountValue.getCurrency());
|
||||
}
|
||||
|
|
@ -388,12 +408,12 @@ public class XlmAssetEngine extends CoinEngine {
|
|||
operation = new ChangeTrustOperation.Builder(Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), "900000000000.0000000").build();
|
||||
} else {
|
||||
if (!coinData.isAssetBalanceZero()) {
|
||||
operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), amountValue.toValueString()).build();
|
||||
operation = new PaymentOperation.Builder(KeyPair.fromAccountId(destination), Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), amountValue.toValueString()).build();
|
||||
} else {
|
||||
if (coinData.isTargetAccountCreated())
|
||||
operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build();
|
||||
operation = new PaymentOperation.Builder(KeyPair.fromAccountId(destination), new AssetTypeNative(), amountValue.toValueString()).build();
|
||||
else
|
||||
operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build();
|
||||
operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(destination), amountValue.toValueString()).build();
|
||||
}
|
||||
}
|
||||
TransactionEx transaction = TransactionEx.buildEx(120, coinData.getAccountResponse(), operation);
|
||||
|
|
@ -564,9 +584,62 @@ public class XlmAssetEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
// TODO: get fee stats?
|
||||
coinData.minFee = coinData.normalFee = coinData.maxFee = coinData.getBaseFee();
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount);
|
||||
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ import android.util.Log;
|
|||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.ServerApiStellar;
|
||||
import com.tangem.data.network.StellarRequest;
|
||||
import com.tangem.data.network.model.PayIdAddress;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
import com.tangem.tangem_card.util.Util;
|
||||
|
|
@ -31,6 +34,11 @@ import org.stellar.sdk.responses.operations.OperationResponse;
|
|||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
/**
|
||||
* Created by dvol on 7.01.2019.
|
||||
* <p>
|
||||
|
|
@ -123,6 +131,10 @@ public class XlmEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public boolean validateAddress(String address) {
|
||||
if (address != null && address.contains("$")) { // PayID
|
||||
return validatePayId(address);
|
||||
}
|
||||
|
||||
try {
|
||||
KeyPair kp = KeyPair.fromAccountId(address);
|
||||
// TODO is it possible to check address testNet or not
|
||||
|
|
@ -356,18 +368,26 @@ public class XlmEngine extends CoinEngine {
|
|||
|
||||
StrictMode.setThreadPolicy(policy);
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
if (IncFee) {
|
||||
amountValue = new Amount(amountValue.subtract(feeValue), amountValue.getCurrency());
|
||||
}
|
||||
|
||||
Operation operation;
|
||||
if (coinData.isTargetAccountCreated()) {
|
||||
operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build();
|
||||
operation = new PaymentOperation.Builder(KeyPair.fromAccountId(destination), new AssetTypeNative(), amountValue.toValueString()).build();
|
||||
} else {
|
||||
if (amountValue.compareTo(coinData.getReserve()) < 0) {
|
||||
throw new IllegalArgumentException("Target account is not created. Send " + coinData.getReserve().toDescriptionString(getDecimals()) + " or more to create");
|
||||
}
|
||||
operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build();
|
||||
operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(destination), amountValue.toValueString()).build();
|
||||
}
|
||||
|
||||
TransactionEx transaction = TransactionEx.buildEx(120, coinData.getAccountResponse(), operation);
|
||||
|
|
@ -541,7 +561,61 @@ public class XlmEngine extends CoinEngine {
|
|||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
coinData.minFee = coinData.normalFee = coinData.maxFee = coinData.getBaseFee();
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount); //TODO: move?
|
||||
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolvePayID(String targetAddress, CompletableObserver observer) {
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
SingleObserver<PayIdResponse> payIdObserver = new DisposableSingleObserver<PayIdResponse>() {
|
||||
@Override
|
||||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals(ctx.getBlockchain().getCurrency()) &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
observer.onComplete();
|
||||
} else {
|
||||
observer.onError(new Exception("Unknown address format in PayID response"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
observer.onError(new Exception("Unknown response format on PayID request"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage());
|
||||
observer.onError(new Exception("PayID error:" + e.getMessage()));
|
||||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), payIdObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class XrpData extends CoinData {
|
|||
|
||||
private Boolean accountNotFound, targetAccountCreated = false;
|
||||
|
||||
private String resolvedPayIdAddress, resolvedPayIdTag = null;
|
||||
private String resolvedPayIdTag = null;
|
||||
|
||||
@Override
|
||||
public void loadFromBundle(Bundle B) {
|
||||
|
|
@ -37,8 +37,6 @@ public class XrpData extends CoinData {
|
|||
else accountNotFound = false;
|
||||
if (B.containsKey("TargetAccountCreated")) targetAccountCreated = B.getBoolean("TargetAccountCreated");
|
||||
else targetAccountCreated = false;
|
||||
if (B.containsKey("ResolvedPayIdAddress")) resolvedPayIdAddress = B.getString("ResolvedPayIdAddress");
|
||||
else resolvedPayIdAddress = null;
|
||||
if (B.containsKey("ResolvedPayIdTag")) resolvedPayIdTag = B.getString("ResolvedPayIdTag");
|
||||
else resolvedPayIdTag = null;
|
||||
}
|
||||
|
|
@ -53,7 +51,6 @@ public class XrpData extends CoinData {
|
|||
if (reserve != null) B.putLong("Reserve", reserve);
|
||||
if (accountNotFound != null) B.putBoolean("AccoundNotFound", accountNotFound);
|
||||
if (targetAccountCreated != null) B.putBoolean("TargetAccountCreated", targetAccountCreated);
|
||||
if (resolvedPayIdAddress != null) B.putString("ResolvedPayIdAddress", resolvedPayIdAddress);
|
||||
if (resolvedPayIdTag != null) B.putString("ResolvedPayIdTag", resolvedPayIdTag);
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
|
|
@ -69,7 +66,6 @@ public class XrpData extends CoinData {
|
|||
reserve = 20000000L;
|
||||
accountNotFound = false;
|
||||
targetAccountCreated = false;
|
||||
resolvedPayIdAddress = null;
|
||||
resolvedPayIdTag = null;
|
||||
}
|
||||
|
||||
|
|
@ -135,14 +131,6 @@ public class XrpData extends CoinData {
|
|||
return hasBalanceInfo() && !balanceConfirmed.equals(balanceUnconfirmed);
|
||||
}
|
||||
|
||||
public String getResolvedPayIdAddress() {
|
||||
return resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
public void setResolvedPayIdAddress(String resolvedPayIdAddress) {
|
||||
this.resolvedPayIdAddress = resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
public String getResolvedPayIdTag() {
|
||||
return resolvedPayIdTag;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,18 +124,7 @@ public class XrpEngine extends CoinEngine {
|
|||
return false;
|
||||
}
|
||||
if (address.contains("$")) { // PayID
|
||||
String[] addressParts = address.split("\\$");
|
||||
|
||||
if (addressParts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
String addressURL = "https://" + addressParts[1] + "/" + addressParts[0];
|
||||
try {
|
||||
new URL(addressURL).toURI();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return validatePayId(address);
|
||||
}
|
||||
try {
|
||||
Addresses.decodeAccountID(address);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.3.72',
|
||||
build_gradle: '4.0.0',
|
||||
build_gradle: '4.0.1',
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue