Updated on 2026-08-14

This commit is contained in:
Tangem 2020-07-21 08:46:29 +00:00
commit 36fd3eb4dd
20 changed files with 737 additions and 109 deletions

View file

@ -10,17 +10,13 @@ private val payIdSupported = EnumSet.of(
Blockchain.Litecoin,
Blockchain.Stellar,
Blockchain.StellarAsset,
Blockchain.Binance,
Blockchain.Cardano,
Blockchain.Ducatus,
Blockchain.BitcoinCash,
Blockchain.Binance,
Blockchain.BinanceAsset,
Blockchain.Rootstock,
Blockchain.RootstockToken,
Blockchain.Tezos,
Blockchain.Eos,
Blockchain.Matic
Blockchain.RootstockToken
)
fun Blockchain.isPayIdSupported(): Boolean {

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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());

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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")) {

View file

@ -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

View file

@ -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

View file

@ -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());

View file

@ -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")) {

View file

@ -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);

View file

@ -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(), "", "");
}

View file

@ -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(), "", "");
}

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -1,4 +1,4 @@
ext.versions = [
kotlin : '1.3.72',
build_gradle: '4.0.0',
build_gradle: '4.0.1',
]