Updated on 2026-08-14
This commit is contained in:
commit
d7fd830c9c
8 changed files with 122 additions and 10 deletions
|
|
@ -38,7 +38,7 @@ public enum Blockchain {
|
|||
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo"),
|
||||
TokenEmv("TTW", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum");
|
||||
|
||||
static private EnumSet<Blockchain> payIdSupported = EnumSet.of(Blockchain.Ripple, Blockchain.Ethereum, Blockchain.Bitcoin);
|
||||
static private EnumSet<Blockchain> payIdSupported = EnumSet.of(Blockchain.Ripple, Blockchain.Ethereum, Blockchain.Bitcoin, Blockchain.Token);
|
||||
|
||||
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
|
||||
mID = ID;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ public class ServerApiPayId {
|
|||
switch (blockchain) {
|
||||
case Ripple: return "application/xrpl-mainnet+json";
|
||||
case Bitcoin: return "application/btc-mainnet+json";
|
||||
case Ethereum: return "application/eth-mainnet+json";
|
||||
case Ethereum:
|
||||
case Token:
|
||||
return "application/eth-mainnet+json";
|
||||
default: throw new InvalidParameterException("PayID is not supported for " + blockchain.getOfficialName());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,5 +20,8 @@ data class PayIdAddress(
|
|||
|
||||
data class PayIdAddressDetails(
|
||||
@SerializedName("address")
|
||||
var address: String? = null
|
||||
var address: String? = null,
|
||||
|
||||
@SerializedName("tag")
|
||||
var tag: String? = null
|
||||
)
|
||||
|
|
@ -375,10 +375,10 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte
|
|||
}
|
||||
|
||||
private fun createPayId(payId: String) {
|
||||
val network = if (ctx.blockchain == Blockchain.Ripple) "XRPL" else ctx.blockchain.id
|
||||
val network = if (ctx.blockchain == Blockchain.Ripple) "XRPL" else ctx.blockchain.currency
|
||||
viewModel.setPayId(
|
||||
Util.byteArrayToHexString(ctx.card!!.cid!!),
|
||||
Util.byteArrayToHexString(ctx.card!!.cardPublicKey!!),
|
||||
Util.byteArrayToHexString(ctx.card.cid!!),
|
||||
Util.byteArrayToHexString(ctx.card.cardPublicKey!!),
|
||||
payId,
|
||||
ctx.coinData.wallet,
|
||||
network
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ import com.tangem.App;
|
|||
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;
|
||||
|
|
@ -34,8 +37,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 20.03.2018.
|
||||
*/
|
||||
|
|
@ -165,6 +172,21 @@ public class TokenEngine 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;
|
||||
}
|
||||
|
|
@ -439,10 +461,18 @@ public class TokenEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
if (amountValue.getCurrency().equals(this.getBlockchain().getCurrency())) {
|
||||
return constructTransactionCoin(feeValue, amountValue, IncFee, targetAddress);
|
||||
String destination;
|
||||
//PayID
|
||||
if (coinData.getResolvedPayIdAddress() != null) {
|
||||
destination = coinData.getResolvedPayIdAddress();
|
||||
} else {
|
||||
return constructTransactionToken(feeValue, amountValue, IncFee, targetAddress);
|
||||
destination = targetAddress;
|
||||
}
|
||||
|
||||
if (amountValue.getCurrency().equals(this.getBlockchain().getCurrency())) {
|
||||
return constructTransactionCoin(feeValue, amountValue, IncFee, destination);
|
||||
} else {
|
||||
return constructTransactionToken(feeValue, amountValue, IncFee, destination);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -776,6 +806,8 @@ public class TokenEngine extends CoinEngine {
|
|||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
ServerApiInfura serverApiInfura = new ServerApiInfura();
|
||||
final ServerApiPayId serverApiPayId = new ServerApiPayId();
|
||||
|
||||
// request requestData eth gasPrice listener
|
||||
ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
|
||||
@Override
|
||||
|
|
@ -826,6 +858,57 @@ public class TokenEngine 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)) {
|
||||
if (resolvedAddress.equals(coinData.getWallet())) {
|
||||
ctx.setError(R.string.prepare_transaction_error_same_address);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
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(), "", "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class XrpData extends CoinData {
|
|||
|
||||
private Boolean accountNotFound, targetAccountCreated = false;
|
||||
|
||||
private String resolvedPayIdAddress = null;
|
||||
private String resolvedPayIdAddress, resolvedPayIdTag = null;
|
||||
|
||||
@Override
|
||||
public void loadFromBundle(Bundle B) {
|
||||
|
|
@ -39,6 +39,8 @@ public class XrpData extends CoinData {
|
|||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -52,6 +54,7 @@ public class XrpData extends CoinData {
|
|||
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());
|
||||
}
|
||||
|
|
@ -67,6 +70,7 @@ public class XrpData extends CoinData {
|
|||
accountNotFound = false;
|
||||
targetAccountCreated = false;
|
||||
resolvedPayIdAddress = null;
|
||||
resolvedPayIdTag = null;
|
||||
}
|
||||
|
||||
// balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present
|
||||
|
|
@ -138,4 +142,12 @@ public class XrpData extends CoinData {
|
|||
public void setResolvedPayIdAddress(String resolvedPayIdAddress) {
|
||||
this.resolvedPayIdAddress = resolvedPayIdAddress;
|
||||
}
|
||||
|
||||
public String getResolvedPayIdTag() {
|
||||
return resolvedPayIdTag;
|
||||
}
|
||||
|
||||
public void setResolvedPayIdTag(String resolvedPayIdTag) {
|
||||
this.resolvedPayIdTag = resolvedPayIdTag;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -367,6 +367,15 @@ public class XrpEngine extends CoinEngine {
|
|||
destinationTag = decodedXAddress.getDestinationTag();
|
||||
}
|
||||
|
||||
if (coinData.getResolvedPayIdTag() != null) {
|
||||
int resolvedTag = Integer.parseInt(coinData.getResolvedPayIdTag());
|
||||
if (destinationTag == null || destinationTag == resolvedTag) {
|
||||
destinationTag = resolvedTag;
|
||||
} else { // Different destination tags in PayID response and in X-address
|
||||
throw new IllegalArgumentException("Conflicting destination tags found");
|
||||
}
|
||||
}
|
||||
|
||||
if (IncFee) {
|
||||
amount = convertToInternalAmount(amountValue).subtract(convertToInternalAmount(feeValue)).setScale(0).toPlainString();
|
||||
} else {
|
||||
|
|
@ -621,15 +630,18 @@ public class XrpEngine extends CoinEngine {
|
|||
public void onSuccess(PayIdResponse payIdResponse) {
|
||||
try {
|
||||
String resolvedAddress = null;
|
||||
String resolvedTag = null;
|
||||
for (PayIdAddress address : payIdResponse.getAddresses()) {
|
||||
if (address.getPaymentNetwork().equals("XRPL") &&
|
||||
address.getEnvironment().equals("MAINNET")) {
|
||||
resolvedAddress = address.getAddressDetails().getAddress();
|
||||
resolvedTag = address.getAddressDetails().getTag();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validateAddress(resolvedAddress)) {
|
||||
coinData.setResolvedPayIdAddress(resolvedAddress);
|
||||
coinData.setResolvedPayIdTag(resolvedTag);
|
||||
|
||||
XrpXAddressDecoded xAddressDecoded = XrpXAddressService.Companion.decode(resolvedAddress);
|
||||
if (xAddressDecoded == null) { // classic address
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue