Updated on 2026-08-14
This commit is contained in:
commit
d106f4928c
7 changed files with 269 additions and 35 deletions
|
|
@ -4,14 +4,12 @@ import com.tangem.data.network.model.PayIdResponse;
|
|||
|
||||
import io.reactivex.Single;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.Path;
|
||||
|
||||
public interface PayIdApi {
|
||||
@Headers({
|
||||
"Accept: application/xrpl-mainnet+json",
|
||||
"PayID-Version: 1.0"
|
||||
})
|
||||
@Headers("PayID-Version: 1.0")
|
||||
@GET("{user}")
|
||||
Single<PayIdResponse> getAddress(@Path("user") String user);
|
||||
Single<PayIdResponse> getAddress(@Path("user") String user, @Header("Accept") String acceptHeader);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.data.network;
|
||||
|
||||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.model.PayIdResponse;
|
||||
import com.tangem.tangem_card.util.Log;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
|
@ -17,12 +19,21 @@ public class ServerApiPayId {
|
|||
|
||||
private int requestsCount = 0;
|
||||
|
||||
public String getAcceptHeader(Blockchain blockchain) throws InvalidParameterException {
|
||||
switch (blockchain) {
|
||||
case Ripple: return "application/xrpl-mainnet+json";
|
||||
case Bitcoin: return "application/btc-mainnet+json";
|
||||
case Ethereum: return "application/eth-mainnet+json";
|
||||
default: throw new InvalidParameterException("PayID is not supported for " + blockchain.getOfficialName());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRequestsSequenceCompleted() {
|
||||
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
|
||||
return requestsCount <= 0;
|
||||
}
|
||||
|
||||
public void getAddress(String payID, SingleObserver<PayIdResponse> addressObserver) {
|
||||
public void getAddress(String payID, Blockchain blockchain, SingleObserver<PayIdResponse> addressObserver) throws InvalidParameterException {
|
||||
requestsCount++;
|
||||
Log.i(TAG, "new getAddress request");
|
||||
|
||||
|
|
@ -38,7 +49,7 @@ public class ServerApiPayId {
|
|||
|
||||
PayIdApi api = retrofit.create(PayIdApi.class);
|
||||
|
||||
Single<PayIdResponse> addressSingle = api.getAddress(user)
|
||||
Single<PayIdResponse> addressSingle = api.getAddress(user, getAcceptHeader(blockchain))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((object, throwable) -> requestsCount--);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public class BtcData extends CoinData {
|
|||
//for blockchain.info
|
||||
private boolean hasUnconfirmed = false;
|
||||
|
||||
private String resolvedPayIdAddress = null;
|
||||
|
||||
public Unspents getUnspentInputsDescription() {
|
||||
try {
|
||||
int gatheredUnspents = 0;
|
||||
|
|
@ -90,6 +92,8 @@ 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
|
||||
|
|
@ -107,6 +111,7 @@ 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());
|
||||
}
|
||||
|
|
@ -119,6 +124,7 @@ public class BtcData extends CoinData {
|
|||
balanceUnconfirmed = null;
|
||||
unspentTransactions = null;
|
||||
hasUnconfirmed = false;
|
||||
resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
|
||||
|
|
@ -160,4 +166,12 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.network.Server;
|
|||
import com.tangem.data.network.ServerApiBlockchainInfo;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiCommon;
|
||||
import com.tangem.data.network.ServerApiPayId;
|
||||
import com.tangem.data.network.model.BlockchainInfoAddress;
|
||||
import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents;
|
||||
import com.tangem.data.network.model.BlockchainInfoInput;
|
||||
|
|
@ -21,6 +22,8 @@ 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;
|
||||
|
|
@ -47,6 +50,7 @@ import java.io.IOException;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
|
@ -54,8 +58,10 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.CompletableObserver;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
|
|
@ -154,6 +160,21 @@ public class BtcEngine 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (address.startsWith("1") || address.startsWith("2") || address.startsWith("3") || address.startsWith("n") || address.startsWith("m")) {
|
||||
if (address.length() < 25) {
|
||||
return false;
|
||||
|
|
@ -458,6 +479,14 @@ public class BtcEngine extends CoinEngine {
|
|||
String myAddress = ctx.getCoinData().getWallet();
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKey();
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
// /blockcypher/
|
||||
// for (BtcData.UnspentTransaction utxo : coinData.getUnspentTransactions()) {
|
||||
// unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null));
|
||||
|
|
@ -502,7 +531,7 @@ public class BtcEngine extends CoinEngine {
|
|||
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]);
|
||||
}
|
||||
|
|
@ -555,7 +584,7 @@ public class BtcEngine extends CoinEngine {
|
|||
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;
|
||||
}
|
||||
|
|
@ -877,8 +906,6 @@ public class BtcEngine 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));
|
||||
coinData.minFee = null;
|
||||
coinData.maxFee = null;
|
||||
coinData.normalFee = null;
|
||||
|
|
@ -886,6 +913,26 @@ public class BtcEngine extends CoinEngine {
|
|||
if (!coinData.isUseBlockcypher()) {
|
||||
final ServerApiCommon serverApiCommon = new ServerApiCommon();
|
||||
|
||||
final Integer[] calcSize = new Integer[1];
|
||||
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
calcSize[0] = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize[0]));
|
||||
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY);
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_NORMAL);
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
final ServerApiCommon.EstimatedFeeListener estimatedFeeListener = new ServerApiCommon.EstimatedFeeListener() {
|
||||
@Override
|
||||
public void onSuccess(int blockCount, String estimateFeeResponse) {
|
||||
|
|
@ -898,8 +945,8 @@ public class BtcEngine extends CoinEngine {
|
|||
return;
|
||||
}
|
||||
|
||||
if (calcSize != 0) {
|
||||
fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024), BigDecimal.ROUND_DOWN); // per Kb -> per byte
|
||||
if (calcSize[0] != 0) {
|
||||
fee = fee.multiply(new BigDecimal(calcSize[0])).divide(new BigDecimal(1024), BigDecimal.ROUND_DOWN); // per Kb -> per byte
|
||||
} else {
|
||||
if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
serverApiCommon.requestBtcEstimatedFee(blockCount);
|
||||
|
|
@ -948,13 +995,38 @@ public class BtcEngine extends CoinEngine {
|
|||
};
|
||||
serverApiCommon.setBtcEstimatedFeeListener(estimatedFeeListener);
|
||||
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY);
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_NORMAL);
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL);
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
calcSize[0] = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize[0]));
|
||||
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY);
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_NORMAL);
|
||||
serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL);
|
||||
}
|
||||
|
||||
} else {
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
final Integer[] calcSize = new Integer[1];
|
||||
|
||||
CompletableObserver payIdObserver = new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
calcSize[0] = calculateEstimatedTransactionSize(coinData.getResolvedPayIdAddress(), amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize[0]));
|
||||
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_FEE, "", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
};
|
||||
|
||||
ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, BlockcypherResponse blockcypherResponse) {
|
||||
|
|
@ -970,9 +1042,9 @@ public class BtcEngine extends CoinEngine {
|
|||
BigDecimal normalByteFee = new BigDecimal(blockcypherFee.getMedium_fee_per_kb()).divide(BigDecimal.valueOf(1024));
|
||||
BigDecimal maxByteFee = new BigDecimal(blockcypherFee.getHigh_fee_per_kb()).divide(BigDecimal.valueOf(1024));
|
||||
|
||||
CoinEngine.InternalAmount minIntAmount = new CoinEngine.InternalAmount(minByteFee.multiply(BigDecimal.valueOf(calcSize)), "satoshi");
|
||||
CoinEngine.InternalAmount normalIntAmount = new CoinEngine.InternalAmount(normalByteFee.multiply(BigDecimal.valueOf(calcSize)), "satoshi");
|
||||
CoinEngine.InternalAmount maxIntAmount = new CoinEngine.InternalAmount(maxByteFee.multiply(BigDecimal.valueOf(calcSize)), "satoshi");
|
||||
CoinEngine.InternalAmount minIntAmount = new CoinEngine.InternalAmount(minByteFee.multiply(BigDecimal.valueOf(calcSize[0])), "satoshi");
|
||||
CoinEngine.InternalAmount normalIntAmount = new CoinEngine.InternalAmount(normalByteFee.multiply(BigDecimal.valueOf(calcSize[0])), "satoshi");
|
||||
CoinEngine.InternalAmount maxIntAmount = new CoinEngine.InternalAmount(maxByteFee.multiply(BigDecimal.valueOf(calcSize[0])), "satoshi");
|
||||
|
||||
coinData.minFee = convertToAmount(minIntAmount);
|
||||
coinData.normalFee = convertToAmount(normalIntAmount);
|
||||
|
|
@ -999,10 +1071,53 @@ public class BtcEngine extends CoinEngine {
|
|||
|
||||
serverApiBlockcypher.setResponseListener(blockcypherListener);
|
||||
|
||||
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_FEE, "", "");
|
||||
if (targetAddress.contains("$")) { // PayID
|
||||
resolvePayID(targetAddress, payIdObserver);
|
||||
} else {
|
||||
calcSize[0] = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize[0]));
|
||||
|
||||
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("BTC") &&
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ 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);
|
||||
|
|
@ -46,6 +48,7 @@ public class EthData extends CoinData {
|
|||
public void clearInfo() {
|
||||
super.clearInfo();
|
||||
balance = null;
|
||||
resolvedPayIdAddress = null;
|
||||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
|
||||
|
|
@ -57,6 +60,13 @@ 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) {
|
||||
|
|
@ -73,6 +83,8 @@ 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
|
||||
|
|
@ -86,6 +98,7 @@ 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());
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ import com.tangem.Constant;
|
|||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.ServerApiBlockcypher;
|
||||
import com.tangem.data.network.ServerApiInfura;
|
||||
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.BlockcypherTxref;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
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.util.CryptoUtil;
|
||||
|
|
@ -33,8 +36,12 @@ import org.bitcoinj.core.ECKey;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 15.02.2018.
|
||||
*/
|
||||
|
|
@ -137,6 +144,21 @@ public class EthEngine 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!address.startsWith("0x") && !address.startsWith("0X")) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -266,7 +288,7 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public boolean checkNewTransactionAmount(Amount amount) {
|
||||
if( BuildConfig.FLAVOR== Constant.FLAVOR_TANGEM_CARDANO ) {
|
||||
if (BuildConfig.FLAVOR == Constant.FLAVOR_TANGEM_CARDANO) {
|
||||
return true;
|
||||
}
|
||||
Amount balance = getBalance();
|
||||
|
|
@ -387,6 +409,14 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
Log.e(TAG, "Construct transaction " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding"));
|
||||
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
BigInteger nonceValue = coinData.getConfirmedTXCount();
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKey();
|
||||
|
||||
|
|
@ -401,13 +431,12 @@ public class EthEngine extends CoinEngine {
|
|||
BigInteger gasLimit = BigInteger.valueOf(21000);
|
||||
Integer chainId = this.getChainIdNum();
|
||||
|
||||
String to = targetAddress;
|
||||
|
||||
if (to.startsWith("0x") || to.startsWith("0X")) {
|
||||
to = to.substring(2);
|
||||
if (destination.startsWith("0x") || destination.startsWith("0X")) {
|
||||
destination = destination.substring(2);
|
||||
}
|
||||
|
||||
final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
|
||||
final EthTransaction tx = EthTransaction.create(destination, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
|
||||
|
||||
return new SignTask.TransactionToSign() {
|
||||
@Override
|
||||
|
|
@ -510,7 +539,7 @@ public class EthEngine extends CoinEngine {
|
|||
break;
|
||||
}
|
||||
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
|
|
@ -521,7 +550,7 @@ public class EthEngine extends CoinEngine {
|
|||
public void onFail(String method, String message) {
|
||||
Log.e(TAG, "onFail: " + method + " " + message);
|
||||
ctx.setError(message);
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
|
|
@ -556,7 +585,7 @@ public class EthEngine extends CoinEngine {
|
|||
Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception");
|
||||
}
|
||||
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
|
|
@ -565,7 +594,7 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
|
||||
Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
|
|
@ -575,7 +604,7 @@ public class EthEngine extends CoinEngine {
|
|||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.i(TAG, "onFail: " + method + " " + message);
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
|
|
@ -593,6 +622,8 @@ public class EthEngine extends CoinEngine {
|
|||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
ServerApiInfura serverApiInfura = new ServerApiInfura(ctx.getBlockchain());
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
// request requestData eth gasPrice listener
|
||||
ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
|
||||
@Override
|
||||
|
|
@ -623,7 +654,11 @@ public class EthEngine extends CoinEngine {
|
|||
Log.i(TAG, "normal fee: " + coinData.normalFee.toString());
|
||||
Log.i(TAG, "max fee : " + coinData.maxFee.toString());
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -634,6 +669,52 @@ public class EthEngine extends CoinEngine {
|
|||
};
|
||||
serverApiInfura.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("ETH") &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
} 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 (serverApiInfura.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);
|
||||
}
|
||||
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", "");
|
||||
}
|
||||
|
||||
|
|
@ -648,7 +729,7 @@ public class EthEngine extends CoinEngine {
|
|||
@Override
|
||||
public void onSuccess(String method, InfuraResponse infuraResponse) {
|
||||
if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
|
||||
if (infuraResponse.getResult()==null || infuraResponse.getResult().isEmpty()) {
|
||||
if (infuraResponse.getResult() == null || infuraResponse.getResult().isEmpty()) {
|
||||
ctx.setError("Rejected by node: " + infuraResponse.getError());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
|
|
@ -675,7 +756,9 @@ public class EthEngine extends CoinEngine {
|
|||
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr);
|
||||
}
|
||||
|
||||
public int pendingTransactionTimeoutInSeconds() { return 10; }
|
||||
public int pendingTransactionTimeoutInSeconds() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needMultipleLinesForBalance() {
|
||||
|
|
|
|||
|
|
@ -658,7 +658,7 @@ public class XrpEngine extends CoinEngine {
|
|||
}
|
||||
};
|
||||
|
||||
serverApiPayId.getAddress(targetAddress, observer);
|
||||
serverApiPayId.getAddress(targetAddress, ctx.getBlockchain(), observer);
|
||||
} else {
|
||||
XrpXAddressDecoded xAddressDecoded = XrpXAddressService.Companion.decode(targetAddress);
|
||||
if (xAddressDecoded == null) { // classic address
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue