From 6e5b27298759e8829713cccca83495102d0ddd4d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jun 2020 12:53:11 +0300 Subject: [PATCH 1/5] Updated on 2026-08-14 --- app/build.gradle | 9 +- .../com/tangem/data/network/PayIdApi.java | 17 +++ .../tangem/data/network/ServerApiPayId.java | 48 ++++++++ .../data/network/model/PayIdResponse.kt | 18 +++ .../java/com/tangem/wallet/xrp/XrpBase58.java | 115 ------------------ .../java/com/tangem/wallet/xrp/XrpData.java | 14 +++ .../java/com/tangem/wallet/xrp/XrpEngine.java | 108 +++++++++++++--- build.gradle | 2 +- dependencies.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- tangem-sdk-old/build.gradle | 2 +- tangem-sdk/build.gradle | 2 +- 12 files changed, 198 insertions(+), 141 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/PayIdApi.java create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiPayId.java create mode 100644 app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt delete mode 100644 app/src/main/java/com/tangem/wallet/xrp/XrpBase58.java diff --git a/app/build.gradle b/app/build.gradle index af2ce223fa..027978da9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,18 +54,19 @@ android { } } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } packagingOptions { pickFirst('META-INF/proguard/okhttp3.pro') - // for one.block:eosiojava:0.1.0 +// for one.block:eosiojava:0.1.0 exclude 'lib/x86_64/darwin/libscrypt.dylib' exclude 'lib/x86_64/freebsd/libscrypt.so' exclude 'lib/x86_64/linux/libscrypt.so' exclude 'org.slf4j:slf4j-jdk14:1.7.25' } - buildToolsVersion '28.0.3' + buildToolsVersion '29.0.3' } repositories { @@ -81,6 +82,8 @@ dependencies { implementation project(':tangem-sdk-old') implementation project(':server-android') + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5' + implementation 'androidx.navigation:navigation-fragment:2.2.2' implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' implementation 'androidx.legacy:legacy-support-v4:1.0.0' @@ -132,7 +135,9 @@ dependencies { implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta04' implementation 'com.google.firebase:firebase-perf:19.0.6' + //dependencies for XRP implementation files('libs/ripple-core-0.0.1.jar') + implementation 'io.xpring:xpring4j:2.2.0' //4 dependencies for ripple-core TODO: move to module? implementation 'net.i2p.crypto:eddsa:0.3.0' implementation 'org.bouncycastle:bcprov-jdk15on:1.61' diff --git a/app/src/main/java/com/tangem/data/network/PayIdApi.java b/app/src/main/java/com/tangem/data/network/PayIdApi.java new file mode 100644 index 0000000000..032968baaa --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/PayIdApi.java @@ -0,0 +1,17 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.PayIdResponse; + +import io.reactivex.Single; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.Path; + +public interface PayIdApi { + @Headers({ + "Accept: application/xrpl-mainnet+json", + "PayID-Version: 1.0" + }) + @GET("{user}") + Single getAddress(@Path("user") String user); +} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiPayId.java b/app/src/main/java/com/tangem/data/network/ServerApiPayId.java new file mode 100644 index 0000000000..237fcb279d --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiPayId.java @@ -0,0 +1,48 @@ +package com.tangem.data.network; + +import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import com.tangem.App; +import com.tangem.data.network.model.PayIdResponse; +import com.tangem.tangem_card.util.Log; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ServerApiPayId { + private static String TAG = ServerApiPayId.class.getSimpleName(); + + private int requestsCount = 0; + + 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 addressObserver) { + requestsCount++; + Log.i(TAG, "new getAddress request"); + + String[] addressParts = payID.split("\\$"); + String user = addressParts[0]; + String domain = addressParts[1]; + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + domain + "/") + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + + PayIdApi api = retrofit.create(PayIdApi.class); + + Single addressSingle = api.getAddress(user) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnEvent((object, throwable) -> requestsCount--); + + addressSingle.subscribe(addressObserver); + } +} diff --git a/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt b/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt new file mode 100644 index 0000000000..5d383b7b0c --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt @@ -0,0 +1,18 @@ +package com.tangem.data.network.model + +import com.google.gson.annotations.SerializedName + +data class PayIdResponse( + @SerializedName("addresses") + var addresses: List? = null +) + +data class PayIdAddress( + @SerializedName("addressDetails") + var addressDetails: PayIdAddressDetails? = null +) + +data class PayIdAddressDetails( + @SerializedName("address") + var address: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpBase58.java b/app/src/main/java/com/tangem/wallet/xrp/XrpBase58.java deleted file mode 100644 index c149776f2c..0000000000 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpBase58.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.tangem.wallet.xrp; - -import java.math.BigInteger; - -/** - * Created by Ilia on 15.02.2018. - */ - -public class XrpBase58 { - private static final char[] BASE58 = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz".toCharArray(); - - private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long - private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS - private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, - -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, - -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, - 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; - - public static byte[] decodeBase58(String input) { - if (input == null) { - return null; - } - input = input.trim(); - if (input.length() == 0) { - return new byte[0]; - } - BigInteger resultNum = BigInteger.ZERO; - int nLeadingZeros = 0; - while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) { - nLeadingZeros++; - } - long acc = 0; - int nDigits = 0; - int p = nLeadingZeros; - while (p < input.length()) { - int v = BASE58_VALUES[input.charAt(p) & 0xff]; - if (v >= 0) { - acc *= 58; - acc += v; - nDigits++; - if (nDigits == BASE58_CHUNK_DIGITS) { - resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc)); - acc = 0; - nDigits = 0; - } - p++; - } else { - break; - } - } - if (nDigits > 0) { - long mul = 58; - while (--nDigits > 0) { - mul *= 58; - } - resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc)); - } - final int BASE58_SPACE = -2; - while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) { - p++; - } - if (p < input.length()) { - return null; - } - byte[] plainNumber = resultNum.toByteArray(); - int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0; - byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs]; - System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs); - return result; - } - - public static String encodeBase58(byte[] input) { - if (input == null) { - return null; - } - StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1); - BigInteger bn = new BigInteger(1, input); - long rem; - while (true) { - BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD); - bn = divideAndRemainder[0]; - rem = divideAndRemainder[1].longValue(); - if (bn.compareTo(BigInteger.ZERO) == 0) { - break; - } - for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) { - str.append(BASE58[(int) (rem % 58)]); - rem /= 58; - } - } - while (rem != 0) { - str.append(BASE58[(int) (rem % 58)]); - rem /= 58; - } - str.reverse(); - int nLeadingZeros = 0; - while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) { - str.insert(0, BASE58[0]); - nLeadingZeros++; - } - return str.toString(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index ca79936381..09bc6276fa 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -19,6 +19,8 @@ public class XrpData extends CoinData { private Boolean accountNotFound, targetAccountCreated = false; + private String resolvedPayIdAddress = null; + @Override public void loadFromBundle(Bundle B) { super.loadFromBundle(B); @@ -35,6 +37,8 @@ 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; } @Override @@ -47,6 +51,7 @@ 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); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -61,6 +66,7 @@ public class XrpData extends CoinData { reserve = 20000000L; accountNotFound = false; targetAccountCreated = false; + resolvedPayIdAddress = null; } // balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present @@ -124,4 +130,12 @@ public class XrpData extends CoinData { public boolean hasUnconfirmed() { return hasBalanceInfo() && !balanceConfirmed.equals(balanceUnconfirmed); } + + public String getResolvedPayIdAddress() { + return resolvedPayIdAddress; + } + + public void setResolvedPayIdAddress(String resolvedPayIdAddress) { + this.resolvedPayIdAddress = resolvedPayIdAddress; + } } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index ff06160ba2..a667537b8f 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -10,7 +10,9 @@ import com.ripple.crypto.ecdsa.ECDSASignature; import com.ripple.encodings.addresses.Addresses; import com.ripple.utils.HashUtils; import com.tangem.App; +import com.tangem.data.network.ServerApiPayId; import com.tangem.data.network.ServerApiRipple; +import com.tangem.data.network.model.PayIdResponse; import com.tangem.data.network.model.RippleResponse; import com.tangem.tangem_card.data.TangemCard; import com.tangem.tangem_card.reader.CardProtocol; @@ -27,8 +29,14 @@ import com.tangem.wallet.TangemContext; import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URL; import java.util.Arrays; +import io.reactivex.SingleObserver; +import io.reactivex.observers.DisposableSingleObserver; +import io.xpring.xrpl.ClassicAddress; +import io.xpring.xrpl.Utils; + public class XrpEngine extends CoinEngine { private static final String TAG = XrpEngine.class.getSimpleName(); @@ -116,25 +124,31 @@ public class XrpEngine extends CoinEngine { if (address == null || address.isEmpty()) { return false; } + if (address.contains("$")) { // PayID + String[] addressParts = address.split("\\$"); - if (address.length() < 25) { - return false; - } - - if (address.length() > 35) { - return false; - } - - if (!address.startsWith("r")) { - return false; + 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; + } } try { Addresses.decodeAccountID(address); + return true; } catch (Exception e) { - return false; + // X-address + ClassicAddress classicAddress = Utils.decodeXAddress(address); + if (classicAddress == null) { + return false; + } + return !classicAddress.isTest(); } - - return true; } @Override @@ -342,7 +356,22 @@ public class XrpEngine extends CoinEngine { public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); - String amount, fee; + String amount, fee, destination; + Integer destinationTag = null; + + //PayID + if (coinData.getResolvedPayIdAddress() != null) { + destination = coinData.getResolvedPayIdAddress(); + } else { + destination = targetAddress; + } + + //X-address + ClassicAddress classicAddress = Utils.decodeXAddress(destination); + if (classicAddress != null) { + destination = classicAddress.address(); + destinationTag = classicAddress.tag().get(); + } if (IncFee) { amount = convertToInternalAmount(amountValue).subtract(convertToInternalAmount(feeValue)).setScale(0).toPlainString(); @@ -363,10 +392,13 @@ public class XrpEngine extends CoinEngine { // Put `as` AccountID field Account, `Object` o payment.as(AccountID.Account, coinData.getWallet()); - payment.as(AccountID.Destination, targetAddress); + payment.as(AccountID.Destination, destination); payment.as(com.ripple.core.coretypes.Amount.Amount, amount); payment.as(UInt32.Sequence, coinData.getSequence()); payment.as(com.ripple.core.coretypes.Amount.Fee, fee); + if (destinationTag != null) { + payment.as(UInt32.DestinationTag, destinationTag); + } XrpSignedTransaction signedTx = payment.prepare(canonisePubKey(ctx.getCard().getWalletPublicKeyRar())); @@ -525,6 +557,7 @@ public class XrpEngine extends CoinEngine { @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { final ServerApiRipple serverApiRipple = new ServerApiRipple(); + final ServerApiPayId serverApiPayId = new ServerApiPayId(); ServerApiRipple.ResponseListener rippleListener = new ServerApiRipple.ResponseListener() { @Override @@ -542,7 +575,7 @@ public class XrpEngine extends CoinEngine { coinData.setTargetAccountCreated(true); //expected behaviour, if account exists, there should be no error code -> null pointer } - if (serverApiRipple.isRequestsSequenceCompleted()) { + if (serverApiRipple.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); @@ -566,7 +599,7 @@ public class XrpEngine extends CoinEngine { ctx.setError(e.getMessage()); } - if (serverApiRipple.isRequestsSequenceCompleted()) { + if (serverApiRipple.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); @@ -585,9 +618,46 @@ public class XrpEngine extends CoinEngine { }; serverApiRipple.setResponseListener(rippleListener); - - serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); serverApiRipple.requestData(ServerApiRipple.RIPPLE_FEE, "", ""); + + if (targetAddress.contains("$")) { // PayID + + SingleObserver observer = new DisposableSingleObserver() { + @Override + public void onSuccess(PayIdResponse payIdResponse) { + try { + String resolvedAddress = payIdResponse.getAddresses().get(0).getAddressDetails().getAddress(); + + if (validateAddress(resolvedAddress)) { + coinData.setResolvedPayIdAddress(resolvedAddress); + //check if target account is created + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, resolvedAddress, ""); //TODO: maybe just assume PayID account is created? + } else { + ctx.setError("Unknown address format in PayID response"); + } + } catch (Exception e) { + ctx.setError("FAIL payID Exception"); + } + if (serverApiRipple.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(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + + serverApiPayId.getAddress(targetAddress, observer); + } else { + //check if target account is created + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); + } } @Override diff --git a/build.gradle b/build.gradle index 511fa6ac99..40bc31028b 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath "com.android.tools.build:gradle:$versions.build_gradle" + classpath "com.android.tools.build:gradle:${versions.build_gradle}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" classpath 'com.google.gms:google-services:4.3.3' diff --git a/dependencies.gradle b/dependencies.gradle index 3c7aaf12d8..f61f047e5a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ kotlin : '1.3.72', - build_gradle: '3.6.3', + build_gradle: '4.0.0', ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3cbbb9a496..01e71a44df 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Mon Mar 30 09:15:33 MSK 2020 +#Thu Jun 11 11:33:17 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/tangem-sdk-old/build.gradle b/tangem-sdk-old/build.gradle index e7ef5a0015..189bc3b334 100644 --- a/tangem-sdk-old/build.gradle +++ b/tangem-sdk-old/build.gradle @@ -24,7 +24,7 @@ android { versionNameSuffix "-beta" } } - buildToolsVersion '28.0.3' + buildToolsVersion '29.0.2' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index de05139eb5..0664ab0e25 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -10,7 +10,7 @@ version "$jitpackSdk.version" android { compileSdkVersion 29 - buildToolsVersion "29.0.2" + buildToolsVersion "29.0.3" defaultConfig { From 29e47d0782ac079fad978dd0771aca2bb4e77407 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jun 2020 18:13:38 +0300 Subject: [PATCH 2/5] Updated on 2026-08-14 --- app/build.gradle | 1 - .../java/com/tangem/wallet/xrp/XrpEngine.java | 32 +++++------ .../com/tangem/wallet/xrp/XrpTaggedAddress.kt | 53 +++++++++++++++++++ 3 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt diff --git a/app/build.gradle b/app/build.gradle index 027978da9f..efe15811f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,7 +137,6 @@ dependencies { //dependencies for XRP implementation files('libs/ripple-core-0.0.1.jar') - implementation 'io.xpring:xpring4j:2.2.0' //4 dependencies for ripple-core TODO: move to module? implementation 'net.i2p.crypto:eddsa:0.3.0' implementation 'org.bouncycastle:bcprov-jdk15on:1.61' diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index a667537b8f..f077d29bb9 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -34,8 +34,6 @@ import java.util.Arrays; import io.reactivex.SingleObserver; import io.reactivex.observers.DisposableSingleObserver; -import io.xpring.xrpl.ClassicAddress; -import io.xpring.xrpl.Utils; public class XrpEngine extends CoinEngine { @@ -142,12 +140,7 @@ public class XrpEngine extends CoinEngine { Addresses.decodeAccountID(address); return true; } catch (Exception e) { - // X-address - ClassicAddress classicAddress = Utils.decodeXAddress(address); - if (classicAddress == null) { - return false; - } - return !classicAddress.isTest(); + return XrpXAddressService.Companion.validate(address); } } @@ -367,10 +360,10 @@ public class XrpEngine extends CoinEngine { } //X-address - ClassicAddress classicAddress = Utils.decodeXAddress(destination); - if (classicAddress != null) { - destination = classicAddress.address(); - destinationTag = classicAddress.tag().get(); + XrpXAddressDecoded decodedXAddress = XrpXAddressService.Companion.decode(destination); + if (decodedXAddress != null) { + destination = decodedXAddress.getAddress(); + destinationTag = decodedXAddress.getDestinationTag(); } if (IncFee) { @@ -564,7 +557,7 @@ public class XrpEngine extends CoinEngine { public void onSuccess(String method, RippleResponse rippleResponse) { Log.i(TAG, "onSuccess: " + method); switch (method) { - case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { + case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { //check if target account is created try { if (rippleResponse.getResult().getError_code().equals(19)) { // "Account not found" coinData.setTargetAccountCreated(false); @@ -630,13 +623,12 @@ public class XrpEngine extends CoinEngine { if (validateAddress(resolvedAddress)) { coinData.setResolvedPayIdAddress(resolvedAddress); - //check if target account is created serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, resolvedAddress, ""); //TODO: maybe just assume PayID account is created? } else { ctx.setError("Unknown address format in PayID response"); } } catch (Exception e) { - ctx.setError("FAIL payID Exception"); + ctx.setError("Unknown response format on PayID request"); } if (serverApiRipple.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); @@ -648,15 +640,19 @@ public class XrpEngine extends CoinEngine { @Override public void onError(Throwable e) { Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage()); - ctx.setError(e.getMessage()); + ctx.setError("PayID error:" + e.getMessage()); blockchainRequestsCallbacks.onComplete(false); } }; serverApiPayId.getAddress(targetAddress, observer); } else { - //check if target account is created - serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); + XrpXAddressDecoded xAddressDecoded = XrpXAddressService.Companion.decode(targetAddress); + if (xAddressDecoded == null) { // classic address + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); + } else { // X-address + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, xAddressDecoded.getAddress(), ""); + } } } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt b/app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt new file mode 100644 index 0000000000..b0e63aa0e0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt @@ -0,0 +1,53 @@ +package com.tangem.wallet.xrp + +import com.ripple.encodings.addresses.Addresses +import com.ripple.encodings.base58.B58 +import org.kethereum.extensions.toBigInteger + +class XrpXAddressService { + companion object { + private val xrpBase58 = B58("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") + private val xrpMainnetPrefix = byteArrayOf(0x05, 0x44) + private val zeroTagBytes = ByteArray(4) { 0 } + + fun validate(address: String): Boolean { + return decode(address) != null + } + + fun decode(address: String): XrpXAddressDecoded? { + try { + val addressBytes = xrpBase58.decodeChecked(address) + if (addressBytes.size != 31) return null + + val prefix = addressBytes.slice(0..1).toByteArray() + if (!prefix.contentEquals(xrpMainnetPrefix)) return null + + val accountBytes = addressBytes.slice(2..21).toByteArray() + val classicAddress = Addresses.encodeAccountID(accountBytes) + + val flag = addressBytes[22] + + val tagBytes = addressBytes.slice(23..26).toByteArray() + val reservedTagBytes = addressBytes.slice(27..30).toByteArray() + if (!reservedTagBytes.contentEquals(zeroTagBytes)) return null + + var tag: Int? = null + when (flag) { + 0.toByte() -> if (!tagBytes.contentEquals(zeroTagBytes)) return null + 1.toByte() -> { + tag = tagBytes.reversedArray().toBigInteger().toInt() + } + else -> return null + } + return XrpXAddressDecoded(classicAddress, tag) + } catch (e: Exception) { + return null + } + } + } +} + +data class XrpXAddressDecoded( + val address: String, + val destinationTag: Int? +) \ No newline at end of file From d9de0a79bbae3bd41a9fd840b0001e67610b74f3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jun 2020 18:20:45 +0300 Subject: [PATCH 3/5] Updated on 2026-08-14 --- app/build.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index efe15811f0..b612618a1f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,7 +54,6 @@ android { } } compileOptions { - coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -82,8 +81,6 @@ dependencies { implementation project(':tangem-sdk-old') implementation project(':server-android') - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5' - implementation 'androidx.navigation:navigation-fragment:2.2.2' implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' implementation 'androidx.legacy:legacy-support-v4:1.0.0' From fc5319f255f51de2006a4304016431f51ee268dd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jun 2020 22:12:39 +0300 Subject: [PATCH 4/5] Updated on 2026-08-14 --- .../com/tangem/data/network/model/PayIdResponse.kt | 6 ++++++ .../main/java/com/tangem/wallet/xrp/XrpEngine.java | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt b/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt index 5d383b7b0c..583ae73462 100644 --- a/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/PayIdResponse.kt @@ -8,6 +8,12 @@ data class PayIdResponse( ) data class PayIdAddress( + @SerializedName("paymentNetwork") + var paymentNetwork: String? = null, + + @SerializedName("environment") + var environment: String? = null, + @SerializedName("addressDetails") var addressDetails: PayIdAddressDetails? = null ) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index f077d29bb9..d60032522a 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -12,6 +12,7 @@ import com.ripple.utils.HashUtils; import com.tangem.App; import com.tangem.data.network.ServerApiPayId; import com.tangem.data.network.ServerApiRipple; +import com.tangem.data.network.model.PayIdAddress; import com.tangem.data.network.model.PayIdResponse; import com.tangem.data.network.model.RippleResponse; import com.tangem.tangem_card.data.TangemCard; @@ -619,8 +620,14 @@ public class XrpEngine extends CoinEngine { @Override public void onSuccess(PayIdResponse payIdResponse) { try { - String resolvedAddress = payIdResponse.getAddresses().get(0).getAddressDetails().getAddress(); - + String resolvedAddress = null; + for (PayIdAddress address : payIdResponse.getAddresses()) { + if (address.getPaymentNetwork().equals("XRPL") && + address.getEnvironment().equals("MAINNET")) { + resolvedAddress = address.getAddressDetails().getAddress(); + break; + } + } if (validateAddress(resolvedAddress)) { coinData.setResolvedPayIdAddress(resolvedAddress); serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, resolvedAddress, ""); //TODO: maybe just assume PayID account is created? From 2c76991bbb0cade286ace4607c1d949a9deadbf9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 13 Jun 2020 13:59:52 +0300 Subject: [PATCH 5/5] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index d60032522a..f7d551992d 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -630,7 +630,13 @@ public class XrpEngine extends CoinEngine { } if (validateAddress(resolvedAddress)) { coinData.setResolvedPayIdAddress(resolvedAddress); - serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, resolvedAddress, ""); //TODO: maybe just assume PayID account is created? + + XrpXAddressDecoded xAddressDecoded = XrpXAddressService.Companion.decode(resolvedAddress); + if (xAddressDecoded == null) { // classic address + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, resolvedAddress, ""); //TODO: maybe just assume PayID account is created? + } else { // X-address + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, xAddressDecoded.getAddress(), ""); + } } else { ctx.setError("Unknown address format in PayID response"); }