diff --git a/.gitignore b/.gitignore
index 13b2127afa..4cc00a7e1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,10 +13,7 @@ local.properties
.gradle
# User-specific configurations
-.idea/caches/
-.idea/libraries/
-.idea/*.xml
-.idea/codeStyles
+.idea
# LayoutInspector files
/captures
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..9dd60618bd
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,184 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.4)
+ rexml
+ addressable (2.8.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ atomos (0.1.3)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.519.0)
+ aws-sdk-core (3.121.3)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.239.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1.0)
+ aws-sdk-kms (1.50.0)
+ aws-sdk-core (~> 3, >= 3.121.2)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.104.0)
+ aws-sdk-core (~> 3, >= 3.121.2)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.4.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ claide (1.0.3)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander-fastlane (4.4.6)
+ highline (~> 1.7.2)
+ declarative (0.0.20)
+ declarative-option (0.1.0)
+ digest-crc (0.6.4)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.7.6)
+ emoji_regex (1.0.1)
+ excon (0.87.0)
+ faraday (0.17.4)
+ multipart-post (>= 1.2, < 3)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday_middleware (0.13.1)
+ faraday (>= 0.7.4, < 1.0)
+ fastimage (2.2.5)
+ fastlane (2.146.1)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.3, < 3.0.0)
+ aws-sdk-s3 (~> 1.0)
+ babosa (>= 1.0.2, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander-fastlane (>= 4.4.6, < 5.0.0)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 2.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 0.17)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 0.13.1)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-api-client (>= 0.29.2, < 0.37.0)
+ google-cloud-storage (>= 1.15.0, < 2.0.0)
+ highline (>= 1.7.2, < 2.0.0)
+ json (< 3.0.0)
+ jwt (~> 2.1.0)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multi_xml (~> 0.5)
+ multipart-post (~> 2.0.0)
+ plist (>= 3.1.0, < 4.0.0)
+ public_suffix (~> 2.0.0)
+ rubyzip (>= 1.3.0, < 2.0.0)
+ security (= 0.1.3)
+ simctl (~> 1.6.3)
+ slack-notifier (>= 2.0.0, < 3.0.0)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ fastlane-plugin-firebase_app_distribution (0.3.1)
+ gh_inspector (1.1.3)
+ google-api-client (0.32.1)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.5, < 0.10.0)
+ httpclient (>= 2.8.1, < 3.0)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.0)
+ signet (~> 0.10)
+ google-cloud-core (1.3.2)
+ google-cloud-env (~> 1.0)
+ google-cloud-env (1.2.1)
+ faraday (~> 0.11)
+ google-cloud-storage (1.21.1)
+ addressable (~> 2.5)
+ digest-crc (~> 0.4)
+ google-api-client (~> 0.26)
+ google-cloud-core (~> 1.2)
+ googleauth (>= 0.6.2, < 0.10.0)
+ mini_mime (~> 1.0)
+ googleauth (0.9.0)
+ faraday (~> 0.12)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (~> 0.7)
+ highline (1.7.10)
+ http-cookie (1.0.4)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ jmespath (1.4.0)
+ json (2.6.1)
+ jwt (2.1.0)
+ memoist (0.16.2)
+ mini_magick (4.11.0)
+ mini_mime (1.1.2)
+ multi_json (1.15.0)
+ multi_xml (0.6.0)
+ multipart-post (2.0.0)
+ nanaimo (0.3.0)
+ naturally (2.2.1)
+ os (1.1.1)
+ plist (3.6.0)
+ public_suffix (2.0.5)
+ rake (13.0.6)
+ representable (3.0.4)
+ declarative (< 0.1.0)
+ declarative-option (< 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.2.5)
+ rouge (2.0.7)
+ rubyzip (1.3.0)
+ security (0.1.3)
+ signet (0.11.0)
+ addressable (~> 2.3)
+ faraday (~> 0.9)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.8)
+ CFPropertyList
+ naturally
+ slack-notifier (2.4.0)
+ terminal-notifier (2.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.1)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.8)
+ unicode-display_width (1.8.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.21.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.3.0)
+ rexml (~> 3.2.4)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ universal-darwin-18
+
+DEPENDENCIES
+ fastlane
+ fastlane-plugin-firebase_app_distribution
+
+BUNDLED WITH
+ 2.2.16
diff --git a/app/build.gradle b/app/build.gradle
index d94e9cbb30..686a683503 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,17 +2,17 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
-apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'
+apply plugin: 'com.google.firebase.crashlytics'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
defaultConfig {
applicationId "com.tangem.wallet"
versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1
versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT"
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 30
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
androidExtensions {
@@ -37,7 +37,8 @@ android {
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"dev\"'
}
debug_beta {
- initWith debug
+ initWith release
+ debuggable false
versionNameSuffix "-beta"
applicationIdSuffix ".debug"
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"'
@@ -57,7 +58,6 @@ android {
exclude 'lib/x86_64/freebsd/libscrypt.so'
exclude 'lib/x86_64/linux/libscrypt.so'
}
- buildToolsVersion '29.0.3'
}
repositories {
@@ -67,23 +67,24 @@ repositories {
dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs')
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin"
- implementation 'androidx.core:core-ktx:1.3.2'
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
- implementation 'com.google.android.material:material:1.3.0'
- implementation "androidx.core:core-ktx:1.3.2"
- implementation 'com.google.android.play:core:1.10.0'
+ implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'androidx.fragment:fragment-ktx:1.3.6'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
+ implementation 'com.google.android.material:material:1.4.0'
+ implementation 'com.google.android.play:core:1.10.2'
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
- implementation 'com.tangem:blockchain:develop-26'
- implementation 'com.tangem:core:fixes_for_app_release-44'
- implementation 'com.tangem:sdk:fixes_for_app_release-44'
+ implementation 'com.tangem:blockchain:develop-48'
+ implementation 'com.tangem.tangem-sdk-kotlin:core:develop-95'
+ implementation 'com.tangem.tangem-sdk-kotlin:android:develop-95'
// WebView
implementation "androidx.browser:browser:1.3.0"
+ implementation 'com.jakewharton.timber:timber:4.7.1'
+
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
@@ -114,13 +115,19 @@ dependencies {
// Network and Json
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-moshi:2.6.0'
- implementation 'com.squareup.moshi:moshi:1.9.2'
- implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
+ implementation 'com.squareup.moshi:moshi:1.12.0'
+ implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
+//
+// //Crypto
+ implementation "com.madgag.spongycastle:core:1.58.0.0"
+
+ // animation
+ implementation "com.airbnb.android:lottie:3.4.0"
testImplementation 'junit:junit:4.13.2'
- testImplementation "com.google.truth:truth:1.0.1"
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ testImplementation "com.google.truth:truth:1.1.3"
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 97e4157f97..f3c400a3ec 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,13 @@
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/binance_tokens.json b/app/src/main/assets/binance_tokens.json
new file mode 100644
index 0000000000..08ed8356fa
--- /dev/null
+++ b/app/src/main/assets/binance_tokens.json
@@ -0,0 +1,3086 @@
+[
+ {
+ "symbol" : "BUSD",
+ "decimalCount" : 8,
+ "contractAddress" : "BUSD-BD1",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BUSD-BD1/logo.png",
+ "name" : "Binance USD"
+ },
+ {
+ "symbol" : "BTCB",
+ "decimalCount" : 8,
+ "contractAddress" : "BTCB-1DE",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BTCB-1DE/logo.png",
+ "name" : "Bitcoin BEP2"
+ },
+ {
+ "symbol" : "ADA",
+ "decimalCount" : 8,
+ "contractAddress" : "ADA-9F4",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ADA-9F4/logo.png",
+ "name" : "CardanoBEP2"
+ },
+ {
+ "symbol" : "AERGO",
+ "decimalCount" : 8,
+ "contractAddress" : "AERGO-46B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/AERGO-46B/logo.png",
+ "name" : "Aergo"
+ },
+ {
+ "symbol" : "ANKR",
+ "decimalCount" : 8,
+ "contractAddress" : "ANKR-E97",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ANKR-E97/logo.png",
+ "name" : "ANKR"
+ },
+ {
+ "symbol" : "ARN",
+ "decimalCount" : 8,
+ "contractAddress" : "ARN-71B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ARN-71B/logo.png",
+ "name" : "Aeron"
+ },
+ {
+ "symbol" : "ARPA",
+ "decimalCount" : 8,
+ "contractAddress" : "ARPA-575",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ARPA-575/logo.png",
+ "name" : "ARPA"
+ },
+ {
+ "symbol" : "BZNT",
+ "decimalCount" : 8,
+ "contractAddress" : "BZNT-464",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BZNT-464/logo.png",
+ "name" : "Bezant Token"
+ },
+ {
+ "symbol" : "DAI",
+ "decimalCount" : 8,
+ "contractAddress" : "DAI-D75",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DAI-D75/logo.png",
+ "name" : "DaiBEP2"
+ },
+ {
+ "symbol" : "ETH",
+ "decimalCount" : 8,
+ "contractAddress" : "ETH-1C9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ETH-1C9/logo.png",
+ "name" : "ETH BEP2"
+ },
+ {
+ "symbol" : "ART",
+ "decimalCount" : 8,
+ "contractAddress" : "ART-3C9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ART-3C9/logo.png",
+ "name" : "Maecenas ART Token"
+ },
+ {
+ "symbol" : "ATOM",
+ "decimalCount" : 8,
+ "contractAddress" : "ATOM-596",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ATOM-596/logo.png",
+ "name" : "ATOMBEP2"
+ },
+ {
+ "symbol" : "ATP",
+ "decimalCount" : 8,
+ "contractAddress" : "ATP-38C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ATP-38C/logo.png",
+ "name" : "Atlas Protocol"
+ },
+ {
+ "symbol" : "CAKE",
+ "decimalCount" : 8,
+ "contractAddress" : "CAKE-435",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CAKE-435/logo.png",
+ "name" : "PancakeSwap Token"
+ },
+ {
+ "symbol" : "CAN",
+ "decimalCount" : 8,
+ "contractAddress" : "CAN-677",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CAN-677/logo.png",
+ "name" : "CanYaCoin"
+ },
+ {
+ "symbol" : "CAS",
+ "decimalCount" : 8,
+ "contractAddress" : "CAS-167",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CAS-167/logo.png",
+ "name" : "CASHAA"
+ },
+ {
+ "symbol" : "CBIX",
+ "decimalCount" : 8,
+ "contractAddress" : "CBIX-3C9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CBIX-3C9/logo.png",
+ "name" : "Cubiex"
+ },
+ {
+ "symbol" : "CBM",
+ "decimalCount" : 8,
+ "contractAddress" : "CBM-4B2",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CBM-4B2/logo.png",
+ "name" : "CryptoBonusMiles"
+ },
+ {
+ "symbol" : "CHZ",
+ "decimalCount" : 8,
+ "contractAddress" : "CHZ-ECD",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CHZ-ECD/logo.png",
+ "name" : "Chiliz"
+ },
+ {
+ "symbol" : "CNNS",
+ "decimalCount" : 8,
+ "contractAddress" : "CNNS-E16",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CNNS-E16/logo.png",
+ "name" : "Crypto Neo-value Neural System"
+ },
+ {
+ "symbol" : "COS",
+ "decimalCount" : 8,
+ "contractAddress" : "COS-2E4",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/COS-2E4/logo.png",
+ "name" : "Contentos"
+ },
+ {
+ "symbol" : "COTI",
+ "decimalCount" : 8,
+ "contractAddress" : "COTI-CBB",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/COTI-CBB/logo.png",
+ "name" : "COTI"
+ },
+ {
+ "symbol" : "COVA",
+ "decimalCount" : 8,
+ "contractAddress" : "COVA-218",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/COVA-218/logo.png",
+ "name" : "Covalent Token"
+ },
+ {
+ "symbol" : "CRPT",
+ "decimalCount" : 8,
+ "contractAddress" : "CRPT-8C9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CRPT-8C9/logo.png",
+ "name" : "Crypterium Token"
+ },
+ {
+ "symbol" : "IDRTB",
+ "decimalCount" : 8,
+ "contractAddress" : "IDRTB-178",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/IDRTB-178/logo.png",
+ "name" : "Rupiah Token"
+ },
+ {
+ "symbol" : "AVA",
+ "decimalCount" : 8,
+ "contractAddress" : "AVA-645",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/AVA-645/logo.png",
+ "name" : "Travala.com Token"
+ },
+ {
+ "symbol" : "AWC",
+ "decimalCount" : 8,
+ "contractAddress" : "AWC-986",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/AWC-986/logo.png",
+ "name" : "Atomic Wallet Token"
+ },
+ {
+ "symbol" : "AXPR",
+ "decimalCount" : 8,
+ "contractAddress" : "AXPR-777",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/AXPR-777/logo.png",
+ "name" : "AXPR.B"
+ },
+ {
+ "symbol" : "BAKE",
+ "decimalCount" : 8,
+ "contractAddress" : "BAKE-5E0",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BAKE-5E0/logo.png",
+ "name" : "BakeryToken"
+ },
+ {
+ "symbol" : "BAND",
+ "decimalCount" : 8,
+ "contractAddress" : "BAND-34B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BAND-34B/logo.png",
+ "name" : "BandProtocolBEP2"
+ },
+ {
+ "symbol" : "BCH",
+ "decimalCount" : 8,
+ "contractAddress" : "BCH-1FD",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BCH-1FD/logo.png",
+ "name" : "BCH BEP2"
+ },
+ {
+ "symbol" : "BCPT",
+ "decimalCount" : 8,
+ "contractAddress" : "BCPT-95A",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BCPT-95A/logo.png",
+ "name" : "Blockmason Credit Protocol"
+ },
+ {
+ "symbol" : "BEAR",
+ "decimalCount" : 8,
+ "contractAddress" : "BEAR-14C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BEAR-14C/logo.png",
+ "name" : "3X Short Bitcoin Token"
+ },
+ {
+ "symbol" : "BET",
+ "decimalCount" : 8,
+ "contractAddress" : "BET-844",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BET-844/logo.png",
+ "name" : "EOSBet Token"
+ },
+ {
+ "symbol" : "TAUDB",
+ "decimalCount" : 8,
+ "contractAddress" : "TAUDB-888",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TAUDB-888/logo.png",
+ "name" : "TrueAUD"
+ },
+ {
+ "symbol" : "THKDB",
+ "decimalCount" : 8,
+ "contractAddress" : "THKDB-888",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/THKDB-888/logo.png",
+ "name" : "TrueHKD"
+ },
+ {
+ "symbol" : "TUSDB",
+ "decimalCount" : 8,
+ "contractAddress" : "TUSDB-888",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TUSDB-888/logo.png",
+ "name" : "TrueUSD"
+ },
+ {
+ "symbol" : "USDSB",
+ "decimalCount" : 8,
+ "contractAddress" : "USDSB-1AC",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/USDSB-1AC/logo.png",
+ "name" : "USDS"
+ },
+ {
+ "symbol" : "USDT",
+ "decimalCount" : 8,
+ "contractAddress" : "USDT-6D8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/USDT-6D8/logo.png",
+ "name" : "USDTBEP2"
+ },
+ {
+ "symbol" : "BIFI",
+ "decimalCount" : 8,
+ "contractAddress" : "BIFI-290",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BIFI-290/logo.png",
+ "name" : "beefy.finance"
+ },
+ {
+ "symbol" : "BKBT",
+ "decimalCount" : 8,
+ "contractAddress" : "BKBT-3A6",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BKBT-3A6/logo.png",
+ "name" : "Bitwires Token"
+ },
+ {
+ "symbol" : "BLINK",
+ "decimalCount" : 8,
+ "contractAddress" : "BLINK-9C6",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BLINK-9C6/logo.png",
+ "name" : "Blockmason Link"
+ },
+ {
+ "symbol" : "BOLT",
+ "decimalCount" : 8,
+ "contractAddress" : "BOLT-4C6",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BOLT-4C6/logo.png",
+ "name" : "BOLT Token"
+ },
+ {
+ "symbol" : "BST2",
+ "decimalCount" : 8,
+ "contractAddress" : "BST2-2F2",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BST2-2F2/logo.png",
+ "name" : "BOOSTO"
+ },
+ {
+ "symbol" : "BTTB",
+ "decimalCount" : 8,
+ "contractAddress" : "BTTB-D31",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BTTB-D31/logo.png",
+ "name" : "BTTB"
+ },
+ {
+ "symbol" : "BULL",
+ "decimalCount" : 8,
+ "contractAddress" : "BULL-BE4",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BULL-BE4/logo.png",
+ "name" : "3x Long Bitcoin Token"
+ },
+ {
+ "symbol" : "BURGER",
+ "decimalCount" : 8,
+ "contractAddress" : "BURGER-33A",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/BURGER-33A/logo.png",
+ "name" : "Burger Swap"
+ },
+ {
+ "symbol" : "CSM",
+ "decimalCount" : 8,
+ "contractAddress" : "CSM-734",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/CSM-734/logo.png",
+ "name" : "“Consentium”"
+ },
+ {
+ "symbol" : "DEFI",
+ "decimalCount" : 8,
+ "contractAddress" : "DEFI-FA5",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DEFI-FA5/logo.png",
+ "name" : "DeFi Token"
+ },
+ {
+ "symbol" : "DOGE",
+ "decimalCount" : 8,
+ "contractAddress" : "DOGE-B67",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DOGE-B67/logo.png",
+ "name" : "Dogecoin"
+ },
+ {
+ "symbol" : "DOS",
+ "decimalCount" : 8,
+ "contractAddress" : "DOS-120",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DOS-120/logo.png",
+ "name" : "DOS Network Token"
+ },
+ {
+ "symbol" : "DOT",
+ "decimalCount" : 8,
+ "contractAddress" : "DOT-64C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DOT-64C/logo.png",
+ "name" : "PolkadotBEP2"
+ },
+ {
+ "symbol" : "DREP",
+ "decimalCount" : 8,
+ "contractAddress" : "DREP-7D2",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DREP-7D2/logo.png",
+ "name" : "DREP"
+ },
+ {
+ "symbol" : "DUSK",
+ "decimalCount" : 8,
+ "contractAddress" : "DUSK-45E",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/DUSK-45E/logo.png",
+ "name" : "Dusk Network"
+ },
+ {
+ "symbol" : "EBST",
+ "decimalCount" : 8,
+ "contractAddress" : "EBST-783",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/EBST-783/logo.png",
+ "name" : "eBoost"
+ },
+ {
+ "symbol" : "ENTRP",
+ "decimalCount" : 8,
+ "contractAddress" : "ENTRP-C8D",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ENTRP-C8D/logo.png",
+ "name" : "Hut34 Entropy"
+ },
+ {
+ "symbol" : "EOS",
+ "decimalCount" : 8,
+ "contractAddress" : "EOS-CDD",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/EOS-CDD/logo.png",
+ "name" : "EOS BEP2"
+ },
+ {
+ "symbol" : "EOSBEAR",
+ "decimalCount" : 8,
+ "contractAddress" : "EOSBEAR-721",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/EOSBEAR-721/logo.png",
+ "name" : "3X Short EOS Token"
+ },
+ {
+ "symbol" : "EOSBULL",
+ "decimalCount" : 8,
+ "contractAddress" : "EOSBULL-F0D",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/EOSBULL-F0D/logo.png",
+ "name" : "3X Long EOS Token"
+ },
+ {
+ "symbol" : "EQL",
+ "decimalCount" : 8,
+ "contractAddress" : "EQL-586",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/EQL-586/logo.png",
+ "name" : "EQUAL"
+ },
+ {
+ "symbol" : "ERD",
+ "decimalCount" : 8,
+ "contractAddress" : "ERD-D06",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ERD-D06/logo.png",
+ "name" : "Elrond"
+ },
+ {
+ "symbol" : "ETHBEAR",
+ "decimalCount" : 8,
+ "contractAddress" : "ETHBEAR-B2B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ETHBEAR-B2B/logo.png",
+ "name" : "3X Short Ethereum Token"
+ },
+ {
+ "symbol" : "ETHBULL",
+ "decimalCount" : 8,
+ "contractAddress" : "ETHBULL-D33",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ETHBULL-D33/logo.png",
+ "name" : "3X Long Ethereum Token"
+ },
+ {
+ "symbol" : "EVT",
+ "decimalCount" : 8,
+ "contractAddress" : "EVT-49B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/EVT-49B/logo.png",
+ "name" : "everiToken"
+ },
+ {
+ "symbol" : "FRM",
+ "decimalCount" : 8,
+ "contractAddress" : "FRM-DE7",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/FRM-DE7/logo.png",
+ "name" : "Ferrum Network Token"
+ },
+ {
+ "symbol" : "FSN",
+ "decimalCount" : 8,
+ "contractAddress" : "FSN-E14",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/FSN-E14/logo.png",
+ "name" : "Fusion"
+ },
+ {
+ "symbol" : "FTM",
+ "decimalCount" : 8,
+ "contractAddress" : "FTM-A64",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/FTM-A64/logo.png",
+ "name" : "Fantom"
+ },
+ {
+ "symbol" : "GIV",
+ "decimalCount" : 8,
+ "contractAddress" : "GIV-94E",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/GIV-94E/logo.png",
+ "name" : "Givly Coin"
+ },
+ {
+ "symbol" : "GMAT",
+ "decimalCount" : 8,
+ "contractAddress" : "GMAT-FC8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/GMAT-FC8/logo.png",
+ "name" : "GoWithMi"
+ },
+ {
+ "symbol" : "GTO",
+ "decimalCount" : 8,
+ "contractAddress" : "GTO-908",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/GTO-908/logo.png",
+ "name" : "Gifto"
+ },
+ {
+ "symbol" : "HNST",
+ "decimalCount" : 8,
+ "contractAddress" : "HNST-3C9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/HNST-3C9/logo.png",
+ "name" : "Honest"
+ },
+ {
+ "symbol" : "HYN",
+ "decimalCount" : 8,
+ "contractAddress" : "HYN-F21",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/HYN-F21/logo.png",
+ "name" : "Hyperion Token"
+ },
+ {
+ "symbol" : "INJ",
+ "decimalCount" : 8,
+ "contractAddress" : "INJ-FAE",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/INJ-FAE/logo.png",
+ "name" : "Injective Protocol"
+ },
+ {
+ "symbol" : "IRIS",
+ "decimalCount" : 8,
+ "contractAddress" : "IRIS-D88",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/IRIS-D88/logo.png",
+ "name" : "IRIS Network"
+ },
+ {
+ "symbol" : "KAT",
+ "decimalCount" : 8,
+ "contractAddress" : "KAT-7BB",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/KAT-7BB/logo.png",
+ "name" : "Kambria Token"
+ },
+ {
+ "symbol" : "LBA",
+ "decimalCount" : 8,
+ "contractAddress" : "LBA-340",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/LBA-340/logo.png",
+ "name" : "Lend-Borrow-Asset"
+ },
+ {
+ "symbol" : "LIT",
+ "decimalCount" : 8,
+ "contractAddress" : "LIT-099",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/LIT-099/logo.png",
+ "name" : "LITION"
+ },
+ {
+ "symbol" : "LOKI",
+ "decimalCount" : 8,
+ "contractAddress" : "LOKI-6A9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/LOKI-6A9/logo.png",
+ "name" : "Loki"
+ },
+ {
+ "symbol" : "LTC",
+ "decimalCount" : 8,
+ "contractAddress" : "LTC-F07",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/LTC-F07/logo.png",
+ "name" : "LTC BEP2"
+ },
+ {
+ "symbol" : "LTO",
+ "decimalCount" : 8,
+ "contractAddress" : "LTO-BDF",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/LTO-BDF/logo.png",
+ "name" : "LTO Network"
+ },
+ {
+ "symbol" : "MATIC",
+ "decimalCount" : 8,
+ "contractAddress" : "MATIC-84A",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MATIC-84A/logo.png",
+ "name" : "Matic Token"
+ },
+ {
+ "symbol" : "MDAB",
+ "decimalCount" : 8,
+ "contractAddress" : "MDAB-D42",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MDAB-D42/logo.png",
+ "name" : "MDAB"
+ },
+ {
+ "symbol" : "MEETONE",
+ "decimalCount" : 8,
+ "contractAddress" : "MEETONE-031",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MEETONE-031/logo.png",
+ "name" : "MEET.ONE"
+ },
+ {
+ "symbol" : "MITH",
+ "decimalCount" : 8,
+ "contractAddress" : "MITH-C76",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MITH-C76/logo.png",
+ "name" : "Mithril"
+ },
+ {
+ "symbol" : "MITX",
+ "decimalCount" : 8,
+ "contractAddress" : "MITX-CAA",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MITX-CAA/logo.png",
+ "name" : "Morpheus Infrastructure Token"
+ },
+ {
+ "symbol" : "MTV",
+ "decimalCount" : 8,
+ "contractAddress" : "MTV-4C6",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MTV-4C6/logo.png",
+ "name" : "MultiVAC"
+ },
+ {
+ "symbol" : "MVL",
+ "decimalCount" : 8,
+ "contractAddress" : "MVL-7B0",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MVL-7B0/logo.png",
+ "name" : "Mass Vehicle Ledger"
+ },
+ {
+ "symbol" : "MZK",
+ "decimalCount" : 8,
+ "contractAddress" : "MZK-2C7",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MZK-2C7/logo.png",
+ "name" : "Muzika"
+ },
+ {
+ "symbol" : "NEW",
+ "decimalCount" : 8,
+ "contractAddress" : "NEW-09E",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/NEW-09E/logo.png",
+ "name" : "NEWTON"
+ },
+ {
+ "symbol" : "NEXO",
+ "decimalCount" : 8,
+ "contractAddress" : "NEXO-A84",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/NEXO-A84/logo.png",
+ "name" : "Nexo"
+ },
+ {
+ "symbol" : "NOIZB",
+ "decimalCount" : 8,
+ "contractAddress" : "NOIZB-878",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/NOIZB-878/logo.png",
+ "name" : "NOIZ Token"
+ },
+ {
+ "symbol" : "NOW",
+ "decimalCount" : 8,
+ "contractAddress" : "NOW-E68",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/NOW-E68/logo.png",
+ "name" : "NOW Token"
+ },
+ {
+ "symbol" : "NPXB",
+ "decimalCount" : 8,
+ "contractAddress" : "NPXB-1E8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/NPXB-1E8/logo.png",
+ "name" : "NPX Binance token"
+ },
+ {
+ "symbol" : "NPXSXEM",
+ "decimalCount" : 8,
+ "contractAddress" : "NPXSXEM-89C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/NPXSXEM-89C/logo.png",
+ "name" : "Pundi X NEM"
+ },
+ {
+ "symbol" : "ONE",
+ "decimalCount" : 8,
+ "contractAddress" : "ONE-5F9",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ONE-5F9/logo.png",
+ "name" : "Harmony.One"
+ },
+ {
+ "symbol" : "ONT",
+ "decimalCount" : 8,
+ "contractAddress" : "ONT-33D",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ONT-33D/logo.png",
+ "name" : "ONTBEP2"
+ },
+ {
+ "symbol" : "PHB",
+ "decimalCount" : 8,
+ "contractAddress" : "PHB-2DF",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/PHB-2DF/logo.png",
+ "name" : "Red Pulse Phoenix Binance"
+ },
+ {
+ "symbol" : "PHV",
+ "decimalCount" : 8,
+ "contractAddress" : "PHV-4A1",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/PHV-4A1/logo.png",
+ "name" : "PathHive Network"
+ },
+ {
+ "symbol" : "PIBNB",
+ "decimalCount" : 8,
+ "contractAddress" : "PIBNB-43C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/PIBNB-43C/logo.png",
+ "name" : "PCHAIN Token"
+ },
+ {
+ "symbol" : "PLG",
+ "decimalCount" : 8,
+ "contractAddress" : "PLG-D8D",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/PLG-D8D/logo.png",
+ "name" : "Pledge Coin"
+ },
+ {
+ "symbol" : "PVT",
+ "decimalCount" : 8,
+ "contractAddress" : "PVT-554",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/PVT-554/logo.png",
+ "name" : "Pivot Token"
+ },
+ {
+ "symbol" : "PYN",
+ "decimalCount" : 8,
+ "contractAddress" : "PYN-C37",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/PYN-C37/logo.png",
+ "name" : "paycentos"
+ },
+ {
+ "symbol" : "QBX",
+ "decimalCount" : 8,
+ "contractAddress" : "QBX-38C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/QBX-38C/logo.png",
+ "name" : "qiibeeToken"
+ },
+ {
+ "symbol" : "RAVEN",
+ "decimalCount" : 8,
+ "contractAddress" : "RAVEN-F66",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/RAVEN-F66/logo.png",
+ "name" : "Raven Protocol"
+ },
+ {
+ "symbol" : "RUNE",
+ "decimalCount" : 8,
+ "contractAddress" : "RUNE-B1A",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/RUNE-B1A/logo.png",
+ "name" : "Rune"
+ },
+ {
+ "symbol" : "SHR",
+ "decimalCount" : 8,
+ "contractAddress" : "SHR-DB6",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/SHR-DB6/logo.png",
+ "name" : "ShareToken"
+ },
+ {
+ "symbol" : "SLV",
+ "decimalCount" : 8,
+ "contractAddress" : "SLV-986",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/SLV-986/logo.png",
+ "name" : "Silverway"
+ },
+ {
+ "symbol" : "SPNDB",
+ "decimalCount" : 8,
+ "contractAddress" : "SPNDB-916",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/SPNDB-916/logo.png",
+ "name" : "Spendcoin"
+ },
+ {
+ "symbol" : "STYL",
+ "decimalCount" : 8,
+ "contractAddress" : "STYL-65B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/STYL-65B/logo.png",
+ "name" : "Yin Lang Music IP Token"
+ },
+ {
+ "symbol" : "SWINGBY",
+ "decimalCount" : 8,
+ "contractAddress" : "SWINGBY-888",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/SWINGBY-888/logo.png",
+ "name" : "Swingby Token"
+ },
+ {
+ "symbol" : "SWIPE.B",
+ "decimalCount" : 8,
+ "contractAddress" : "SWIPE.B-DC0",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/SWIPE.B-DC0/logo.png",
+ "name" : "SWIPE Token"
+ },
+ {
+ "symbol" : "SXP",
+ "decimalCount" : 8,
+ "contractAddress" : "SXP-CCC",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/SXP-CCC/logo.png",
+ "name" : "Swipe"
+ },
+ {
+ "symbol" : "TM2",
+ "decimalCount" : 8,
+ "contractAddress" : "TM2-0C4",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TM2-0C4/logo.png",
+ "name" : "Traxia 2"
+ },
+ {
+ "symbol" : "TOMOB",
+ "decimalCount" : 8,
+ "contractAddress" : "TOMOB-4BC",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TOMOB-4BC/logo.png",
+ "name" : "TomoChain"
+ },
+ {
+ "symbol" : "TOP",
+ "decimalCount" : 8,
+ "contractAddress" : "TOP-491",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TOP-491/logo.png",
+ "name" : "TOP Network"
+ },
+ {
+ "symbol" : "TROY",
+ "decimalCount" : 8,
+ "contractAddress" : "TROY-9B8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TROY-9B8/logo.png",
+ "name" : "TROY"
+ },
+ {
+ "symbol" : "TRUE",
+ "decimalCount" : 8,
+ "contractAddress" : "TRUE-D84",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TRUE-D84/logo.png",
+ "name" : "TrueChain"
+ },
+ {
+ "symbol" : "TRXB",
+ "decimalCount" : 8,
+ "contractAddress" : "TRXB-2E6",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TRXB-2E6/logo.png",
+ "name" : "TRXB"
+ },
+ {
+ "symbol" : "TWT",
+ "decimalCount" : 8,
+ "contractAddress" : "TWT-8C2",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/TWT-8C2/logo.png",
+ "name" : "Trust Wallet"
+ },
+ {
+ "symbol" : "UGAS",
+ "decimalCount" : 8,
+ "contractAddress" : "UGAS-B0C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/UGAS-B0C/logo.png",
+ "name" : "Ultrain Coin"
+ },
+ {
+ "symbol" : "UND",
+ "decimalCount" : 8,
+ "contractAddress" : "UND-EBC",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/UND-EBC/logo.png",
+ "name" : "United Network Distribution"
+ },
+ {
+ "symbol" : "UPX",
+ "decimalCount" : 8,
+ "contractAddress" : "UPX-F3E",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/UPX-F3E/logo.png",
+ "name" : "UPX"
+ },
+ {
+ "symbol" : "VDX",
+ "decimalCount" : 8,
+ "contractAddress" : "VDX-A17",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/VDX-A17/logo.png",
+ "name" : "Vodi X"
+ },
+ {
+ "symbol" : "VIDT",
+ "decimalCount" : 8,
+ "contractAddress" : "VIDT-F53",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/VIDT-F53/logo.png",
+ "name" : "V-ID Token"
+ },
+ {
+ "symbol" : "VOTE",
+ "decimalCount" : 8,
+ "contractAddress" : "VOTE-FD4",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/VOTE-FD4/logo.png",
+ "name" : "Vote"
+ },
+ {
+ "symbol" : "WICC",
+ "decimalCount" : 8,
+ "contractAddress" : "WICC-01D",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/WICC-01D/logo.png",
+ "name" : "WaykiChain Coin"
+ },
+ {
+ "symbol" : "WINB",
+ "decimalCount" : 8,
+ "contractAddress" : "WINB-41F",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/WINB-41F/logo.png",
+ "name" : "WINB"
+ },
+ {
+ "symbol" : "WISH",
+ "decimalCount" : 8,
+ "contractAddress" : "WISH-2D5",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/WISH-2D5/logo.png",
+ "name" : "MyWish"
+ },
+ {
+ "symbol" : "WRX",
+ "decimalCount" : 8,
+ "contractAddress" : "WRX-ED1",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/WRX-ED1/logo.png",
+ "name" : "WazirX Token"
+ },
+ {
+ "symbol" : "XBASE",
+ "decimalCount" : 8,
+ "contractAddress" : "XBASE-CD2",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/XBASE-CD2/logo.png",
+ "name" : "Eterbase Coin"
+ },
+ {
+ "symbol" : "XRP",
+ "decimalCount" : 8,
+ "contractAddress" : "XRP-BF2",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/XRP-BF2/logo.png",
+ "name" : "XRP BEP2"
+ },
+ {
+ "symbol" : "XRPBEAR",
+ "decimalCount" : 8,
+ "contractAddress" : "XRPBEAR-00B",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/XRPBEAR-00B/logo.png",
+ "name" : "3X Short XRP Token"
+ },
+ {
+ "symbol" : "XRPBULL",
+ "decimalCount" : 8,
+ "contractAddress" : "XRPBULL-E7C",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/XRPBULL-E7C/logo.png",
+ "name" : "3X Long XRP Token"
+ },
+ {
+ "symbol" : "XTZ",
+ "decimalCount" : 8,
+ "contractAddress" : "XTZ-F7A",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/XTZ-F7A/logo.png",
+ "name" : "XTZ BEP2"
+ },
+ {
+ "symbol" : "XVS",
+ "decimalCount" : 8,
+ "contractAddress" : "XVS-795",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/XVS-795/logo.png",
+ "name" : "Venus"
+ },
+ {
+ "symbol" : "YFII",
+ "decimalCount" : 8,
+ "contractAddress" : "YFII-061",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/YFII-061/logo.png",
+ "name" : "YFIIBEP2"
+ },
+ {
+ "symbol" : "ZEBI",
+ "decimalCount" : 8,
+ "contractAddress" : "ZEBI-84F",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ZEBI-84F/logo.png",
+ "name" : "ZEBI"
+ },
+ {
+ "symbol" : "ZEC",
+ "decimalCount" : 8,
+ "contractAddress" : "ZEC-93E",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/ZEC-93E/logo.png",
+ "name" : "ZECBEP2"
+ },
+ {
+ "contractAddress": "NMX-7AFM",
+ "symbol": "NMX",
+ "name": "NOMINEX",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GTMRS-D96M",
+ "symbol": "GTMRS",
+ "name": "Getmoders",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "DRC-FF4M",
+ "symbol": "DRC",
+ "name": "DARIK COIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "IMC-D5AM",
+ "symbol": "IMC",
+ "name": "IMC",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "JPIN-89AM",
+ "symbol": "JPIN",
+ "name": "JADEPIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TBCC",
+ "symbol": "TBCC",
+ "name": "TBC",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SZAR-952M",
+ "symbol": "SZAR",
+ "name": "SMART ZAR",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SOUL-D11M",
+ "symbol": "SOUL",
+ "name": "CryptoSoul",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "VTBC-C26M",
+ "symbol": "VTBC",
+ "name": "TBCCVPN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "LGVM-0F6M",
+ "symbol": "LGVM",
+ "name": "Longevium",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "JUV-FC7M",
+ "symbol": "JUV",
+ "name": "Juventus",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "KNB-9D6M",
+ "symbol": "KNB",
+ "name": "Kronobit",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "POLO-E0DM",
+ "symbol": "POLO",
+ "name": "MarcoPoloCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "FIO-0CDM",
+ "symbol": "FIO",
+ "name": "FIO",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BIR",
+ "symbol": "BIR",
+ "name": "BinanceIran",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SBC-AF0M",
+ "symbol": "SBC",
+ "name": "SBCOIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "DPSC-CCBM",
+ "symbol": "DPSC",
+ "name": "DPASSCOIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "EPIC-066M",
+ "symbol": "EPIC",
+ "name": "EPIC",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "UBETS-619M",
+ "symbol": "UBETS",
+ "name": "UBETS Coin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "JDXU-932M",
+ "symbol": "JDXU",
+ "name": "JDXUCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GMNG-084M",
+ "symbol": "GMNG",
+ "name": "GlobalGaming",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BTC-992M",
+ "symbol": "BTC",
+ "name": "Bitcoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ETH-EDDM",
+ "symbol": "ETH",
+ "name": "Ethereum",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ANK-5D8M",
+ "symbol": "ANK",
+ "name": "AppleNetwork",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "FLGHT-96EM",
+ "symbol": "FLGHT",
+ "name": "TheFlight",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "YTC-6BFM",
+ "symbol": "YTC",
+ "name": "YouTubeCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "MSV-114M",
+ "symbol": "MSV",
+ "name": "MoskvinInvest",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "AZBIT-A07M",
+ "symbol": "AZBIT",
+ "name": "AZbitCryptoExchange",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BFT-321M",
+ "symbol": "BFT",
+ "name": "BitForexToken",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "LIBRA-D54M",
+ "symbol": "LIBRA",
+ "name": "FacebookCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ZANO-F71M",
+ "symbol": "ZANO",
+ "name": "Zano",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BTS-FA7M",
+ "symbol": "BTS",
+ "name": "BTSentertainment",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "VNB-684M",
+ "symbol": "VNB",
+ "name": "VNNetwork",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "MKR-119M",
+ "symbol": "MKR",
+ "name": "Maker",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "XAV-3C5M",
+ "symbol": "XAV",
+ "name": "XAVIERCOIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TSL-751M",
+ "symbol": "TSL",
+ "name": "TESLA",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GRAM-6BBM",
+ "symbol": "GRAM",
+ "name": "TelegramOpenNetwork",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ZENK-8BCM",
+ "symbol": "ZENK",
+ "name": "ZenKoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "STK70-882M",
+ "symbol": "STK70",
+ "name": "STK70",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "UBER-5D0M",
+ "symbol": "UBER",
+ "name": "UBER",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CR7-A67M",
+ "symbol": "CR7",
+ "name": "CristianoRonaldoFanToken",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ITTT-C65M",
+ "symbol": "ITTT",
+ "name": "IntuitiveToken",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "FNXS-983M",
+ "symbol": "FNXS",
+ "name": "FinanceX",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ELT-61FM",
+ "symbol": "ELT",
+ "name": "ELITE",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "KODK-9ABM",
+ "symbol": "KODK",
+ "name": "EastmanKodakCompany",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "COMP-3C5M",
+ "symbol": "COMP",
+ "name": "Compound",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CCT-AD7M",
+ "symbol": "CCT",
+ "name": "CryptoCompareToken",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BNS-5ADM",
+ "symbol": "BNS",
+ "name": "BNSAFEER",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "10XDF-9FBM",
+ "symbol": "10XDF",
+ "name": "10xDeFiXy",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "RUBY-FFCM",
+ "symbol": "RUBY",
+ "name": "VNOvernight",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BHC-3E8M",
+ "symbol": "BHC",
+ "name": "BillionHappiness",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SAND-25CM",
+ "symbol": "SAND",
+ "name": "Sandbox",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "VNBIT-5D5M",
+ "symbol": "VNBIT",
+ "name": "VNBit",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CVD-940M",
+ "symbol": "CVD",
+ "name": "Covid19",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "100X-0A2M",
+ "symbol": "100X",
+ "name": "100x",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "PLV-328M",
+ "symbol": "PLV",
+ "name": "PLAVNI",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "VOTE-692M",
+ "symbol": "VOTE",
+ "name": "Vote",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "NBTC-09BM",
+ "symbol": "NBTC",
+ "name": "NBTC",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "WEBIT-4D0M",
+ "symbol": "WEBIT",
+ "name": "WEBIT",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BUSY-3AFM",
+ "symbol": "BUSY",
+ "name": "BusyProtocol",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "DEFIX-46FM",
+ "symbol": "DEFIX",
+ "name": "DefiX",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "200DX-985M",
+ "symbol": "200DX",
+ "name": "200DX",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GOGH-510M",
+ "symbol": "GOGH",
+ "name": "Vincent",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "HOOTZ-CE2M",
+ "symbol": "HOOTZ",
+ "name": "Hootz",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "YFFI-7E4M",
+ "symbol": "YFFI",
+ "name": "YFFIfinance",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "YFII-7FBM",
+ "symbol": "YFII",
+ "name": "yearnfinanceII",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TNH-2D6M",
+ "symbol": "TNH",
+ "name": "Tanhu",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GOLD-42AM",
+ "symbol": "GOLD",
+ "name": "GOLD",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CMW-CF8M",
+ "symbol": "CMW",
+ "name": "CMWLTD14",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "AMZN-2A5M",
+ "symbol": "AMZN",
+ "name": "Amazon",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "YFL-4CFM",
+ "symbol": "YFL",
+ "name": "YFLink",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CUCAP-090M",
+ "symbol": "CUCAP",
+ "name": "CopperCommons",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "YETH-687M",
+ "symbol": "YETH",
+ "name": "YETH",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "XLS-A82M",
+ "symbol": "XLS",
+ "name": "Xwolfers",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "POKER-C92M",
+ "symbol": "POKER",
+ "name": "POKERSTARS",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "XCT",
+ "symbol": "XCT",
+ "name": "Citron",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SIP",
+ "symbol": "SIP",
+ "name": "Arbitrx",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "KDC-4E4M",
+ "symbol": "KDC",
+ "name": "KingdomCasino",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "INNBC",
+ "symbol": "INNBC",
+ "name": "InnovativeBioresearchCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ODAEE-AD8M",
+ "symbol": "ODAEE",
+ "name": "ODAEE",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CALTO-C51M",
+ "symbol": "CALTO",
+ "name": "caltechcasino",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ZONIC-C26M",
+ "symbol": "ZONIC",
+ "name": "ZONIC",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TAINO-CC7M",
+ "symbol": "TAINO",
+ "name": "TAINOCOIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BZAR-188M",
+ "symbol": "BZAR",
+ "name": "Bitcoinrand",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "JSB-614M",
+ "symbol": "JSB",
+ "name": "JSB",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BSATT-9F8M",
+ "symbol": "BSATT",
+ "name": "BinanceSATT",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GOAL-A7EM",
+ "symbol": "GOAL",
+ "name": "GoalCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ALF-346M",
+ "symbol": "ALF",
+ "name": "AlfaBlock",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GLXY-783M",
+ "symbol": "GLXY",
+ "name": "GalaxianCoin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "FXP-BE7M",
+ "symbol": "FXP",
+ "name": "FXPay",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "ATM-95EM",
+ "symbol": "ATM",
+ "name": "Atletico Madrid",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "VRTN2-F89M",
+ "symbol": "VRTN2",
+ "name": "VINYL RECORDS TOKEN 2",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "STN-DB6M",
+ "symbol": "STN",
+ "name": "Saturno",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TON-12EM",
+ "symbol": "TON",
+ "name": "Telegram Open Network",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "FLATS-9A4M",
+ "symbol": "FLATS",
+ "name": "FLAT EARTH",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "LTT-D42M",
+ "symbol": "LTT",
+ "name": "LiteTrader",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "FUTSWAP-8D1M",
+ "symbol": "FUTSWAP",
+ "name": "Futswap",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "IBAI-6FDM",
+ "symbol": "IBAI",
+ "name": "IBAI FAN TOKEN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BNGO-9D3M",
+ "symbol": "BNGO",
+ "name": "BINGO",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "STH-07AM",
+ "symbol": "STH",
+ "name": "SmartHoldem",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "WSB-63CM",
+ "symbol": "WSB",
+ "name": "WallStreetBets",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "XAQ-ABCM",
+ "symbol": "XAQ",
+ "name": "AQAQ",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "XCL-DAFM",
+ "symbol": "XCL",
+ "name": "CLICO",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "AGU-14FM",
+ "symbol": "AGU",
+ "name": "Aguacate Mini Token",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "GRPX-D25M",
+ "symbol": "GRPX",
+ "name": "Grapbox",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SHIT-580M",
+ "symbol": "SHIT",
+ "name": "SHIT DIAMOND",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SWAN-288M",
+ "symbol": "SWAN",
+ "name": "Swan Finance",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TRS-182M",
+ "symbol": "TRS",
+ "name": "Traders",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SZR-4F6M",
+ "symbol": "SZR",
+ "name": "Smart ZAR",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "TOD-442M",
+ "symbol": "TOD",
+ "name": "TOD COIN",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "WWWC-C3EM",
+ "symbol": "WWWC",
+ "name": "World Wide Wallet Coin",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CFN-D88M",
+ "symbol": "CFN",
+ "name": "Cafeina Token",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "BZB-034M",
+ "symbol": "BZB",
+ "name": "One million BZE",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "CCSCEX-E2CM",
+ "symbol": "CCSCEX",
+ "name": "EXCHANGE",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "XOTL-247M",
+ "symbol": "XOTL",
+ "name": "Xolotl Token",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "UWIM-06AM",
+ "symbol": "UWIM",
+ "name": "UWIM",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "LEARN",
+ "symbol": "LEARN",
+ "name": "BinanceAcademyToken",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "IKA-105M",
+ "symbol": "IKA",
+ "name": "Mining",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "contractAddress": "SCAM-914M",
+ "symbol": "SCAM",
+ "name": "SCAM",
+ "decimalCount": 8,
+ "type": "bep8",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ }
+]
diff --git a/app/src/main/assets/binance_tokens_testnet.json b/app/src/main/assets/binance_tokens_testnet.json
new file mode 100644
index 0000000000..7d5590d293
--- /dev/null
+++ b/app/src/main/assets/binance_tokens_testnet.json
@@ -0,0 +1,14 @@
+[
+ {
+ "symbol" : "HEM",
+ "decimalCount" : 8,
+ "contractAddress" : "HEM-452",
+ "blockchain" : {
+ "key" : "binance",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MATIC-84A/logo.png",
+ "name" : "Hemster - 452"
+ }
+]
diff --git a/app/src/main/assets/bsc_tokens.json b/app/src/main/assets/bsc_tokens.json
new file mode 100644
index 0000000000..712d35c6dd
--- /dev/null
+++ b/app/src/main/assets/bsc_tokens.json
@@ -0,0 +1,782 @@
+[
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x6c5FE6e99D2484db7E4BF34F365ABA42d0E4dC20/logo.png",
+ "symbol" : "ABS",
+ "name" : "Absorber",
+ "contractAddress" : "0x6c5FE6e99D2484db7E4BF34F365ABA42d0E4dC20",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x4197C6EF3879a08cD51e5560da5064B773aa1d29/logo.png",
+ "symbol" : "ACS",
+ "name" : "ACryptoS",
+ "contractAddress" : "0x4197C6EF3879a08cD51e5560da5064B773aa1d29",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xa1faa113cbE53436Df28FF0aEe54275c13B40975/logo.png",
+ "symbol" : "ALPHA",
+ "name" : "Alpha Token",
+ "contractAddress" : "0xa1faa113cbE53436Df28FF0aEe54275c13B40975",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xa184088a740c695E156F91f5cC086a06bb78b827/logo.png",
+ "symbol" : "AUTO",
+ "name" : "AUTOv2",
+ "contractAddress" : "0xa184088a740c695E156F91f5cC086a06bb78b827",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56/logo.png",
+ "symbol" : "BUSD",
+ "name" : "BUSD",
+ "contractAddress" : "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xAD6cAEb32CD2c308980a548bD0Bc5AA4306c6c18/logo.png",
+ "symbol" : "BAND",
+ "name" : "Binance-Peg Band Protocol",
+ "contractAddress" : "0xAD6cAEb32CD2c308980a548bD0Bc5AA4306c6c18",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf/logo.png",
+ "symbol" : "BCH",
+ "name" : "Binance-Peg Bitcoin Cash",
+ "contractAddress" : "0x8fF795a6F4D97E7887C79beA79aba5cc76444aDf",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x72fAa679E1008Ad8382959FF48E392042A8b06f7/logo.png",
+ "symbol" : "bALBT",
+ "name" : "AllianceBlock Token",
+ "contractAddress" : "0x72fAa679E1008Ad8382959FF48E392042A8b06f7",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x63870A18B6e42b01Ef1Ad8A2302ef50B7132054F/logo.png",
+ "symbol" : "BLINK",
+ "name" : "BLink Token",
+ "contractAddress" : "0x63870A18B6e42b01Ef1Ad8A2302ef50B7132054F",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x5Ac52EE5b2a633895292Ff6d8A89bB9190451587/logo.png",
+ "symbol" : "BSCX",
+ "name" : "BSCX",
+ "contractAddress" : "0x5Ac52EE5b2a633895292Ff6d8A89bB9190451587",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c/logo.png",
+ "symbol" : "BTCB",
+ "name" : "Binance-Peg BTCB Token",
+ "contractAddress" : "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47/logo.png",
+ "symbol" : "ADA",
+ "name" : "Binance-Peg Cardano",
+ "contractAddress" : "0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xF8A0BF9cF54Bb92F17374d9e9A321E6a111a51bD/logo.png",
+ "symbol" : "LINK",
+ "name" : "Binance-Peg ChainLink",
+ "contractAddress" : "0xF8A0BF9cF54Bb92F17374d9e9A321E6a111a51bD",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x0Eb3a705fc54725037CC9e008bDede697f62F335/logo.png",
+ "symbol" : "ATOM",
+ "name" : "Binance-Peg Cosmos",
+ "contractAddress" : "0x0Eb3a705fc54725037CC9e008bDede697f62F335",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xC9849E6fdB743d08fAeE3E34dd2D1bc69EA11a51/logo.png",
+ "symbol" : "BUNNY",
+ "name" : "Bunny Token",
+ "contractAddress" : "0xC9849E6fdB743d08fAeE3E34dd2D1bc69EA11a51",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xBf5140A22578168FD562DCcF235E5D43A02ce9B1/logo.png",
+ "symbol" : "UNI",
+ "name" : "Binance-Peg Uniswap",
+ "contractAddress" : "0xBf5140A22578168FD562DCcF235E5D43A02ce9B1",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x88f1A5ae2A3BF98AEAF342D26B30a79438c9142e/logo.png",
+ "symbol" : "YFI",
+ "name" : "Binance-Peg yearn.finance",
+ "contractAddress" : "0x88f1A5ae2A3BF98AEAF342D26B30a79438c9142e",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x81859801b01764D4f0Fa5E64729f5a6C3b91435b/logo.png",
+ "symbol" : "BFI",
+ "name" : "bearn.fi",
+ "contractAddress" : "0x81859801b01764D4f0Fa5E64729f5a6C3b91435b",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xCa3F508B8e4Dd382eE878A314789373D80A5190A/logo.png",
+ "symbol" : "BIFI",
+ "name" : "beefy.finance",
+ "contractAddress" : "0xCa3F508B8e4Dd382eE878A314789373D80A5190A",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7F70642d88cf1C4a3a7abb072B53B929b653edA5/logo.png",
+ "symbol" : "YFII",
+ "name" : "Binance-Peg YFII.finance",
+ "contractAddress" : "0x7F70642d88cf1C4a3a7abb072B53B929b653edA5",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x790Be81C3cA0e53974bE2688cDb954732C9862e1/logo.png",
+ "symbol" : "BREW",
+ "name" : "CafeSwap Token",
+ "contractAddress" : "0x790Be81C3cA0e53974bE2688cDb954732C9862e1",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xcc2E12a9b5b75360c6FBf23B584c275D52cDdb0E/logo.png",
+ "symbol" : "CROW",
+ "name" : "Crow Token",
+ "contractAddress" : "0xcc2E12a9b5b75360c6FBf23B584c275D52cDdb0E",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xA8c2B8eec3d368C0253ad3dae65a5F2BBB89c929/logo.png",
+ "symbol" : "CTK",
+ "name" : "CertiK Token",
+ "contractAddress" : "0xA8c2B8eec3d368C0253ad3dae65a5F2BBB89c929",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x97a30C692eCe9C317235d48287d23d358170FC40/logo.png",
+ "symbol" : "CRX",
+ "name" : "CryptEx Token",
+ "contractAddress" : "0x97a30C692eCe9C317235d48287d23d358170FC40",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xF952Fc3ca7325Cc27D15885d37117676d25BfdA6/logo.png",
+ "symbol" : "EGG",
+ "name" : "Goose Golden Egg",
+ "contractAddress" : "0xF952Fc3ca7325Cc27D15885d37117676d25BfdA6",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xbF7c81FFF98BbE61B40Ed186e4AfD6DDd01337fe/logo.png",
+ "symbol" : "EGLD",
+ "name" : "Elrond",
+ "contractAddress" : "0xbF7c81FFF98BbE61B40Ed186e4AfD6DDd01337fe",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x928e55daB735aa8260AF3cEDadA18B5f70C72f1b/logo.png",
+ "symbol" : "FRONT",
+ "name" : "Frontier Token",
+ "contractAddress" : "0x928e55daB735aa8260AF3cEDadA18B5f70C72f1b",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xf79037F6f6bE66832DE4E7516be52826BC3cBcc4/logo.png",
+ "symbol" : "HARD",
+ "name" : "Hard Protocol",
+ "contractAddress" : "0xf79037F6f6bE66832DE4E7516be52826BC3cBcc4",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xC7d8D35EBA58a0935ff2D5a33Df105DD9f071731/logo.png",
+ "symbol" : "HGET",
+ "name" : "Hedget",
+ "contractAddress" : "0xC7d8D35EBA58a0935ff2D5a33Df105DD9f071731",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x948d2a81086A075b3130BAc19e4c6DEe1D2E3fE8/logo.png",
+ "symbol" : "Helmet",
+ "name" : "Helmet.insure Governance Token",
+ "contractAddress" : "0x948d2a81086A075b3130BAc19e4c6DEe1D2E3fE8",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xa2B726B1145A4773F68593CF171187d8EBe4d495/logo.png",
+ "symbol" : "INJ",
+ "name" : "Injective Protocol",
+ "contractAddress" : "0xa2B726B1145A4773F68593CF171187d8EBe4d495",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7979F6C54ebA05E18Ded44C4F986F49a5De551c2/logo.png",
+ "symbol" : "KEBAB",
+ "name" : "Kebab Token",
+ "contractAddress" : "0x7979F6C54ebA05E18Ded44C4F986F49a5De551c2",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xb59490aB09A0f526Cc7305822aC65f2Ab12f9723/logo.png",
+ "symbol" : "LIT",
+ "name" : "Litentry",
+ "contractAddress" : "0xb59490aB09A0f526Cc7305822aC65f2Ab12f9723",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png",
+ "symbol" : "MATIC",
+ "name" : "Matic Token",
+ "contractAddress" : "0xcc42724c6683b7e57334c4e856f4c9965ed682bd",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xC7FB8c5eFE8564205eE5CbA4dE8eeA8a086a4bd2/logo.png",
+ "symbol" : "POCROC",
+ "name" : "PocketRocket",
+ "contractAddress" : "0xC7FB8c5eFE8564205eE5CbA4dE8eeA8a086a4bd2",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xF21768cCBC73Ea5B6fd3C687208a7c2def2d966e/logo.png",
+ "symbol" : "REEF",
+ "name" : "Reef.finance",
+ "contractAddress" : "0xF21768cCBC73Ea5B6fd3C687208a7c2def2d966e",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xE40255C5d7fa7ceEc5120408C78C787CECB4cfdb/logo.png",
+ "symbol" : "SWGb",
+ "name" : "SWGb",
+ "contractAddress" : "0xE40255C5d7fa7ceEc5120408C78C787CECB4cfdb",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xD41FDb03Ba84762dD66a0af1a6C8540FF1ba5dfb/logo.png",
+ "symbol" : "SFP",
+ "name" : "SafePal Token",
+ "contractAddress" : "0xD41FDb03Ba84762dD66a0af1a6C8540FF1ba5dfb",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x78650B139471520656b9E7aA7A5e9276814a38e9/logo.png",
+ "symbol" : "BTCST",
+ "name" : "StandardBTCHashrateToken",
+ "contractAddress" : "0x78650B139471520656b9E7aA7A5e9276814a38e9",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
+ "symbol" : "DAI",
+ "name" : "Binance-Peg Dai Token",
+ "contractAddress" : "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402/logo.png",
+ "symbol" : "DOT",
+ "name" : "Binance-Peg Polkadot",
+ "contractAddress" : "0x7083609fCE4d1d8Dc0C979AAb8c869Ea2C873402",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x67ee3Cb086F8a16f34beE3ca72FAD36F7Db929e2/logo.png",
+ "symbol" : "DODO",
+ "name" : "DODO",
+ "contractAddress" : "0x67ee3Cb086F8a16f34beE3ca72FAD36F7Db929e2",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x56b6fB708fC5732DEC1Afc8D8556423A2EDcCbD6/logo.png",
+ "symbol" : "EOS",
+ "name" : "Binance-Peg EOS",
+ "contractAddress" : "0x56b6fB708fC5732DEC1Afc8D8556423A2EDcCbD6",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x233d91A0713155003fc4DcE0AFa871b508B3B715/logo.png",
+ "symbol" : "DITTO",
+ "name" : "Ditto",
+ "contractAddress" : "0x233d91A0713155003fc4DcE0AFa871b508B3B715",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x2170Ed0880ac9A755fd29B2688956BD959F933F8/logo.png",
+ "symbol" : "ETH",
+ "name" : "Ethereum Token",
+ "contractAddress" : "0x2170Ed0880ac9A755fd29B2688956BD959F933F8",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x190b589cf9Fb8DDEabBFeae36a813FFb2A702454/logo.png",
+ "symbol" : "BDO",
+ "name" : "bDollar",
+ "contractAddress" : "0x190b589cf9Fb8DDEabBFeae36a813FFb2A702454",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82/logo.png",
+ "symbol" : "Cake",
+ "name" : "PancakeSwap Token",
+ "contractAddress" : "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x0D8Ce2A99Bb6e3B7Db580eD848240e4a0F9aE153/logo.png",
+ "symbol" : "FIL",
+ "name" : "Binance-Peg Filecoin",
+ "contractAddress" : "0x0D8Ce2A99Bb6e3B7Db580eD848240e4a0F9aE153",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x4338665CBB7B2485A8855A139b75D5e34AB0DB94/logo.png",
+ "symbol" : "LTC",
+ "name" : "Binance-Peg Litecoin",
+ "contractAddress" : "0x4338665CBB7B2485A8855A139b75D5e34AB0DB94",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
+ "symbol" : "USDT",
+ "name" : "Binance-Peg Tether USD",
+ "contractAddress" : "0x55d398326f99059fF775485246999027B3197955",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x2090c8295769791ab7A3CF1CC6e0AA19F35e441A/logo.png",
+ "symbol" : "Fuel",
+ "name" : "Fuel Token",
+ "contractAddress" : "0x2090c8295769791ab7A3CF1CC6e0AA19F35e441A",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x3aabCf53A1930A42E18D938C019E83Ebee50a849/logo.png",
+ "symbol" : "SPG",
+ "name" : "SPONGE",
+ "contractAddress" : "0x3aabCf53A1930A42E18D938C019E83Ebee50a849",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x37Ac4D6140e54304D77437A5c11924f61a2D976f/logo.png",
+ "symbol" : "SFUEL",
+ "name" : "SparkPoint Fuel",
+ "contractAddress" : "0x37Ac4D6140e54304D77437A5c11924f61a2D976f",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x47BEAd2563dCBf3bF2c9407fEa4dC236fAbA485A/logo.png",
+ "symbol" : "SXP",
+ "name" : "Swipe",
+ "contractAddress" : "0x47BEAd2563dCBf3bF2c9407fEa4dC236fAbA485A",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xeAF7D8395CCE52DAef138d39a1CEfA51b97C15aE/logo.png",
+ "symbol" : "TBC",
+ "name" : "TeraBlock Token",
+ "contractAddress" : "0xeAF7D8395CCE52DAef138d39a1CEfA51b97C15aE",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x4B0F1812e5Df2A09796481Ff14017e6005508003/logo.png",
+ "symbol" : "TWT",
+ "name" : "Trust Wallet",
+ "contractAddress" : "0x4B0F1812e5Df2A09796481Ff14017e6005508003",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x728C5baC3C3e370E372Fc4671f9ef6916b814d8B/logo.png",
+ "symbol" : "UNFI",
+ "name" : "UNFI",
+ "contractAddress" : "0x728C5baC3C3e370E372Fc4671f9ef6916b814d8B",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7/logo.png",
+ "symbol" : "VAI",
+ "name" : "VAI Stablecoin",
+ "contractAddress" : "0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x23396cF899Ca06c4472205fC903bDB4de249D6fC/logo.png",
+ "symbol" : "UST",
+ "name" : "Wrapped UST Token",
+ "contractAddress" : "0x23396cF899Ca06c4472205fC903bDB4de249D6fC",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE/logo.png",
+ "symbol" : "XRP",
+ "name" : "Binance-Peg XRP",
+ "contractAddress" : "0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x16939ef78684453bfDFb47825F8a5F714f12623a/logo.png",
+ "symbol" : "XTZ",
+ "name" : "Binance-Peg Tezos",
+ "contractAddress" : "0x16939ef78684453bfDFb47825F8a5F714f12623a",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63/logo.png",
+ "symbol" : "XVS",
+ "name" : "Venus",
+ "contractAddress" : "0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x0231f91e02DebD20345Ae8AB7D71A41f8E140cE7/logo.png",
+ "symbol" : "bwJUP",
+ "name" : "Jupiter",
+ "contractAddress" : "0x0231f91e02DebD20345Ae8AB7D71A41f8E140cE7",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c/logo.png",
+ "symbol" : "WBNB",
+ "name" : "Wrapped BNB",
+ "contractAddress" : "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x44754455564474A89358B2C2265883DF993b12F0/logo.png",
+ "symbol" : "ZEE",
+ "name" : "ZeroSwapToken",
+ "contractAddress" : "0x44754455564474A89358B2C2265883DF993b12F0",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ }
+]
diff --git a/app/src/main/assets/bsc_tokens_testnet.json b/app/src/main/assets/bsc_tokens_testnet.json
new file mode 100644
index 0000000000..c07946b0d7
--- /dev/null
+++ b/app/src/main/assets/bsc_tokens_testnet.json
@@ -0,0 +1,99 @@
+[
+ {
+ "decimalCount" : 18,
+ "symbol" : "BUSD",
+ "name" : "Binance-Peg BUSD Token",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56/logo.png",
+ "contractAddress" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ETH",
+ "name" : "Ethereum Token",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x2170Ed0880ac9A755fd29B2688956BD959F933F8/logo.png",
+ "contractAddress" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BTCB",
+ "name" : "Binance-Peg BTCB Token",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c/logo.png",
+ "contractAddress" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "USDT",
+ "name" : "Binance-Peg Tether USD",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
+ "contractAddress" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "USDT",
+ "name" : "Tether USD",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
+ "contractAddress" : "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "XRP",
+ "name" : "Binance-Peg XRP",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE/logo.png",
+ "contractAddress" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DAI",
+ "name" : "Binance-Peg Dai Token",
+ "customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
+ "contractAddress" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "USDC",
+ "name" : "Binance-Peg USDC Token",
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "contractAddress" : "0x64544969ed7ebf5f083679233325356ebe738930",
+ "blockchain" : {
+ "key" : "bsc",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ }
+]
+
diff --git a/app/src/main/assets/erc20_tokens.json b/app/src/main/assets/erc20_tokens.json
deleted file mode 100644
index 22fe8bd802..0000000000
--- a/app/src/main/assets/erc20_tokens.json
+++ /dev/null
@@ -1,927 +0,0 @@
-[
- {
- "decimalCount" : 18,
- "symbol" : "ZRX",
- "name" : "0x",
- "contractAddress" : "0xE41d2489571d322189246DaFA5ebDe1F4699F498"
- },
- {
- "decimalCount" : 18,
- "symbol" : "AAVE",
- "name" : "Aave",
- "contractAddress" : "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9"
- },
- {
- "decimalCount" : 18,
- "symbol" : "LEND",
- "name" : "Aave (OLD)",
- "contractAddress" : "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ADX",
- "name" : "AdEx",
- "contractAddress" : "0xADE00C28244d5CE17D72E40330B1c318cD12B7c3"
- },
- {
- "decimalCount" : 9,
- "symbol" : "ADT",
- "name" : "adToken",
- "contractAddress" : "0xD0D6D6C5Fe4a677D343cC433536BB717bAe167dD"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ELF",
- "name" : "Aelf",
- "contractAddress" : "0xbf2179859fc6d5bee9bf9158632dc51678a4100e"
- },
- {
- "decimalCount" : 8,
- "symbol" : "ARN",
- "name" : "Aeron",
- "contractAddress" : "0xBA5F11b16B155792Cf3B2E6880E8706859A8AEB6"
- },
- {
- "decimalCount" : 18,
- "symbol" : "AE",
- "name" : "Aeternity",
- "contractAddress" : "0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d"
- },
- {
- "decimalCount" : 8,
- "symbol" : "AION",
- "name" : "Aion",
- "contractAddress" : "0x4CEdA7906a5Ed2179785Cd3A40A69ee8bc99C466"
- },
- {
- "decimalCount" : 4,
- "symbol" : "AST",
- "name" : "Airswap",
- "contractAddress" : "0x27054b13b1b798b345b591a4d22e6562d47ea75a"
- },
- {
- "decimalCount" : 18,
- "symbol" : "AMB",
- "name" : "Amber",
- "contractAddress" : "0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce"
- },
- {
- "decimalCount" : 18,
- "symbol" : "AMLT",
- "name" : "AMLT by Coinfirm",
- "contractAddress" : "0xca0e7269600d353f70b14ad118a49575455c0f2f"
- },
- {
- "decimalCount" : 18,
- "symbol" : "APPC",
- "name" : "AppCoins",
- "contractAddress" : "0x1a7a8bd9106f2b8d977e08582dc7d24c723ab0db"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ANT",
- "name" : "Aragon",
- "contractAddress" : "0xa117000000f279d81a1d3cc75430faa017fa5a2e"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ABT",
- "name" : "ArcBlock",
- "contractAddress" : "0xb98d4c97425d9908e66e53a6fdf673acca0be986"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ATMI",
- "name" : "Atonomi",
- "contractAddress" : "0x97aeb5066e1a590e868b511457beb6fe99d329f5"
- },
- {
- "decimalCount" : 18,
- "symbol" : "REP",
- "name" : "Augur",
- "contractAddress" : "0x1985365e9f78359a9B6AD760e32412f4a445E862"
- },
- {
- "decimalCount" : 18,
- "symbol" : "AVM",
- "name" : "AVM Ecosystem",
- "contractAddress" : "0x74004a7227615fb52b82d17ffabfa376907d8a4d"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BNT",
- "name" : "Bancor",
- "contractAddress" : "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BAT",
- "name" : "Basic Attention",
- "contractAddress" : "0x0D8775F648430679A709E98d2b0Cb6250d2887EF"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BNB",
- "name" : "Binance Coin",
- "contractAddress" : "0xb8c77482e45f1f44de1745f52c74426c631bdd52"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BUSD",
- "name" : "Binance USD",
- "contractAddress" : "0x4fabb145d64652a948d72533023f6e7a623c7c53"
- },
- {
- "decimalCount" : 18,
- "symbol" : "STU",
- "name" : "bitJob",
- "contractAddress" : "0x0371a82e4a9d0a4312f3ee2ac9c6958512891372"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BIX",
- "name" : "BIX Token",
- "contractAddress" : "0xb3104b4b9da82025e8b9f8fb28b3553ce2f67069"
- },
- {
- "decimalCount" : 18,
- "symbol" : "VEE",
- "name" : "BLOCKv",
- "contractAddress" : "0x340d2bde5eb28c1eed91b2f790723e3b160613b7"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BLZ",
- "name" : "Bluzelle",
- "contractAddress" : "0x5732046a883704404f284ce41ffadd5b007fd668"
- },
- {
- "decimalCount" : 18,
- "symbol" : "BRD",
- "name" : "BRD Token",
- "contractAddress" : "0x558ec3152e2eb2174905cd19aea4e34a23de9ad6"
- },
- {
- "decimalCount" : 8,
- "symbol" : "BTM",
- "name" : "Bytom",
- "contractAddress" : "0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CAS",
- "name" : "Cashaa",
- "contractAddress" : "0xe8780b48bdb05f928697a5e8155f672ed91462f7"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CSP",
- "name" : "Caspian",
- "contractAddress" : "0xa6446d655a0c34bc4f05042ee88170d056cbaf45"
- },
- {
- "decimalCount" : 18,
- "symbol" : "LINK",
- "name" : "ChainLink",
- "contractAddress" : "0x514910771af9ca656af840dff83e8264ecf986ca"
- },
- {
- "decimalCount" : 8,
- "symbol" : "TIME",
- "name" : "Chronobank",
- "contractAddress" : "0x6531f133e6DeeBe7F2dcE5A0441aA7ef330B4e53"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CND",
- "name" : "Cindicator",
- "contractAddress" : "0xd4c435f5b09f855c3317c8524cb1f586e42795fa"
- },
- {
- "decimalCount" : 8,
- "symbol" : "CVC",
- "name" : "Civic",
- "contractAddress" : "0x41e5560054824ea6b0732e656e3ad64e20e94e45"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CNN",
- "name" : "CNN Token",
- "contractAddress" : "0x8713d26637cf49e1b6b4a7ce57106aabc9325343"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CFI",
- "name" : "Cofound.it",
- "contractAddress" : "0x12FEF5e57bF45873Cd9B62E9DBd7BFb99e32D73e"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CCC",
- "name" : "Container Crypto Coin",
- "contractAddress" : "0x9e3359f862b6c7f5c660cfd6d1aa6909b1d9504d"
- },
- {
- "decimalCount" : 6,
- "symbol" : "CS",
- "name" : "Credits",
- "contractAddress" : "0x46b9ad944d1059450da1163511069c718f699d31"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CRPT",
- "name" : "Crypterium",
- "contractAddress" : "0x08389495d7456e1951ddf7c3a1314a4bfb646d8b"
- },
- {
- "decimalCount" : 18,
- "symbol" : "AUTO",
- "name" : "CUBE",
- "contractAddress" : "0x622dFfCc4e83C64ba959530A5a5580687a57581b"
- },
- {
- "decimalCount" : 18,
- "symbol" : "CMT",
- "name" : "CyberMiles",
- "contractAddress" : "0xf85fEea2FdD81d51177F6b8F35F0e6734Ce45F5F"
- },
- {
- "decimalCount" : 18,
- "symbol" : "DAI",
- "name" : "Dai",
- "contractAddress" : "0x6b175474e89094c44da98b954eedeac495271d0f"
- },
- {
- "decimalCount" : 18,
- "symbol" : "MANA",
- "name" : "Decentraland",
- "contractAddress" : "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942"
- },
- {
- "decimalCount" : 8,
- "symbol" : "DENT",
- "name" : "DENT",
- "contractAddress" : "0x3597bfd533a99c9aa083587b074434e61eb0a258"
- },
- {
- "decimalCount" : 0,
- "symbol" : "XJP",
- "name" : "Digital JPY",
- "contractAddress" : "0x39689fE671C01fcE173395f6BC45D4C332026666"
- },
- {
- "decimalCount" : 9,
- "symbol" : "DGD",
- "name" : "DigixDAO",
- "contractAddress" : "0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A"
- },
- {
- "decimalCount" : 18,
- "symbol" : "DNT",
- "name" : "district0x",
- "contractAddress" : "0x0abdace70d3790235af448c88547603b945604ea"
- },
- {
- "decimalCount" : 18,
- "symbol" : "DOV",
- "name" : "Dovu",
- "contractAddress" : "0xac3211a5025414af2866ff09c23fc18bc97e79b1"
- },
- {
- "decimalCount" : 18,
- "symbol" : "DRGN",
- "name" : "Dragon",
- "contractAddress" : "0x419c4db4b9e25d6db2ad9691ccb832c8d9fda05e"
- },
- {
- "decimalCount" : 0,
- "symbol" : "EDG",
- "name" : "Edgeless",
- "contractAddress" : "0x08711D3B02C8758F2FB3ab4e80228418a7F8e39c"
- },
- {
- "decimalCount" : 18,
- "symbol" : "EDO",
- "name" : "Eidoo",
- "contractAddress" : "0xced4e93198734ddaff8492d525bd258d49eb388e"
- },
- {
- "decimalCount" : 8,
- "symbol" : "ENG",
- "name" : "Enigma",
- "contractAddress" : "0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ENJ",
- "name" : "EnjinCoin",
- "contractAddress" : "0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c"
- },
- {
- "decimalCount" : 18,
- "symbol" : "EOS",
- "name" : "EOS",
- "contractAddress" : "0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"
- },
- {
- "decimalCount" : 2,
- "symbol" : "EUR.AVM",
- "name" : "EUR AVM",
- "contractAddress" : "0xF01Cd2f1c9E42c509d309aC8C5b29B6dA8E64b1a"
- },
- {
- "decimalCount" : 18,
- "symbol" : "XUC",
- "name" : "Exchange Union",
- "contractAddress" : "0xc324a2f6b05880503444451b8b27e6f9e63287cb"
- },
- {
- "decimalCount" : 18,
- "symbol" : "1ST",
- "name" : "FirstBlood",
- "contractAddress" : "0xAf30D2a7E90d7DC361c8C4585e9BB7D2F6f15bc7"
- },
- {
- "decimalCount" : 18,
- "symbol" : "FXC",
- "name" : "Flexacoin",
- "contractAddress" : "0x4a57E687b9126435a9B19E4A802113e266AdeBde"
- },
- {
- "decimalCount" : 8,
- "symbol" : "FUN",
- "name" : "FunFair",
- "contractAddress" : "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b"
- },
- {
- "decimalCount" : 18,
- "symbol" : "FSN",
- "name" : "Fusion",
- "contractAddress" : "0xd0352a019e9ab9d757776f532377aaebd36fd541"
- },
- {
- "decimalCount" : 2,
- "symbol" : "GUSD",
- "name" : "Gemini Dollar",
- "contractAddress" : "0x056fd409e1d7a124bd7017459dfea2f387b6d5cd"
- },
- {
- "decimalCount" : 9,
- "symbol" : "GNX",
- "name" : "Genaro Network",
- "contractAddress" : "0x6ec8a24cabdc339a06a172f8223ea557055adaa5"
- },
- {
- "decimalCount" : 18,
- "symbol" : "GVT",
- "name" : "Genesis Vision",
- "contractAddress" : "0x103c3A209da59d3E7C4A89307e66521e081CFDF0"
- },
- {
- "decimalCount" : 5,
- "symbol" : "GTO",
- "name" : "Gifto",
- "contractAddress" : "0xc5bbae50781be1669306b9e001eff57a2957b09d"
- },
- {
- "decimalCount" : 8,
- "symbol" : "GBX",
- "name" : "Globitex",
- "contractAddress" : "0x12fcd6463e66974cf7bbc24ffc4d40d6be458283"
- },
- {
- "decimalCount" : 18,
- "symbol" : "GNO",
- "name" : "Gnosis",
- "contractAddress" : "0x6810e776880C02933D47DB1b9fc05908e5386b96"
- },
- {
- "decimalCount" : 18,
- "symbol" : "GNT",
- "name" : "Golem",
- "contractAddress" : "0xa74476443119A942dE498590Fe1f2454d7D4aC0d"
- },
- {
- "decimalCount" : 18,
- "symbol" : "HPB",
- "name" : "HPBCoin",
- "contractAddress" : "0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2"
- },
- {
- "decimalCount" : 18,
- "symbol" : "HYDRO",
- "name" : "Hydro",
- "contractAddress" : "0xebbdf302c940c6bfd49c6b165f457fdb324649bc"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ICX",
- "name" : "ICON",
- "contractAddress" : "0xb5a5f22694352c15b00323844ad545abb2b11028"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ICN",
- "name" : "ICONOMI",
- "contractAddress" : "0x888666CA69E0f178DED6D75b5726Cee99A87D698"
- },
- {
- "decimalCount" : 9,
- "symbol" : "RLC",
- "name" : "iExec RLC",
- "contractAddress" : "0x607F4C5BB672230e8672085532f7e901544a7375"
- },
- {
- "decimalCount" : 18,
- "symbol" : "IOST",
- "name" : "IOStoken",
- "contractAddress" : "0xfa1a856cfa3409cfa145fa4e20eb270df3eb21ab"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ITC",
- "name" : "IOT Chain",
- "contractAddress" : "0x5e6b6d9abad9093fdc861ea1600eba1b355cd940"
- },
- {
- "decimalCount" : 8,
- "symbol" : "IXT",
- "name" : "iXledger",
- "contractAddress" : "0xfca47962d45adfdfd1ab2d972315db4ce7ccf094"
- },
- {
- "decimalCount" : 18,
- "symbol" : "KIN",
- "name" : "Kin",
- "contractAddress" : "0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5"
- },
- {
- "decimalCount" : 18,
- "symbol" : "KNC",
- "name" : "Kyber Network",
- "contractAddress" : "0xdd974D5C2e2928deA5F71b9825b8b646686BD200"
- },
- {
- "decimalCount" : 18,
- "symbol" : "LOOM",
- "name" : "Loom",
- "contractAddress" : "0xa4e8c3ec456107ea67d3075bf9e3df3a75823db0"
- },
- {
- "decimalCount" : 18,
- "symbol" : "LRC",
- "name" : "Loopring",
- "contractAddress" : "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd"
- },
- {
- "decimalCount" : 18,
- "symbol" : "LUN",
- "name" : "Lunyr",
- "contractAddress" : "0xfa05A73FfE78ef8f1a739473e462c54bae6567D9"
- },
- {
- "decimalCount" : 18,
- "symbol" : "MKR",
- "name" : "Maker",
- "contractAddress" : "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2"
- },
- {
- "decimalCount" : 3,
- "symbol" : "GUP",
- "name" : "Matchpool",
- "contractAddress" : "0xf7B098298f7C69Fc14610bf71d5e02c60792894C"
- },
- {
- "decimalCount" : 18,
- "symbol" : "MAN",
- "name" : "MATRIX AI Network",
- "contractAddress" : "0xe25bcec5d3801ce3a794079bf94adf1b8ccd802d"
- },
- {
- "decimalCount" : 8,
- "symbol" : "MTL",
- "name" : "Metal",
- "contractAddress" : "0xF433089366899D83a9f26A773D59ec7eCF30355e"
- },
- {
- "decimalCount" : 8,
- "symbol" : "MCO",
- "name" : "Monaco",
- "contractAddress" : "0xB63B606Ac810a52cCa15e44bB630fd42D8d1d83d"
- },
- {
- "decimalCount" : 8,
- "symbol" : "MYST",
- "name" : "Mysterium",
- "contractAddress" : "0xa645264C5603E96c3b0B078cdab68733794B0A71"
- },
- {
- "decimalCount" : 18,
- "symbol" : "NAS",
- "name" : "Nebulas",
- "contractAddress" : "0x5d65D971895Edc438f465c17DB6992698a52318D"
- },
- {
- "decimalCount" : 18,
- "symbol" : "NEXO",
- "name" : "Nexo",
- "contractAddress" : "0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206"
- },
- {
- "decimalCount" : 18,
- "symbol" : "NCASH",
- "name" : "Nucleus Vision",
- "contractAddress" : "0x809826cceab68c387726af962713b64cb5cb3cca"
- },
- {
- "decimalCount" : 18,
- "symbol" : "NULS",
- "name" : "Nuls",
- "contractAddress" : "0xb91318f35bdb262e9423bc7c7c2a3a93dd93c92c"
- },
- {
- "decimalCount" : 18,
- "symbol" : "NMR",
- "name" : "Numeraire",
- "contractAddress" : "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671"
- },
- {
- "decimalCount" : 18,
- "symbol" : "OMG",
- "name" : "OmiseGO",
- "contractAddress" : "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
- },
- {
- "decimalCount" : 18,
- "symbol" : "OXT",
- "name" : "Orchid",
- "contractAddress" : "0x4575f41308ec1483f3d399aa9a2826d74da13deb"
- },
- {
- "decimalCount" : 18,
- "symbol" : "OST",
- "name" : "OST",
- "contractAddress" : "0x2C4e8f2D746113d0696cE89B35F0d8bF88E0AEcA"
- },
- {
- "decimalCount" : 18,
- "symbol" : "PRL",
- "name" : "Oyster Pearl",
- "contractAddress" : "0x1844b21593262668b7248d0f57a220caaba46ab9"
- },
- {
- "decimalCount" : 8,
- "symbol" : "PTOY",
- "name" : "Patientory",
- "contractAddress" : "0x8Ae4BF2C33a8e667de34B54938B0ccD03Eb8CC06"
- },
- {
- "decimalCount" : 18,
- "symbol" : "PAX",
- "name" : "Paxos Standard",
- "contractAddress" : "0x8e870d67f660d95d5be530380d0ec0bd388289e1"
- },
- {
- "decimalCount" : 18,
- "symbol" : "PMTN",
- "name" : "Peer Mountain",
- "contractAddress" : "0xe91df2bb9bccd18c45f01843cab88768d64f2d32"
- },
- {
- "decimalCount" : 8,
- "symbol" : "POE",
- "name" : "Po.et",
- "contractAddress" : "0x0e0989b1f9b8a38983c2ba8053269ca62ec9b195"
- },
- {
- "decimalCount" : 18,
- "symbol" : "PAL",
- "name" : "PolicyPal Network",
- "contractAddress" : "0xfeDAE5642668f8636A11987Ff386bfd215F942EE"
- },
- {
- "decimalCount" : 18,
- "symbol" : "POLY",
- "name" : "Polymath",
- "contractAddress" : "0x9992eC3cF6A55b00978cdDF2b27BC6882d88D1eC"
- },
- {
- "decimalCount" : 8,
- "symbol" : "PPT",
- "name" : "Populous",
- "contractAddress" : "0xd4fa1460F537bb9085d22C7bcCB5DD450Ef28e3a"
- },
- {
- "decimalCount" : 6,
- "symbol" : "POWR",
- "name" : "Power Ledger",
- "contractAddress" : "0x595832f8fc6bf59c85c527fec3740a1b7a361269"
- },
- {
- "decimalCount" : 18,
- "symbol" : "NPXS",
- "name" : "Pundi X Token",
- "contractAddress" : "0xa15c7ebe1f07caf6bff097d8a589fb8ac49ae5b3"
- },
- {
- "decimalCount" : 6,
- "symbol" : "QASH",
- "name" : "QASH",
- "contractAddress" : "0x618e75ac90b12c6049ba3b27f5d5f8651b0037f6"
- },
- {
- "decimalCount" : 8,
- "symbol" : "QCX",
- "name" : "QuickX Protocol",
- "contractAddress" : "0xf9e5af7b42d31d51677c75bbbd37c1986ec79aee",
- "customIcon": "qcx"
- },
- {
- "decimalCount" : 8,
- "symbol" : "QRL",
- "name" : "QRL",
- "contractAddress" : "0x697beac28B09E122C4332D163985e8a73121b97F"
- },
- {
- "decimalCount" : 18,
- "symbol" : "QSP",
- "name" : "Quantstamp",
- "contractAddress" : "0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d"
- },
- {
- "decimalCount" : 18,
- "symbol" : "RDN",
- "name" : "Raiden Network",
- "contractAddress" : "0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6"
- },
- {
- "decimalCount" : 18,
- "symbol" : "RCC",
- "name" : "Reality Clash",
- "contractAddress" : "0x9b6443b0fb9c241a7fdac375595cea13e6b7807a"
- },
- {
- "decimalCount" : 6,
- "symbol" : "RC1U03",
- "name" : "RentalCoin 1.0",
- "contractAddress" : "0x7384db1aaa06364d1e6e67f5434391d3eea2a038"
- },
- {
- "decimalCount" : 18,
- "symbol" : "REQ",
- "name" : "Request",
- "contractAddress" : "0x8f8221aFbB33998d8584A2B05749bA73c37a938a"
- },
- {
- "decimalCount" : 0,
- "symbol" : "REV",
- "name" : "Revain",
- "contractAddress" : "0x48f775efbe4f5ece6e0df2f7b5932df56823b990"
- },
- {
- "decimalCount" : 18,
- "symbol" : "RCN",
- "name" : "Ripio Credit",
- "contractAddress" : "0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6"
- },
- {
- "decimalCount" : 18,
- "symbol" : "RVT",
- "name" : "Rivetz",
- "contractAddress" : "0x3d1ba9be9f66b8ee101911bc36d3fb562eac2244"
- },
- {
- "decimalCount" : 18,
- "symbol" : "RUFF",
- "name" : "RUFF",
- "contractAddress" : "0xf278c1ca969095ffddded020290cf8b5c424ace2"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SAI",
- "name" : "Sai",
- "contractAddress" : "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
- },
- {
- "decimalCount" : 8,
- "symbol" : "SALT",
- "name" : "SALT",
- "contractAddress" : "0x4156D3342D5c385a87D264F90653733592000581"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SAN",
- "name" : "SAN",
- "contractAddress" : "0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098"
- },
- {
- "decimalCount" : 8,
- "symbol" : "SENT",
- "name" : "SENTinel",
- "contractAddress" : "0xa44e5137293e855b1b7bc7e2c6f8cd796ffcb037"
- },
- {
- "decimalCount" : 0,
- "symbol" : "SNGLS",
- "name" : "Singular DTV",
- "contractAddress" : "0xaeC2E87E0A235266D9C5ADc9DEb4b2E29b54D009"
- },
- {
- "decimalCount" : 8,
- "symbol" : "AGI",
- "name" : "SingularityNET",
- "contractAddress" : "0x8eB24319393716668D768dCEC29356ae9CfFe285"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SRN",
- "name" : "SIRIN",
- "contractAddress" : "0x68d57c9a1C35f63E2c83eE8e49A64e9d70528D25"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SNM",
- "name" : "SONM",
- "contractAddress" : "0x983F6d60db79ea8cA4eB9968C6aFf8cfA04B3c63"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SNT",
- "name" : "Status",
- "contractAddress" : "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E"
- },
- {
- "decimalCount" : 18,
- "symbol" : "STK",
- "name" : "STK",
- "contractAddress" : "0xae73b38d1c9a8b274127ec30160a4927c4d71824"
- },
- {
- "decimalCount" : 18,
- "symbol" : "STQ",
- "name" : "Storiqa",
- "contractAddress" : "0x5c3a228510d246b78a3765c20221cbf3082b44a4"
- },
- {
- "decimalCount" : 8,
- "symbol" : "STORJ",
- "name" : "Storj",
- "contractAddress" : "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC"
- },
- {
- "decimalCount" : 18,
- "symbol" : "STORM",
- "name" : "Storm",
- "contractAddress" : "0xD0a4b8946Cb52f0661273bfbC6fD0E0C75Fc6433"
- },
- {
- "decimalCount" : 18,
- "symbol" : "STMX",
- "name" : "StormX",
- "contractAddress" : "0xbE9375C6a420D2eEB258962efB95551A5b722803"
- },
- {
- "decimalCount" : 18,
- "symbol" : "STX",
- "name" : "Stox",
- "contractAddress" : "0x006BeA43Baa3f7A6f765F14f10A1a1b08334EF45"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SUB",
- "name" : "Substratum",
- "contractAddress" : "0x8D75959f1E61EC2571aa72798237101F084DE63a"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SNC",
- "name" : "SunContract",
- "contractAddress" : "0xF4134146AF2d511Dd5EA8cDB1C4AC88C57D60404"
- },
- {
- "decimalCount" : 18,
- "symbol" : "SUSHI",
- "name" : "SushiSwap",
- "contractAddress" : "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2"
- },
- {
- "decimalCount" : 2,
- "symbol" : "TEL",
- "name" : "Telcoin",
- "contractAddress" : "0x467bccd9d29f223bce8043b84e8c8b282827790f"
- },
- {
- "decimalCount" : 18,
- "symbol" : "PAY",
- "name" : "TenX",
- "contractAddress" : "0xB97048628DB6B661D4C2aA833e95Dbe1A905B280"
- },
- {
- "decimalCount" : 6,
- "symbol" : "USDT",
- "name" : "Tether USD",
- "contractAddress" : "0xdac17f958d2ee523a2206206994597c13d831ec7"
- },
- {
- "decimalCount" : 18,
- "symbol" : "THETA",
- "name" : "Theta Token",
- "contractAddress" : "0x3883f5e181fccaf8410fa61e12b59bad963fb645"
- },
- {
- "decimalCount" : 6,
- "symbol" : "TRX",
- "name" : "TRON",
- "contractAddress" : "0xf230b790e05390fc8295f4d3f60332c93bed42e2"
- },
- {
- "decimalCount" : 18,
- "symbol" : "TUSD",
- "name" : "TrueUSD",
- "contractAddress" : "0x0000000000085d4780B73119b644AE5ecd22b376"
- },
- {
- "decimalCount" : 8,
- "symbol" : "UCASH",
- "name" : "UCASH",
- "contractAddress" : "0x92e52a1a235d9a103d970901066ce910aacefd37"
- },
- {
- "decimalCount" : 18,
- "symbol" : "UNI",
- "name" : "Uniswap",
- "contractAddress" : "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"
- },
- {
- "decimalCount" : 6,
- "symbol" : "USDC",
- "name" : "USD Coin",
- "contractAddress" : "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
- },
- {
- "decimalCount" : 18,
- "symbol" : "VIB",
- "name" : "Viberate",
- "contractAddress" : "0x2C974B2d0BA1716E644c1FC59982a89DDD2fF724"
- },
- {
- "decimalCount" : 8,
- "symbol" : "VGX",
- "name" : "Voyager",
- "contractAddress" : "0x5af2be193a6abca9c8817001f45744777db30756"
- },
- {
- "decimalCount" : 18,
- "symbol" : "WTC",
- "name" : "Waltonchain",
- "contractAddress" : "0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74"
- },
- {
- "decimalCount" : 8,
- "symbol" : "WAX",
- "name" : "WAX",
- "contractAddress" : "0x39Bb259F66E1C59d5ABEF88375979b4D20D98022"
- },
- {
- "decimalCount" : 18,
- "symbol" : "WETH",
- "name" : "Wrapped Ether",
- "contractAddress" : "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
- },
- {
- "decimalCount" : 18,
- "symbol" : "WPR",
- "name" : "WePower",
- "contractAddress" : "0x4CF488387F035FF08c371515562CBa712f9015d4"
- },
- {
- "decimalCount" : 18,
- "symbol" : "WINGS",
- "name" : "Wings",
- "contractAddress" : "0x667088b212ce3d06a1b553a7221E1fD19000d9aF"
- },
- {
- "decimalCount" : 6,
- "symbol" : "WRC",
- "name" : "Worldcore",
- "contractAddress" : "0x72adadb447784dd7ab1f472467750fc485e4cb2d"
- },
- {
- "decimalCount" : 18,
- "symbol" : "ZLA",
- "name" : "Zilla",
- "contractAddress" : "0xfd8971d5e8e1740ce2d0a84095fca4de729d0c16"
- },
- {
- "decimalCount" : 0,
- "symbol" : "GD",
- "name" : "Zilla GD",
- "contractAddress" : "0xb4cebdb66b3a5183fd4764a25cad1fc109535016"
- },
- {
- "decimalCount" : 12,
- "symbol" : "ZIL",
- "name" : "Zilliqa",
- "contractAddress" : "0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27"
- }
-]
diff --git a/app/src/main/assets/ethereum_tokens.json b/app/src/main/assets/ethereum_tokens.json
new file mode 100644
index 0000000000..d8a8c0cd97
--- /dev/null
+++ b/app/src/main/assets/ethereum_tokens.json
@@ -0,0 +1,4765 @@
+[
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7FF4169a6B5122b664c51c95727d87750eC07c84/logo.png",
+ "symbol" : "10SET",
+ "name" : "10Set Token",
+ "contractAddress" : "0x7FF4169a6B5122b664c51c95727d87750eC07c84",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x111111111117dC0aa78b770fA6A738034120C302/logo.png",
+ "symbol" : "1INCH",
+ "name" : "1INCH Token",
+ "contractAddress" : "0x111111111117dC0aa78b770fA6A738034120C302",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xBBc2AE13b23d715c30720F079fcd9B4a74093505/logo.png",
+ "symbol" : "ERN",
+ "name" : "@EthernityChain $ERN Token",
+ "contractAddress" : "0xBBc2AE13b23d715c30720F079fcd9B4a74093505",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "1ST",
+ "name" : "FirstBlood",
+ "contractAddress" : "0xAf30D2a7E90d7DC361c8C4585e9BB7D2F6f15bc7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/logo.png",
+ "symbol" : "AAVE",
+ "name" : "Aave Token",
+ "contractAddress" : "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ABT",
+ "name" : "ArcBlock",
+ "contractAddress" : "0xb98d4c97425d9908e66e53a6fdf673acca0be986",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xADE00C28244d5CE17D72E40330B1c318cD12B7c3/logo.png",
+ "symbol" : "ADX",
+ "name" : "AdEx Network",
+ "contractAddress" : "0xADE00C28244d5CE17D72E40330B1c318cD12B7c3",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AE",
+ "name" : "Aeternity",
+ "contractAddress" : "0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "AION",
+ "name" : "Aion",
+ "contractAddress" : "0x4CEdA7906a5Ed2179785Cd3A40A69ee8bc99C466",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8eB24319393716668D768dCEC29356ae9CfFe285/logo.png",
+ "symbol" : "AGI",
+ "name" : "SingularityNET Token",
+ "contractAddress" : "0x8eB24319393716668D768dCEC29356ae9CfFe285",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x626E8036dEB333b408Be468F951bdB42433cBF18/logo.png",
+ "symbol" : "AIOZ",
+ "name" : "AIOZ Network",
+ "contractAddress" : "0x626E8036dEB333b408Be468F951bdB42433cBF18",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x00a8b738E453fFd858a7edf03bcCfe20412f0Eb0/logo.png",
+ "symbol" : "ALBT",
+ "name" : "AllianceBlock Token",
+ "contractAddress" : "0x00a8b738E453fFd858a7edf03bcCfe20412f0Eb0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AMLT",
+ "name" : "AMLT by Coinfirm",
+ "contractAddress" : "0xca0e7269600d353f70b14ad118a49575455c0f2f",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x95a4492F028aa1fd432Ea71146b433E7B4446611/logo.png",
+ "symbol" : "APY",
+ "name" : "APY Governance Token",
+ "contractAddress" : "0x95a4492F028aa1fd432Ea71146b433E7B4446611",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "ARN",
+ "name" : "Aeron",
+ "contractAddress" : "0xBA5F11b16B155792Cf3B2E6880E8706859A8AEB6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 4,
+ "symbol" : "AST",
+ "name" : "Airswap",
+ "contractAddress" : "0x27054b13b1b798b345b591a4d22e6562d47ea75a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AVM",
+ "name" : "AVM Ecosystem",
+ "contractAddress" : "0x74004a7227615fb52b82d17ffabfa376907d8a4d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ELF",
+ "name" : "Aelf",
+ "contractAddress" : "0xbf2179859fc6d5bee9bf9158632dc51678a4100e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AMB",
+ "name" : "Amber",
+ "contractAddress" : "0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xfF20817765cB7f73d4bde2e66e067E58D11095C2/logo.png",
+ "symbol" : "AMP",
+ "name" : "Amp",
+ "contractAddress" : "0xfF20817765cB7f73d4bde2e66e067E58D11095C2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets//logo.png",
+ "symbol" : "AMPL",
+ "name" : "Ampleforth",
+ "contractAddress" : "0xD46bA6D942050d489DBd938a2C909A5d5039A161",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa117000000f279D81A1D3cc75430fAA017FA5A2e/logo.png",
+ "symbol" : "ANT",
+ "name" : "Aragon Network Token",
+ "contractAddress" : "0xa117000000f279D81A1D3cc75430fAA017FA5A2e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "APPC",
+ "name" : "AppCoins",
+ "contractAddress" : "0x1a7a8bd9106f2b8d977e08582dc7d24c723ab0db",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1337DEF16F9B486fAEd0293eb623Dc8395dFE46a/logo.png",
+ "symbol" : "ARMOR",
+ "name" : "Armor",
+ "contractAddress" : "0x1337DEF16F9B486fAEd0293eb623Dc8395dFE46a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdacD69347dE42baBfAEcD09dC88958378780FB62/logo.png",
+ "symbol" : "ATRI",
+ "name" : "AtariToken",
+ "contractAddress" : "0xdacD69347dE42baBfAEcD09dC88958378780FB62",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ATMI",
+ "name" : "Atonomi",
+ "contractAddress" : "0x97aeb5066e1a590e868b511457beb6fe99d329f5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998/logo.png",
+ "symbol" : "AUDIO",
+ "name" : "Audius",
+ "contractAddress" : "0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AUTO",
+ "name" : "CUBE",
+ "contractAddress" : "0x622dFfCc4e83C64ba959530A5a5580687a57581b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF5D669627376EBd411E34b98F19C868c8ABA5ADA/logo.png",
+ "symbol" : "AXS",
+ "name" : "Axie Infinity",
+ "contractAddress" : "0xF5D669627376EBd411E34b98F19C868c8ABA5ADA",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1C9491865a1DE77C5b6e19d2E6a5F1D7a6F2b25F/logo.png",
+ "symbol" : "MATTER",
+ "name" : "Antimatter.Finance Governance Token",
+ "contractAddress" : "0x1C9491865a1DE77C5b6e19d2E6a5F1D7a6F2b25F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x71F85B2E46976bD21302B64329868fd15eb0D127/logo.png",
+ "symbol" : "AXN",
+ "name" : "Axion",
+ "contractAddress" : "0x71F85B2E46976bD21302B64329868fd15eb0D127",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xba100000625a3754423978a60c9317c58a424e3D/logo.png",
+ "symbol" : "BAL",
+ "name" : "Balancer",
+ "contractAddress" : "0xba100000625a3754423978a60c9317c58a424e3D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BIX",
+ "name" : "BIX Token",
+ "contractAddress" : "0xb3104b4b9da82025e8b9f8fb28b3553ce2f67069",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5Dc02Ea99285E17656b8350722694c35154DB1E8/logo.png",
+ "symbol" : "BOND",
+ "name" : "BOND",
+ "contractAddress" : "0x5Dc02Ea99285E17656b8350722694c35154DB1E8",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BRD",
+ "name" : "BRD Token",
+ "contractAddress" : "0x558ec3152e2eb2174905cd19aea4e34a23de9ad6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BNT",
+ "name" : "Bancor",
+ "contractAddress" : "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xBA11D00c5f74255f56a5E366F4F77f5A186d7f55/logo.png",
+ "symbol" : "BAND",
+ "name" : "BandToken",
+ "contractAddress" : "0xBA11D00c5f74255f56a5E366F4F77f5A186d7f55",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x54C9EA2E9C9E8eD865Db4A4ce6711C2a0d5063Ba/logo.png",
+ "symbol" : "BART",
+ "name" : "BarterTrade",
+ "contractAddress" : "0x54C9EA2E9C9E8eD865Db4A4ce6711C2a0d5063Ba",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x07150e919B4De5fD6a63DE1F9384828396f25fDC/logo.png",
+ "symbol" : "BASE",
+ "name" : "Base Protocol",
+ "contractAddress" : "0x07150e919B4De5fD6a63DE1F9384828396f25fDC",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0D8775F648430679A709E98d2b0Cb6250d2887EF/logo.png",
+ "symbol" : "BAT",
+ "name" : "Basic Attention Token",
+ "contractAddress" : "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA91ac63D040dEB1b7A5E4d4134aD23eb0ba07e14/logo.png",
+ "symbol" : "BEL",
+ "name" : "Bella",
+ "contractAddress" : "0xA91ac63D040dEB1b7A5E4d4134aD23eb0ba07e14",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BNB",
+ "name" : "Binance Coin",
+ "contractAddress" : "0xb8c77482e45f1f44de1745f52c74426c631bdd52",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BUSD",
+ "name" : "Binance USD",
+ "contractAddress" : "0x4fabb145d64652a948d72533023f6e7a623c7c53",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x67c597624B17b16fb77959217360B7cD18284253/logo.png",
+ "symbol" : "MARK",
+ "name" : "Benchmark",
+ "contractAddress" : "0x67c597624B17b16fb77959217360B7cD18284253",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7671904eed7f10808B664fc30BB8693FD7237abF/logo.png",
+ "symbol" : "BBR",
+ "name" : "BitberryToken",
+ "contractAddress" : "0x7671904eed7f10808B664fc30BB8693FD7237abF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xCF3C8Be2e2C42331Da80EF210e9B1b307C03d36A/logo.png",
+ "symbol" : "BEPRO",
+ "name" : "BetProtocolToken",
+ "contractAddress" : "0xCF3C8Be2e2C42331Da80EF210e9B1b307C03d36A",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0c7D5ae016f806603CB1782bEa29AC69471CAb9c/logo.png",
+ "symbol" : "BFC",
+ "name" : "Bifrost",
+ "contractAddress" : "0x0c7D5ae016f806603CB1782bEa29AC69471CAb9c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9BE89D2a4cd102D8Fecc6BF9dA793be995C22541/logo.png",
+ "symbol" : "BBTC",
+ "name" : "Binance Wrapped BTC",
+ "contractAddress" : "0x9BE89D2a4cd102D8Fecc6BF9dA793be995C22541",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x25e1474170c4c0aA64fa98123bdc8dB49D7802fa/logo.png",
+ "symbol" : "BID",
+ "name" : "Bidao",
+ "contractAddress" : "0x25e1474170c4c0aA64fa98123bdc8dB49D7802fa",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x70401dFD142A16dC7031c56E862Fc88Cb9537Ce0/logo.png",
+ "symbol" : "BIRD",
+ "name" : "Bird.Money",
+ "contractAddress" : "0x70401dFD142A16dC7031c56E862Fc88Cb9537Ce0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xAec7e1f531Bb09115103C53ba76829910Ec48966/logo.png",
+ "symbol" : "BLANK",
+ "name" : "Blank Token",
+ "contractAddress" : "0xAec7e1f531Bb09115103C53ba76829910Ec48966",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BLZ",
+ "name" : "Bluzelle",
+ "contractAddress" : "0x5732046a883704404f284ce41ffadd5b007fd668",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x725C263e32c72dDC3A19bEa12C5a0479a81eE688/logo.png",
+ "symbol" : "BMI",
+ "name" : "Bridge Mutual",
+ "contractAddress" : "0x725C263e32c72dDC3A19bEa12C5a0479a81eE688",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD2dDa223b2617cB616c1580db421e4cFAe6a8a85/logo.png",
+ "symbol" : "BONDLY",
+ "name" : "Bondly Token",
+ "contractAddress" : "0xD2dDa223b2617cB616c1580db421e4cFAe6a8a85",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "BTM",
+ "name" : "Bytom",
+ "contractAddress" : "0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CANDY",
+ "name" : "Candy",
+ "contractAddress" : "0xf2eab3a2034d3f6b63734d2e08262040e3ff7b48",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x954b890704693af242613edEf1B603825afcD708/logo.png",
+ "symbol" : "CARD",
+ "name" : "Cardstack",
+ "contractAddress" : "0x954b890704693af242613edEf1B603825afcD708",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CAS",
+ "name" : "Cashaa",
+ "contractAddress" : "0xe8780b48bdb05f928697a5e8155f672ed91462f7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CCC",
+ "name" : "Container Crypto Coin",
+ "contractAddress" : "0x9e3359f862b6c7f5c660cfd6d1aa6909b1d9504d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x17aC188e09A7890a1844E5E65471fE8b0CcFadF3/logo.png",
+ "symbol" : "CC10",
+ "name" : "Cryptocurrency Top 10 Tokens Index",
+ "contractAddress" : "0x17aC188e09A7890a1844E5E65471fE8b0CcFadF3",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x26c8AFBBFE1EBaca03C2bB082E69D0476Bffe099/logo.png",
+ "symbol" : "CELL",
+ "name" : "Cellframe Token",
+ "contractAddress" : "0x26c8AFBBFE1EBaca03C2bB082E69D0476Bffe099",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 4,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d/logo.png",
+ "symbol" : "CEL",
+ "name" : "Celsius",
+ "contractAddress" : "0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CFI",
+ "name" : "Cofound.it",
+ "contractAddress" : "0x12FEF5e57bF45873Cd9B62E9DBd7BFb99e32D73e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC4C2614E694cF534D407Ee49F8E44D125E4681c4/logo.png",
+ "symbol" : "CHAIN",
+ "name" : "Chain Games",
+ "contractAddress" : "0xC4C2614E694cF534D407Ee49F8E44D125E4681c4",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x63b4f3e3fa4e438698CE330e365E831F7cCD1eF4/logo.png",
+ "symbol" : "CFi",
+ "name" : "CyberFi Token",
+ "contractAddress" : "0x63b4f3e3fa4e438698CE330e365E831F7cCD1eF4",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CNN",
+ "name" : "CNN Token",
+ "contractAddress" : "0x8713d26637cf49e1b6b4a7ce57106aabc9325343",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xDDB3422497E61e13543BeA06989C0789117555c5/logo.png",
+ "symbol" : "COTI",
+ "name" : "COTI Token",
+ "contractAddress" : "0xDDB3422497E61e13543BeA06989C0789117555c5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CSP",
+ "name" : "Caspian",
+ "contractAddress" : "0xa6446d655a0c34bc4f05042ee88170d056cbaf45",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1fE24F25b1Cf609B9c4e7E12D802e3640dFA5e43/logo.png",
+ "symbol" : "CGG",
+ "name" : "ChainGuardians Governance Token",
+ "contractAddress" : "0x1fE24F25b1Cf609B9c4e7E12D802e3640dFA5e43",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CMT",
+ "name" : "CyberMiles",
+ "contractAddress" : "0xf85fEea2FdD81d51177F6b8F35F0e6734Ce45F5F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CND",
+ "name" : "Cindicator",
+ "contractAddress" : "0xd4c435f5b09f855c3317c8524cb1f586e42795fa",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "CVC",
+ "name" : "Civic",
+ "contractAddress" : "0x41e5560054824ea6b0732e656e3ad64e20e94e45",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x69692D3345010a207b759a7D1af6fc7F38b35c5E/logo.png",
+ "symbol" : "CHADS",
+ "name" : "chads.vc",
+ "contractAddress" : "0x69692D3345010a207b759a7D1af6fc7F38b35c5E",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png",
+ "symbol" : "Link",
+ "name" : "ChainLink",
+ "contractAddress" : "0x514910771AF9Ca656af840dff83E8264EcF986CA",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "TIME",
+ "name" : "Chronobank",
+ "contractAddress" : "0x6531f133e6DeeBe7F2dcE5A0441aA7ef330B4e53",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xE61fDAF474Fac07063f2234Fb9e60C1163Cfa850/logo.png",
+ "symbol" : "COIN",
+ "name" : "Coin Utility Token",
+ "contractAddress" : "0xE61fDAF474Fac07063f2234Fb9e60C1163Cfa850",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc00e94Cb662C3520282E6f5717214004A7f26888/logo.png",
+ "symbol" : "COMP",
+ "name" : "Compound",
+ "contractAddress" : "0xc00e94Cb662C3520282E6f5717214004A7f26888",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x38e4adB44ef08F22F5B5b76A8f0c2d0dCbE7DcA1/logo.png",
+ "symbol" : "CVP",
+ "name" : "Concentrated Voting Power",
+ "contractAddress" : "0x38e4adB44ef08F22F5B5b76A8f0c2d0dCbE7DcA1",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc834Fa996fA3BeC7aAD3693af486ae53D8aA8B50/logo.png",
+ "symbol" : "CONV",
+ "name" : "Convergence",
+ "contractAddress" : "0xc834Fa996fA3BeC7aAD3693af486ae53D8aA8B50",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x62359Ed7505Efc61FF1D56fEF82158CcaffA23D7/logo.png",
+ "symbol" : "CORE",
+ "name" : "cVault.finance",
+ "contractAddress" : "0x62359Ed7505Efc61FF1D56fEF82158CcaffA23D7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2ba592F78dB6436527729929AAf6c908497cB200/logo.png",
+ "symbol" : "CREAM",
+ "name" : "Cream",
+ "contractAddress" : "0x2ba592F78dB6436527729929AAf6c908497cB200",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "CS",
+ "name" : "Credits",
+ "contractAddress" : "0x46b9ad944d1059450da1163511069c718f699d31",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaC0104Cca91D167873B8601d2e71EB3D4D8c33e0/logo.png",
+ "symbol" : "CWS",
+ "name" : "Crowns",
+ "contractAddress" : "0xaC0104Cca91D167873B8601d2e71EB3D4D8c33e0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CRPT",
+ "name" : "Crypterium",
+ "contractAddress" : "0x08389495d7456e1951ddf7c3a1314a4bfb646d8b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0f51bb10119727a7e5eA3538074fb341F56B09Ad/logo.png",
+ "symbol" : "DAO",
+ "name" : "DAO Maker Token",
+ "contractAddress" : "0x0f51bb10119727a7e5eA3538074fb341F56B09Ad",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x26CE25148832C04f3d7F26F32478a9fe55197166/logo.png",
+ "symbol" : "DEXT",
+ "name" : "DEXTools",
+ "contractAddress" : "0x26CE25148832C04f3d7F26F32478a9fe55197166",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png",
+ "symbol" : "DAI",
+ "name" : "Dai Stablecoin",
+ "contractAddress" : "0x6B175474E89094C44Da98b954EedeAC495271d0F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xFbEEa1C75E4c4465CB2FCCc9c6d6afe984558E20/logo.png",
+ "symbol" : "DDIM",
+ "name" : "DuckDaoDime",
+ "contractAddress" : "0xFbEEa1C75E4c4465CB2FCCc9c6d6afe984558E20",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xE1c7E30C42C24582888C758984f6e382096786bd/logo.png",
+ "symbol" : "XCUR",
+ "name" : "Curate",
+ "contractAddress" : "0xE1c7E30C42C24582888C758984f6e382096786bd",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD533a949740bb3306d119CC777fa900bA034cd52/logo.png",
+ "symbol" : "CRV",
+ "name" : "Curve DAO Token",
+ "contractAddress" : "0xD533a949740bb3306d119CC777fa900bA034cd52",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x30f271C9E86D2B7d00a6376Cd96A1cFBD5F0b9b3/logo.png",
+ "symbol" : "DEC",
+ "name" : "Decentr",
+ "contractAddress" : "0x30f271C9E86D2B7d00a6376Cd96A1cFBD5F0b9b3",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9EA3b5b4EC044b70375236A281986106457b20EF/logo.png",
+ "symbol" : "DELTA",
+ "name" : "Delta Financial",
+ "contractAddress" : "0x9EA3b5b4EC044b70375236A281986106457b20EF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "DENT",
+ "name" : "DENT",
+ "contractAddress" : "0x3597bfd533a99c9aa083587b074434e61eb0a258",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xd379700999F4805Ce80aa32DB46A94dF64561108/logo.png",
+ "symbol" : "DETS",
+ "name" : "Dextrust",
+ "contractAddress" : "0xd379700999F4805Ce80aa32DB46A94dF64561108",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5cAf454Ba92e6F2c929DF14667Ee360eD9fD5b26/logo.png",
+ "symbol" : "DEV",
+ "name" : "Dev",
+ "contractAddress" : "0x5cAf454Ba92e6F2c929DF14667Ee360eD9fD5b26",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5F64Ab1544D28732F0A24F4713c2C8ec0dA089f0/logo.png",
+ "symbol" : "DEXTF",
+ "name" : "DEXTF Token",
+ "contractAddress" : "0x5F64Ab1544D28732F0A24F4713c2C8ec0dA089f0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc719d010B63E5bbF2C0551872CD5316ED26AcD83/logo.png",
+ "symbol" : "DIP",
+ "name" : "Decentralized Insurance Protocol",
+ "contractAddress" : "0xc719d010B63E5bbF2C0551872CD5316ED26AcD83",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x26E43759551333e57F073bb0772F50329A957b30/logo.png",
+ "symbol" : "DGVC",
+ "name" : "DegenVC",
+ "contractAddress" : "0x26E43759551333e57F073bb0772F50329A957b30",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x152687Bc4A7FCC89049cF119F9ac3e5aCF2eE7ef/logo.png",
+ "symbol" : "DHC",
+ "name" : "DeltaHub Community",
+ "contractAddress" : "0x152687Bc4A7FCC89049cF119F9ac3e5aCF2eE7ef",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x431ad2ff6a9C365805eBaD47Ee021148d6f7DBe0/logo.png",
+ "symbol" : "DF",
+ "name" : "dForce",
+ "contractAddress" : "0x431ad2ff6a9C365805eBaD47Ee021148d6f7DBe0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "DGD",
+ "name" : "DigixDAO",
+ "contractAddress" : "0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x88EF27e69108B2633F8E1C184CC37940A075cC02/logo.png",
+ "symbol" : "DEGO",
+ "name" : "dego.finance",
+ "contractAddress" : "0x88EF27e69108B2633F8E1C184CC37940A075cC02",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x84cA8bc7997272c7CfB4D0Cd3D55cd942B3c9419/logo.png",
+ "symbol" : "DIA",
+ "name" : "DIA",
+ "contractAddress" : "0x84cA8bc7997272c7CfB4D0Cd3D55cd942B3c9419",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xEd91879919B71bB6905f23af0A68d231EcF87b14/logo.png",
+ "symbol" : "DMG",
+ "name" : "DMM: Governance",
+ "contractAddress" : "0xEd91879919B71bB6905f23af0A68d231EcF87b14",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9cEB84f92A0561fa3Cc4132aB9c0b76A59787544/logo.png",
+ "symbol" : "DOKI",
+ "name" : "DokiDokiFinance",
+ "contractAddress" : "0x9cEB84f92A0561fa3Cc4132aB9c0b76A59787544",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DOV",
+ "name" : "Dovu",
+ "contractAddress" : "0xac3211a5025414af2866ff09c23fc18bc97e79b1",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DRGN",
+ "name" : "Dragon",
+ "contractAddress" : "0x419c4db4b9e25d6db2ad9691ccb832c8d9fda05e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DNT",
+ "name" : "district0x",
+ "contractAddress" : "0x0abdace70d3790235af448c88547603b945604ea",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3aFfCCa64c2A6f4e3B6Bd9c64CD2C969EFd1ECBe/logo.png",
+ "symbol" : "DSLA",
+ "name" : "DSLA",
+ "contractAddress" : "0x3aFfCCa64c2A6f4e3B6Bd9c64CD2C969EFd1ECBe",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC0bA369c8Db6eB3924965e5c4FD0b4C1B91e305F/logo.png",
+ "symbol" : "DUCK",
+ "name" : "DLP Duck Token",
+ "contractAddress" : "0xC0bA369c8Db6eB3924965e5c4FD0b4C1B91e305F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x961C8c0B1aaD0c0b10a51FeF6a867E3091BCef17/logo.png",
+ "symbol" : "DYP",
+ "name" : "DeFiYieldProtocol",
+ "contractAddress" : "0x961C8c0B1aaD0c0b10a51FeF6a867E3091BCef17",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x740623d2c797b7D8D1EcB98e9b4Afcf99Ec31E14/logo.png",
+ "symbol" : "DYT",
+ "name" : "DoYourTip",
+ "contractAddress" : "0x740623d2c797b7D8D1EcB98e9b4Afcf99Ec31E14",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "EOS",
+ "name" : "EOS",
+ "contractAddress" : "0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD/logo.png",
+ "symbol" : "ETH2x-FLI",
+ "name" : "ETH 2x Flexible Leverage Index",
+ "contractAddress" : "0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 2,
+ "symbol" : "EUR.AVM",
+ "name" : "EUR AVM",
+ "contractAddress" : "0xF01Cd2f1c9E42c509d309aC8C5b29B6dA8E64b1a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "EDG",
+ "name" : "Edgeless",
+ "contractAddress" : "0x08711D3B02C8758F2FB3ab4e80228418a7F8e39c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "EDO",
+ "name" : "Eidoo",
+ "contractAddress" : "0xced4e93198734ddaff8492d525bd258d49eb388e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x178c820f862B14f316509ec36b13123DA19A6054/logo.png",
+ "symbol" : "EWTB",
+ "name" : "Energy Web Token Bridged",
+ "contractAddress" : "0x178c820f862B14f316509ec36b13123DA19A6054",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c/logo.png",
+ "symbol" : "ENJ",
+ "name" : "Enjin Coin",
+ "contractAddress" : "0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa0246c9032bC3A600820415aE600c6388619A14D/logo.png",
+ "symbol" : "FARM",
+ "name" : "FARM Reward Token",
+ "contractAddress" : "0xa0246c9032bC3A600820415aE600c6388619A14D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xE5CAeF4Af8780E59Df925470b050Fb23C43CA68C/logo.png",
+ "symbol" : "FRM",
+ "name" : "Ferrum Network Token",
+ "contractAddress" : "0xE5CAeF4Af8780E59Df925470b050Fb23C43CA68C",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "FXC",
+ "name" : "Flexacoin",
+ "contractAddress" : "0x4a57E687b9126435a9B19E4A802113e266AdeBde",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8a40c222996f9F3431f63Bf80244C36822060f12/logo.png",
+ "symbol" : "FXF",
+ "name" : "Finxflo",
+ "contractAddress" : "0x8a40c222996f9F3431f63Bf80244C36822060f12",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x853d955aCEf822Db058eb8505911ED77F175b99e/logo.png",
+ "symbol" : "FRAX",
+ "name" : "Frax",
+ "contractAddress" : "0x853d955aCEf822Db058eb8505911ED77F175b99e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x29502fE4d233EF0b45C3647101Fa1252cE0634BD/logo.png",
+ "symbol" : "FROGE",
+ "name" : "Froge Finance",
+ "contractAddress" : "0x29502fE4d233EF0b45C3647101Fa1252cE0634BD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b/logo.png",
+ "symbol" : "FUN",
+ "name" : "FunFair",
+ "contractAddress" : "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8c15Ef5b4B21951d50E53E4fbdA8298FFAD25057/logo.png",
+ "symbol" : "FX",
+ "name" : "Function X",
+ "contractAddress" : "0x8c15Ef5b4B21951d50E53E4fbdA8298FFAD25057",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6BFf2fE249601ed0Db3a87424a2E923118BB0312/logo.png",
+ "symbol" : "FYZ",
+ "name" : "Fyooz",
+ "contractAddress" : "0x6BFf2fE249601ed0Db3a87424a2E923118BB0312",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8a854288a5976036A725879164Ca3e91d30c6A1B/logo.png",
+ "symbol" : "GET",
+ "name" : "GET",
+ "contractAddress" : "0x8a854288a5976036A725879164Ca3e91d30c6A1B",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "GNX",
+ "name" : "Genaro Network",
+ "contractAddress" : "0x6ec8a24cabdc339a06a172f8223ea557055adaa5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9F9c8ec3534c3cE16F928381372BfbFBFb9F4D24/logo.png",
+ "symbol" : "GLQ",
+ "name" : "GraphLinq",
+ "contractAddress" : "0x9F9c8ec3534c3cE16F928381372BfbFBFb9F4D24",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6810e776880C02933D47DB1b9fc05908e5386b96/logo.png",
+ "symbol" : "GNO",
+ "name" : "Gnosis Token",
+ "contractAddress" : "0x6810e776880C02933D47DB1b9fc05908e5386b96",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "GNT",
+ "name" : "Golem",
+ "contractAddress" : "0xa74476443119A942dE498590Fe1f2454d7D4aC0d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc3771d47E2Ab5A519E2917E61e23078d0C05Ed7f/logo.png",
+ "symbol" : "GTH",
+ "name" : "Gather",
+ "contractAddress" : "0xc3771d47E2Ab5A519E2917E61e23078d0C05Ed7f",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 2,
+ "symbol" : "GUSD",
+ "name" : "Gemini Dollar",
+ "contractAddress" : "0x056fd409e1d7a124bd7017459dfea2f387b6d5cd",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "GVT",
+ "name" : "Genesis Vision",
+ "contractAddress" : "0x103c3A209da59d3E7C4A89307e66521e081CFDF0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xbEa98c05eEAe2f3bC8c3565Db7551Eb738c8CCAb/logo.png",
+ "symbol" : "GYSR",
+ "name" : "Geyser",
+ "contractAddress" : "0xbEa98c05eEAe2f3bC8c3565Db7551Eb738c8CCAb",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "GD",
+ "name" : "Zilla GD",
+ "contractAddress" : "0xb4cebdb66b3a5183fd4764a25cad1fc109535016",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x09e64c2B61a5f1690Ee6fbeD9baf5D6990F8dFd0/logo.png",
+ "symbol" : "GRO",
+ "name" : "Growth",
+ "contractAddress" : "0x09e64c2B61a5f1690Ee6fbeD9baf5D6990F8dFd0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc944E90C64B2c07662A292be6244BDf05Cda44a7/logo.png",
+ "symbol" : "GRT",
+ "name" : "Graph Token",
+ "contractAddress" : "0xc944E90C64B2c07662A292be6244BDf05Cda44a7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 3,
+ "symbol" : "GUP",
+ "name" : "Matchpool",
+ "contractAddress" : "0xf7B098298f7C69Fc14610bf71d5e02c60792894C",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
+ "symbol" : "HEX",
+ "name" : "HEX",
+ "contractAddress" : "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "HPB",
+ "name" : "HPBCoin",
+ "contractAddress" : "0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x584bC13c7D411c00c01A62e8019472dE68768430/logo.png",
+ "symbol" : "HEGIC",
+ "name" : "Hegic",
+ "contractAddress" : "0x584bC13c7D411c00c01A62e8019472dE68768430",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xEEF9f339514298C6A857EfCfC1A762aF84438dEE/logo.png",
+ "symbol" : "HEZ",
+ "name" : "Hermez Network Token",
+ "contractAddress" : "0xEEF9f339514298C6A857EfCfC1A762aF84438dEE",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6c6EE5e31d828De241282B9606C8e98Ea48526E2/logo.png",
+ "symbol" : "HOT",
+ "name" : "HoloToken",
+ "contractAddress" : "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "HYDRO",
+ "name" : "Hydro",
+ "contractAddress" : "0xebbdf302c940c6bfd49c6b165f457fdb324649bc",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ICX",
+ "name" : "ICON",
+ "contractAddress" : "0xb5a5f22694352c15b00323844ad545abb2b11028",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ICN",
+ "name" : "ICONOMI",
+ "contractAddress" : "0x888666CA69E0f178DED6D75b5726Cee99A87D698",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xEBd9D99A3982d547C5Bb4DB7E3b1F9F14b67Eb83/logo.png",
+ "symbol" : "ID",
+ "name" : "Everest ID",
+ "contractAddress" : "0xEBd9D99A3982d547C5Bb4DB7E3b1F9F14b67Eb83",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x72630B1e3B42874bf335020Ba0249e3E9e47Bafc/logo.png",
+ "symbol" : "EPAN",
+ "name" : "Paypolitan Token",
+ "contractAddress" : "0x72630B1e3B42874bf335020Ba0249e3E9e47Bafc",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "GBX",
+ "name" : "Globitex",
+ "contractAddress" : "0x12fcd6463e66974cf7bbc24ffc4d40d6be458283",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xf6537FE0df7F0Cc0985Cf00792CC98249E73EFa0/logo.png",
+ "symbol" : "GIV",
+ "name" : "GIVToken",
+ "contractAddress" : "0xf6537FE0df7F0Cc0985Cf00792CC98249E73EFa0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xeEAA40B28A2d1b0B08f6f97bB1DD4B75316c6107/logo.png",
+ "symbol" : "GOVI",
+ "name" : "GOVI",
+ "contractAddress" : "0xeEAA40B28A2d1b0B08f6f97bB1DD4B75316c6107",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 5,
+ "symbol" : "GTO",
+ "name" : "Gifto",
+ "contractAddress" : "0xc5bbae50781be1669306b9e001eff57a2957b09d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "IOST",
+ "name" : "IOStoken",
+ "contractAddress" : "0xfa1a856cfa3409cfa145fa4e20eb270df3eb21ab",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ITC",
+ "name" : "IOT Chain",
+ "contractAddress" : "0x5e6b6d9abad9093fdc861ea1600eba1b355cd940",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30/logo.png",
+ "symbol" : "INJ",
+ "name" : "Injective Token",
+ "contractAddress" : "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4B1E80cAC91e2216EEb63e29B957eB91Ae9C2Be8/logo.png",
+ "symbol" : "JUP",
+ "name" : "Jupiter",
+ "contractAddress" : "0x4B1E80cAC91e2216EEb63e29B957eB91Ae9C2Be8",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x85Eee30c52B0b379b046Fb0F85F4f3Dc3009aFEC/logo.png",
+ "symbol" : "KEEP",
+ "name" : "KEEP Token",
+ "contractAddress" : "0x85Eee30c52B0b379b046Fb0F85F4f3Dc3009aFEC",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x16980b3B4a3f9D89E33311B5aa8f80303E5ca4F8/logo.png",
+ "symbol" : "KEX",
+ "name" : "KIRA Network",
+ "contractAddress" : "0x16980b3B4a3f9D89E33311B5aa8f80303E5ca4F8",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "KIN",
+ "name" : "Kin",
+ "contractAddress" : "0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xCbfef8fdd706cde6F208460f2Bf39Aa9c785F05D/logo.png",
+ "symbol" : "KINE",
+ "name" : "Kine Governance Token",
+ "contractAddress" : "0xCbfef8fdd706cde6F208460f2Bf39Aa9c785F05D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44/logo.png",
+ "symbol" : "KP3R",
+ "name" : "Keep3rV1",
+ "contractAddress" : "0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1dD80016e3d4ae146Ee2EBB484e8edD92dacC4ce/logo.png",
+ "symbol" : "LEAD",
+ "name" : "Lead Token",
+ "contractAddress" : "0x1dD80016e3d4ae146Ee2EBB484e8edD92dacC4ce",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0fF6ffcFDa92c53F615a4A75D982f399C989366b/logo.png",
+ "symbol" : "LAYER",
+ "name" : "Unilayer",
+ "contractAddress" : "0x0fF6ffcFDa92c53F615a4A75D982f399C989366b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD/logo.png",
+ "symbol" : "LRC",
+ "name" : "LoopringCoin V2",
+ "contractAddress" : "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3593D125a4f7849a1B059E64F4517A86Dd60c95d/logo.png",
+ "symbol" : "OM",
+ "name" : "MANTRA DAO",
+ "contractAddress" : "0x3593D125a4f7849a1B059E64F4517A86Dd60c95d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "MAN",
+ "name" : "MATRIX AI Network",
+ "contractAddress" : "0xe25bcec5d3801ce3a794079bf94adf1b8ccd802d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "MYST",
+ "name" : "Mysterium",
+ "contractAddress" : "0xa645264C5603E96c3b0B078cdab68733794B0A71",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NULS",
+ "name" : "Nuls",
+ "contractAddress" : "0xb91318f35bdb262e9423bc7c7c2a3a93dd93c92c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xfAd45E47083e4607302aa43c65fB3106F1cd7607/logo.png",
+ "symbol" : "HOGE",
+ "name" : "hoge.finance",
+ "contractAddress" : "0xfAd45E47083e4607302aa43c65fB3106F1cd7607",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "IXT",
+ "name" : "iXledger",
+ "contractAddress" : "0xfca47962d45adfdfd1ab2d972315db4ce7ccf094",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB9d99C33eA2d86EC5eC6b8A4dD816EBBA64404AF/logo.png",
+ "symbol" : "K21",
+ "name" : "k21.kanon.art",
+ "contractAddress" : "0xB9d99C33eA2d86EC5eC6b8A4dD816EBBA64404AF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0F5D2fB29fb7d3CFeE444a200298f468908cC942/logo.png",
+ "symbol" : "MANA",
+ "name" : "Decentraland MANA",
+ "contractAddress" : "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1494CA1F11D487c2bBe4543E90080AeBa4BA3C2b/logo.png",
+ "symbol" : "DPI",
+ "name" : "DefiPulse Index",
+ "contractAddress" : "0x1494CA1F11D487c2bBe4543E90080AeBa4BA3C2b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "ENG",
+ "name" : "Enigma",
+ "contractAddress" : "0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 16,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD6c67B93a7b248dF608a653d82a100556144c5DA/logo.png",
+ "symbol" : "EXNT",
+ "name" : "ExNetwork Community Token",
+ "contractAddress" : "0xD6c67B93a7b248dF608a653d82a100556144c5DA",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xfffffffFf15AbF397dA76f1dcc1A1604F45126DB/logo.png",
+ "symbol" : "FSW",
+ "name" : "FalconSwap Token",
+ "contractAddress" : "0xfffffffFf15AbF397dA76f1dcc1A1604F45126DB",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF4d861575ecC9493420A3f5a14F85B13f0b50EB3/logo.png",
+ "symbol" : "FCL",
+ "name" : "Fractal Protocol Token",
+ "contractAddress" : "0xF4d861575ecC9493420A3f5a14F85B13f0b50EB3",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x956F47F50A910163D8BF957Cf5846D573E7f87CA/logo.png",
+ "symbol" : "FEI",
+ "name" : "Fei USD",
+ "contractAddress" : "0x956F47F50A910163D8BF957Cf5846D573E7f87CA",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "FSN",
+ "name" : "Fusion",
+ "contractAddress" : "0xd0352a019e9ab9d757776f532377aaebd36fd541",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdd974D5C2e2928deA5F71b9825b8b646686BD200/logo.png",
+ "symbol" : "KNC",
+ "name" : "Kyber Network Crystal",
+ "contractAddress" : "0xdd974D5C2e2928deA5F71b9825b8b646686BD200",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x67B6D479c7bB412C54e03dCA8E1Bc6740ce6b99C/logo.png",
+ "symbol" : "KYL",
+ "name" : "Kylin Network",
+ "contractAddress" : "0x67B6D479c7bB412C54e03dCA8E1Bc6740ce6b99C",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaE697F994Fc5eBC000F8e22EbFfeE04612f98A0d/logo.png",
+ "symbol" : "LGCY",
+ "name" : "LGCY Network",
+ "contractAddress" : "0xaE697F994Fc5eBC000F8e22EbFfeE04612f98A0d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0a50C93c762fDD6E56D86215C24AaAD43aB629aa/logo.png",
+ "symbol" : "LGO",
+ "name" : "LGO Token",
+ "contractAddress" : "0x0a50C93c762fDD6E56D86215C24AaAD43aB629aa",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6149C26Cd2f7b5CCdb32029aF817123F6E37Df5B/logo.png",
+ "symbol" : "LPOOL",
+ "name" : "Launchpool token",
+ "contractAddress" : "0x6149C26Cd2f7b5CCdb32029aF817123F6E37Df5B",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x80fB784B7eD66730e8b1DBd9820aFD29931aab03/logo.png",
+ "symbol" : "LEND",
+ "name" : "Lend",
+ "contractAddress" : "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0000000000095413afC295d19EDeb1Ad7B71c952/logo.png",
+ "symbol" : "LON",
+ "name" : "Tokenlon",
+ "contractAddress" : "0x0000000000095413afC295d19EDeb1Ad7B71c952",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4de2573e27E648607B50e1Cfff921A33E4A34405/logo.png",
+ "symbol" : "LST",
+ "name" : "Lendroid Support Token",
+ "contractAddress" : "0x4de2573e27E648607B50e1Cfff921A33E4A34405",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LOOM",
+ "name" : "Loom",
+ "contractAddress" : "0xa4e8c3ec456107ea67d3075bf9e3df3a75823db0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3DB6Ba6ab6F95efed1a6E794caD492fAAabF294D/logo.png",
+ "symbol" : "LTO",
+ "name" : "LTO Network Token",
+ "contractAddress" : "0x3DB6Ba6ab6F95efed1a6E794caD492fAAabF294D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA8b919680258d369114910511cc87595aec0be6D/logo.png",
+ "symbol" : "LYXe",
+ "name" : "LUKSO Token",
+ "contractAddress" : "0xA8b919680258d369114910511cc87595aec0be6D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa393473d64d2F9F026B60b6Df7859A689715d092/logo.png",
+ "symbol" : "LTX",
+ "name" : "Lattice Token",
+ "contractAddress" : "0xa393473d64d2F9F026B60b6Df7859A689715d092",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LUN",
+ "name" : "Lunyr",
+ "contractAddress" : "0xfa05A73FfE78ef8f1a739473e462c54bae6567D9",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9/logo.png",
+ "symbol" : "LUNA",
+ "name" : "Wrapped LUNA Token",
+ "contractAddress" : "0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5f98805A4E8be255a32880FDeC7F6728C6568bA0/logo.png",
+ "symbol" : "LUSD",
+ "name" : "LUSD Stablecoin",
+ "contractAddress" : "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB4d930279552397bbA2ee473229f89Ec245bc365/logo.png",
+ "symbol" : "MAHA",
+ "name" : "MahaDAO",
+ "contractAddress" : "0xB4d930279552397bbA2ee473229f89Ec245bc365",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xfC98e825A2264D890F9a1e68ed50E1526abCcacD/logo.png",
+ "symbol" : "MCO2",
+ "name" : "Moss Carbon Credit",
+ "contractAddress" : "0xfC98e825A2264D890F9a1e68ed50E1526abCcacD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xAd4f86a25bbc20FfB751f2FAC312A0B4d8F88c64/logo.png",
+ "symbol" : "ROOM",
+ "name" : "OptionRoom Token",
+ "contractAddress" : "0xAd4f86a25bbc20FfB751f2FAC312A0B4d8F88c64",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5a666c7d92E5fA7Edcb6390E4efD6d0CDd69cF37/logo.png",
+ "symbol" : "MARSH",
+ "name" : "UnmarshalToken",
+ "contractAddress" : "0x5a666c7d92E5fA7Edcb6390E4efD6d0CDd69cF37",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x69af81e73A73B40adF4f3d4223Cd9b1ECE623074/logo.png",
+ "symbol" : "MASK",
+ "name" : "Mask Network",
+ "contractAddress" : "0x69af81e73A73B40adF4f3d4223Cd9b1ECE623074",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4e352cF164E64ADCBad318C3a1e222E9EBa4Ce42/logo.png",
+ "symbol" : "MCB",
+ "name" : "MCDEX Token",
+ "contractAddress" : "0x4e352cF164E64ADCBad318C3a1e222E9EBa4Ce42",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png",
+ "symbol" : "MATIC",
+ "name" : "Matic Token",
+ "contractAddress" : "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD5525D397898e5502075Ea5E830d8914f6F0affe/logo.png",
+ "symbol" : "MEME",
+ "name" : "MEME",
+ "contractAddress" : "0xD5525D397898e5502075Ea5E830d8914f6F0affe",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2/logo.png",
+ "symbol" : "MKR",
+ "name" : "MakerDAO",
+ "contractAddress" : "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2/logo.png",
+ "symbol" : "MTA",
+ "name" : "Meta",
+ "contractAddress" : "0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "MTL",
+ "name" : "Metal",
+ "contractAddress" : "0xF433089366899D83a9f26A773D59ec7eCF30355e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x72e364F2ABdC788b7E918bc238B21f109Cd634D7/logo.png",
+ "symbol" : "MVI",
+ "name" : "Metaverse Index",
+ "contractAddress" : "0x72e364F2ABdC788b7E918bc238B21f109Cd634D7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB26631c6dda06aD89B93C71400D25692de89c068/logo.png",
+ "symbol" : "MINDS",
+ "name" : "Minds",
+ "contractAddress" : "0xB26631c6dda06aD89B93C71400D25692de89c068",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xEA1ea0972fa092dd463f2968F9bB51Cc4c981D71/logo.png",
+ "symbol" : "MOD",
+ "name" : "Modefi",
+ "contractAddress" : "0xEA1ea0972fa092dd463f2968F9bB51Cc4c981D71",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "MCO",
+ "name" : "Monaco",
+ "contractAddress" : "0xB63B606Ac810a52cCa15e44bB630fd42D8d1d83d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NAS",
+ "name" : "Nebulas",
+ "contractAddress" : "0x5d65D971895Edc438f465c17DB6992698a52318D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a/logo.png",
+ "symbol" : "TKN",
+ "name" : "Monolith TKN",
+ "contractAddress" : "0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa8c8CfB141A3bB59FEA1E2ea6B79b5ECBCD7b6ca/logo.png",
+ "symbol" : "NOIA",
+ "name" : "NOIA Token",
+ "contractAddress" : "0xa8c8CfB141A3bB59FEA1E2ea6B79b5ECBCD7b6ca",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206/logo.png",
+ "symbol" : "NEXO",
+ "name" : "Nexo",
+ "contractAddress" : "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1cBb83EbcD552D5EBf8131eF8c9CD9d9BAB342bC/logo.png",
+ "symbol" : "NFY",
+ "name" : "Non-Fungible Yearn",
+ "contractAddress" : "0x1cBb83EbcD552D5EBf8131eF8c9CD9d9BAB342bC",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NMR",
+ "name" : "Numeraire",
+ "contractAddress" : "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x967da4048cD07aB37855c090aAF366e4ce1b9F48/logo.png",
+ "symbol" : "OCEAN",
+ "name" : "Ocean Protocol",
+ "contractAddress" : "0x967da4048cD07aB37855c090aAF366e4ce1b9F48",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets//logo.png",
+ "symbol" : "OMG",
+ "name" : "OMG Network",
+ "contractAddress" : "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4c11249814f11b9346808179Cf06e71ac328c1b5/logo.png",
+ "symbol" : "ORAI",
+ "name" : "Oraichain Token",
+ "contractAddress" : "0x4c11249814f11b9346808179Cf06e71ac328c1b5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8207c1FfC5B6804F6024322CcF34F29c3541Ae26/logo.png",
+ "symbol" : "OGN",
+ "name" : "OriginToken",
+ "contractAddress" : "0x8207c1FfC5B6804F6024322CcF34F29c3541Ae26",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0258F474786DdFd37ABCE6df6BBb1Dd5dfC4434a/logo.png",
+ "symbol" : "ORN",
+ "name" : "Orion Protocol",
+ "contractAddress" : "0x0258F474786DdFd37ABCE6df6BBb1Dd5dfC4434a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "OST",
+ "name" : "OST",
+ "contractAddress" : "0x2C4e8f2D746113d0696cE89B35F0d8bF88E0AEcA",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x21BfBDa47A0B4B5b1248c767Ee49F7caA9B23697/logo.png",
+ "symbol" : "OVR",
+ "name" : "OVR",
+ "contractAddress" : "0x21BfBDa47A0B4B5b1248c767Ee49F7caA9B23697",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4575f41308EC1483f3d399aa9a2826d74Da13Deb/logo.png",
+ "symbol" : "OXT",
+ "name" : "Orchid",
+ "contractAddress" : "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1614F18Fc94f47967A3Fbe5FfcD46d4e7Da3D787/logo.png",
+ "symbol" : "PAID",
+ "name" : "PAID Network",
+ "contractAddress" : "0x1614F18Fc94f47967A3Fbe5FfcD46d4e7Da3D787",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8c8687fC965593DFb2F0b4EAeFD55E9D8df348df/logo.png",
+ "symbol" : "PAID",
+ "name" : "PAID Network",
+ "contractAddress" : "0x8c8687fC965593DFb2F0b4EAeFD55E9D8df348df",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x45804880De22913dAFE09f4980848ECE6EcbAf78/logo.png",
+ "symbol" : "PAXG",
+ "name" : "Paxos Gold",
+ "contractAddress" : "0x45804880De22913dAFE09f4980848ECE6EcbAf78",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PAX",
+ "name" : "Paxos Standard",
+ "contractAddress" : "0x8e870d67f660d95d5be530380d0ec0bd388289e1",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xbC396689893D065F41bc2C6EcbeE5e0085233447/logo.png",
+ "symbol" : "PERP",
+ "name" : "Perpetual",
+ "contractAddress" : "0xbC396689893D065F41bc2C6EcbeE5e0085233447",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x429881672B9AE42b8EbA0E26cD9C73711b891Ca5/logo.png",
+ "symbol" : "PICKLE",
+ "name" : "PickleToken",
+ "contractAddress" : "0x429881672B9AE42b8EbA0E26cD9C73711b891Ca5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d/logo.png",
+ "symbol" : "PNK",
+ "name" : "Pinakion",
+ "contractAddress" : "0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "POE",
+ "name" : "Po.et",
+ "contractAddress" : "0x0e0989b1f9b8a38983c2ba8053269ca62ec9b195",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PAL",
+ "name" : "PolicyPal Network",
+ "contractAddress" : "0xfeDAE5642668f8636A11987Ff386bfd215F942EE",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8B39B70E39Aa811b69365398e0aACe9bee238AEb/logo.png",
+ "symbol" : "PKF",
+ "name" : "PolkaFoundry",
+ "contractAddress" : "0x8B39B70E39Aa811b69365398e0aACe9bee238AEb",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD478161C952357F05f0292B56012Cd8457F1cfbF/logo.png",
+ "symbol" : "POLK",
+ "name" : "Polkamarkets",
+ "contractAddress" : "0xD478161C952357F05f0292B56012Cd8457F1cfbF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x83e6f1E41cdd28eAcEB20Cb649155049Fac3D5Aa/logo.png",
+ "symbol" : "POLS",
+ "name" : "PolkastarterToken",
+ "contractAddress" : "0x83e6f1E41cdd28eAcEB20Cb649155049Fac3D5Aa",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PRL",
+ "name" : "Oyster Pearl",
+ "contractAddress" : "0x1844b21593262668b7248d0f57a220caaba46ab9",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x362bc847A3a9637d3af6624EeC853618a43ed7D2/logo.png",
+ "symbol" : "PRQ",
+ "name" : "Parsiq Token",
+ "contractAddress" : "0x362bc847A3a9637d3af6624EeC853618a43ed7D2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "PTOY",
+ "name" : "Patientory",
+ "contractAddress" : "0x8Ae4BF2C33a8e667de34B54938B0ccD03Eb8CC06",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1796ae0b0fa4862485106a0de9b654eFE301D0b2/logo.png",
+ "symbol" : "PMON",
+ "name" : "Polkamon",
+ "contractAddress" : "0x1796ae0b0fa4862485106a0de9b654eFE301D0b2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "POLY",
+ "name" : "Polymath",
+ "contractAddress" : "0x9992eC3cF6A55b00978cdDF2b27BC6882d88D1eC",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "PPT",
+ "name" : "Populous",
+ "contractAddress" : "0xd4fa1460F537bb9085d22C7bcCB5DD450Ef28e3a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "POWR",
+ "name" : "Power Ledger",
+ "contractAddress" : "0x595832f8fc6bf59c85c527fec3740a1b7a361269",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC57d533c50bC22247d49a368880fb49a1caA39F7/logo.png",
+ "symbol" : "PTF",
+ "name" : "PowerTrade Fuel Token",
+ "contractAddress" : "0xC57d533c50bC22247d49a368880fb49a1caA39F7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0CDF9acd87E940837ff21BB40c9fd55F68bba059/logo.png",
+ "symbol" : "MINT",
+ "name" : "Public Mint",
+ "contractAddress" : "0x0CDF9acd87E940837ff21BB40c9fd55F68bba059",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "QASH",
+ "name" : "QASH",
+ "contractAddress" : "0x618e75ac90b12c6049ba3b27f5d5f8651b0037f6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4a220E6096B25EADb88358cb44068A3248254675/logo.png",
+ "symbol" : "QNT",
+ "name" : "Quant",
+ "contractAddress" : "0x4a220E6096B25EADb88358cb44068A3248254675",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6e0daDE58D2d89eBBe7aFc384e3E4f15b70b14D8/logo.png",
+ "symbol" : "QRX",
+ "name" : "QuiverX",
+ "contractAddress" : "0x6e0daDE58D2d89eBBe7aFc384e3E4f15b70b14D8",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x33D0568941C0C64ff7e0FB4fbA0B11BD37deEd9f/logo.png",
+ "symbol" : "RAMP",
+ "name" : "RAMP DEFI",
+ "contractAddress" : "0x33D0568941C0C64ff7e0FB4fbA0B11BD37deEd9f",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "RC1U03",
+ "name" : "RentalCoin 1.0",
+ "contractAddress" : "0x7384db1aaa06364d1e6e67f5434391d3eea2a038",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RDN",
+ "name" : "Raiden Network",
+ "contractAddress" : "0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xf1f955016EcbCd7321c7266BccFB96c68ea5E49b/logo.png",
+ "symbol" : "RLY",
+ "name" : "Rally",
+ "contractAddress" : "0xf1f955016EcbCd7321c7266BccFB96c68ea5E49b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA4EED63db85311E22dF4473f87CcfC3DaDCFA3E3/logo.png",
+ "symbol" : "RBC",
+ "name" : "Rubic",
+ "contractAddress" : "0xA4EED63db85311E22dF4473f87CcfC3DaDCFA3E3",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8f8221aFbB33998d8584A2B05749bA73c37a938a/logo.png",
+ "symbol" : "REQ",
+ "name" : "Request Token",
+ "contractAddress" : "0x8f8221aFbB33998d8584A2B05749bA73c37a938a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8762db106B2c2A0bccB3A80d1Ed41273552616E8/logo.png",
+ "symbol" : "RSR",
+ "name" : "Reserve Rights",
+ "contractAddress" : "0x8762db106B2c2A0bccB3A80d1Ed41273552616E8",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7dE91B204C1C737bcEe6F000AAA6569Cf7061cb7/logo.png",
+ "symbol" : "XRT",
+ "name" : "Robonomics",
+ "contractAddress" : "0x7dE91B204C1C737bcEe6F000AAA6569Cf7061cb7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xCb5f72d37685C3D5aD0bB5F982443BC8FcdF570E/logo.png",
+ "symbol" : "ROOT",
+ "name" : "RootKit",
+ "contractAddress" : "0xCb5f72d37685C3D5aD0bB5F982443BC8FcdF570E",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3155BA85D5F96b2d030a4966AF206230e46849cb/logo.png",
+ "symbol" : "RUNE",
+ "name" : "THORChain ETH.RUNE",
+ "contractAddress" : "0x3155BA85D5F96b2d030a4966AF206230e46849cb",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SAN",
+ "name" : "SAN",
+ "contractAddress" : "0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE/logo.png",
+ "symbol" : "SHIB",
+ "name" : "SHIBA INU",
+ "contractAddress" : "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x72e9D9038cE484EE986FEa183f8d8Df93f9aDA13/logo.png",
+ "symbol" : "SMARTCREDIT",
+ "name" : "SMARTCREDIT Token",
+ "contractAddress" : "0x72e9D9038cE484EE986FEa183f8d8Df93f9aDA13",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SRN",
+ "name" : "SIRIN",
+ "contractAddress" : "0x68d57c9a1C35f63E2c83eE8e49A64e9d70528D25",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SNM",
+ "name" : "SONM",
+ "contractAddress" : "0x983F6d60db79ea8cA4eB9968C6aFf8cfA04B3c63",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets//logo.png",
+ "symbol" : "SNX",
+ "name" : "Synthetix Network Token",
+ "contractAddress" : "0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x23B608675a2B2fB1890d3ABBd85c5775c51691d5/logo.png",
+ "symbol" : "SOCKS",
+ "name" : "Unisocks Edition 0",
+ "contractAddress" : "0x23B608675a2B2fB1890d3ABBd85c5775c51691d5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0Ae055097C6d159879521C384F1D2123D1f195e6/logo.png",
+ "symbol" : "STAKE",
+ "name" : "xDai",
+ "contractAddress" : "0x0Ae055097C6d159879521C384F1D2123D1f195e6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SUB",
+ "name" : "Substratum",
+ "contractAddress" : "0x8D75959f1E61EC2571aa72798237101F084DE63a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xEa319e87Cf06203DAe107Dd8E5672175e3Ee976c/logo.png",
+ "symbol" : "SURF",
+ "name" : "SURF.Finance",
+ "contractAddress" : "0xEa319e87Cf06203DAe107Dd8E5672175e3Ee976c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xCC4304A31d09258b0029eA7FE63d032f52e44EFe/logo.png",
+ "symbol" : "SWAP",
+ "name" : "TrustSwap Token",
+ "contractAddress" : "0xCC4304A31d09258b0029eA7FE63d032f52e44EFe",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB6ff96B8A8d214544Ca0dBc9B33f7AD6503eFD32/logo.png",
+ "symbol" : "SYNC",
+ "name" : "SYNC",
+ "contractAddress" : "0xB6ff96B8A8d214544Ca0dBc9B33f7AD6503eFD32",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB987D48Ed8f2C468D52D6405624EADBa5e76d723/logo.png",
+ "symbol" : "STBZ",
+ "name" : "Stabilize Token",
+ "contractAddress" : "0xB987D48Ed8f2C468D52D6405624EADBa5e76d723",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SNT",
+ "name" : "Status",
+ "contractAddress" : "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SEED",
+ "name" : "Superbloom Network",
+ "contractAddress" : "0x4e7bd88e3996f48e2a24d15e37ca4c02b4d134d2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1C9922314ED1415c95b9FD453c3818fd41867d0B/logo.png",
+ "symbol" : "TOWER",
+ "name" : "TOWER",
+ "contractAddress" : "0x1C9922314ED1415c95b9FD453c3818fd41867d0B",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xd084B83C305daFD76AE3E1b4E1F1fe2eCcCb3988/logo.png",
+ "symbol" : "TVK",
+ "name" : "Terra Virtua Kolect",
+ "contractAddress" : "0xd084B83C305daFD76AE3E1b4E1F1fe2eCcCb3988",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaA7a9CA87d3694B5755f213B5D04094b8d0F0A6F/logo.png",
+ "symbol" : "TRAC",
+ "name" : "Trace Token",
+ "contractAddress" : "0xaA7a9CA87d3694B5755f213B5D04094b8d0F0A6F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6F87D756DAf0503d08Eb8993686c7Fc01Dc44fB1/logo.png",
+ "symbol" : "TRADE",
+ "name" : "UniTrade",
+ "contractAddress" : "0x6F87D756DAf0503d08Eb8993686c7Fc01Dc44fB1",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B/logo.png",
+ "symbol" : "TRIBE",
+ "name" : "Tribe",
+ "contractAddress" : "0xc7283b66Eb1EB5FB86327f08e1B5816b0720212B",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0000000000085d4780B73119b644AE5ecd22b376/logo.png",
+ "symbol" : "TUSD",
+ "name" : "TrueUSD",
+ "contractAddress" : "0x0000000000085d4780B73119b644AE5ecd22b376",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4C19596f5aAfF459fA38B0f7eD92F11AE6543784/logo.png",
+ "symbol" : "TRU",
+ "name" : "TrustToken",
+ "contractAddress" : "0x4C19596f5aAfF459fA38B0f7eD92F11AE6543784",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8400D94A5cb0fa0D041a3788e395285d61c9ee5e/logo.png",
+ "symbol" : "UBT",
+ "name" : "Unibright",
+ "contractAddress" : "0x8400D94A5cb0fa0D041a3788e395285d61c9ee5e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "UCASH",
+ "name" : "UCASH",
+ "contractAddress" : "0x92e52a1a235d9a103d970901066ce910aacefd37",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828/logo.png",
+ "symbol" : "UMA",
+ "name" : "UMA Voting Token v1",
+ "contractAddress" : "0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6fC13EACE26590B80cCCAB1ba5d51890577D83B2/logo.png",
+ "symbol" : "UMB",
+ "name" : "Umbrella",
+ "contractAddress" : "0x6fC13EACE26590B80cCCAB1ba5d51890577D83B2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9Ed8e7C9604790F7Ec589F99b94361d8AAB64E5E/logo.png",
+ "symbol" : "UNISTAKE",
+ "name" : "Unistake",
+ "contractAddress" : "0x9Ed8e7C9604790F7Ec589F99b94361d8AAB64E5E",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x10Be9a8dAe441d276a5027936c3aADEd2d82bC15/logo.png",
+ "symbol" : "UMX",
+ "name" : "https://unimex.network/",
+ "contractAddress" : "0x10Be9a8dAe441d276a5027936c3aADEd2d82bC15",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x226f7b842E0F0120b7E194D05432b3fd14773a9D/logo.png",
+ "symbol" : "UNN",
+ "name" : "UNION Protocol Governance Token",
+ "contractAddress" : "0x226f7b842E0F0120b7E194D05432b3fd14773a9D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "VIB",
+ "name" : "Viberate",
+ "contractAddress" : "0x2C974B2d0BA1716E644c1FC59982a89DDD2fF724",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3D3D35bb9bEC23b06Ca00fe472b50E7A4c692C30/logo.png",
+ "symbol" : "VIDYA",
+ "name" : "Vidya",
+ "contractAddress" : "0x3D3D35bb9bEC23b06Ca00fe472b50E7A4c692C30",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF938424F7210f31dF2Aee3011291b658f872e91e/logo.png",
+ "symbol" : "VISR",
+ "name" : "VISOR",
+ "contractAddress" : "0xF938424F7210f31dF2Aee3011291b658f872e91e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "WAX",
+ "name" : "WAX",
+ "contractAddress" : "0x39Bb259F66E1C59d5ABEF88375979b4D20D98022",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7a2Bc711E19ba6aff6cE8246C546E8c4B4944DFD/logo.png",
+ "symbol" : "WAXE",
+ "name" : "WAX Economic Token",
+ "contractAddress" : "0x7a2Bc711E19ba6aff6cE8246C546E8c4B4944DFD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "WINGS",
+ "name" : "Wings",
+ "contractAddress" : "0x667088b212ce3d06a1b553a7221E1fD19000d9aF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x66a0f676479Cee1d7373f3DC2e2952778BfF5bd6/logo.png",
+ "symbol" : "WISE",
+ "name" : "Wise Token",
+ "contractAddress" : "0x66a0f676479Cee1d7373f3DC2e2952778BfF5bd6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xBF494F02EE3FdE1F20BEE6242bCe2d1ED0c15e47/logo.png",
+ "symbol" : "WORLD",
+ "name" : "World Token",
+ "contractAddress" : "0xBF494F02EE3FdE1F20BEE6242bCe2d1ED0c15e47",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "WRC",
+ "name" : "Worldcore",
+ "contractAddress" : "0x72adadb447784dd7ab1f472467750fc485e4cb2d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png",
+ "symbol" : "WBTC",
+ "name" : "Wrapped BTC",
+ "contractAddress" : "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
+ "symbol" : "WETH",
+ "name" : "Wrapped Ethereum",
+ "contractAddress" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x09a3EcAFa817268f77BE1283176B946C4ff2E608/logo.png",
+ "symbol" : "MIR",
+ "name" : "Wrapped MIR Token",
+ "contractAddress" : "0x09a3EcAFa817268f77BE1283176B946C4ff2E608",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8888801aF4d980682e47f1A9036e589479e835C5/logo.png",
+ "symbol" : "MPH",
+ "name" : "88mph.app",
+ "contractAddress" : "0x8888801aF4d980682e47f1A9036e589479e835C5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9469D013805bFfB7D3DEBe5E7839237e535ec483/logo.png",
+ "symbol" : "RING",
+ "name" : "Evolution Land Global Token",
+ "contractAddress" : "0x9469D013805bFfB7D3DEBe5E7839237e535ec483",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "XJP",
+ "name" : "Digital JPY",
+ "contractAddress" : "0x39689fE671C01fcE173395f6BC45D4C332026666",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF063fE1aB7a291c5d06a86e14730b00BF24cB589/logo.png",
+ "symbol" : "SALE",
+ "name" : "DxSale.Network",
+ "contractAddress" : "0xF063fE1aB7a291c5d06a86e14730b00BF24cB589",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "XUC",
+ "name" : "Exchange Union",
+ "contractAddress" : "0xc324a2f6b05880503444451b8b27e6f9e63287cb",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xee573a945B01B788B9287CE062A0CFC15bE9fd86/logo.png",
+ "symbol" : "XED",
+ "name" : "Exeedme",
+ "contractAddress" : "0xee573a945B01B788B9287CE062A0CFC15bE9fd86",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2e1E15C44Ffe4Df6a0cb7371CD00d5028e571d14/logo.png",
+ "symbol" : "MTLX",
+ "name" : "Mettalex",
+ "contractAddress" : "0x2e1E15C44Ffe4Df6a0cb7371CD00d5028e571d14",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NCASH",
+ "name" : "Nucleus Vision",
+ "contractAddress" : "0x809826cceab68c387726af962713b64cb5cb3cca",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA15C7Ebe1f07CaF6bFF097D8a589fb8AC49Ae5B3/logo.png",
+ "symbol" : "NPXS",
+ "name" : "Pundi X Token",
+ "contractAddress" : "0xA15C7Ebe1f07CaF6bFF097D8a589fb8AC49Ae5B3",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4fE83213D56308330EC302a8BD641f1d0113A4Cc/logo.png",
+ "symbol" : "NU",
+ "name" : "NuCypher",
+ "contractAddress" : "0x4fE83213D56308330EC302a8BD641f1d0113A4Cc",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "QRL",
+ "name" : "QRL",
+ "contractAddress" : "0x697beac28B09E122C4332D163985e8a73121b97F",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PAY",
+ "name" : "TenX",
+ "contractAddress" : "0xB97048628DB6B661D4C2aA833e95Dbe1A905B280",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xe3818504c1B32bF1557b16C238B2E01Fd3149C17/logo.png",
+ "symbol" : "PLR",
+ "name" : "PILLAR",
+ "contractAddress" : "0xe3818504c1B32bF1557b16C238B2E01Fd3149C17",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PMTN",
+ "name" : "Peer Mountain",
+ "contractAddress" : "0xe91df2bb9bccd18c45f01843cab88768d64f2d32",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5BEfBB272290dD5b8521D4a938f6c4757742c430/logo.png",
+ "symbol" : "XFI",
+ "name" : "Xfinance",
+ "contractAddress" : "0x5BEfBB272290dD5b8521D4a938f6c4757742c430",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0f7F961648aE6Db43C75663aC7E5414Eb79b5704/logo.png",
+ "symbol" : "XIO",
+ "name" : "XIO Network",
+ "contractAddress" : "0x0f7F961648aE6Db43C75663aC7E5414Eb79b5704",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+
+ },
+ {
+ "decimalCount" : 9,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x607F4C5BB672230e8672085532f7e901544a7375/logo.png",
+ "symbol" : "RLC",
+ "name" : "iExec RLC",
+ "contractAddress" : "0x607F4C5BB672230e8672085532f7e901544a7375",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD/logo.png",
+ "symbol" : "PNT",
+ "name" : "pNetwork Token",
+ "contractAddress" : "0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x69A95185ee2a045CDC4bCd1b1Df10710395e4e23/logo.png",
+ "symbol" : "POOLZ",
+ "name" : "$Poolz Finance",
+ "contractAddress" : "0x69A95185ee2a045CDC4bCd1b1Df10710395e4e23",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ },
+ "symbol" : "QCX",
+ "name" : "QuickX Protocol",
+ "contractAddress" : "0xf9e5af7b42d31d51677c75bbbd37c1986ec79aee",
+ "customIcon" : "qcx"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "QSP",
+ "name" : "Quantstamp",
+ "contractAddress" : "0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919/logo.png",
+ "symbol" : "RAI",
+ "name" : "Rai Reflex Index",
+ "contractAddress" : "0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xFca59Cd816aB1eaD66534D82bc21E7515cE441CF/logo.png",
+ "symbol" : "RARI",
+ "name" : "Rarible",
+ "contractAddress" : "0xFca59Cd816aB1eaD66534D82bc21E7515cE441CF",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x50DE6856358Cc35f3A9a57eAAA34BD4cB707d2cd/logo.png",
+ "symbol" : "RAZOR",
+ "name" : "RAZOR",
+ "contractAddress" : "0x50DE6856358Cc35f3A9a57eAAA34BD4cB707d2cd",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RCN",
+ "name" : "Ripio Credit",
+ "contractAddress" : "0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xFE3E6a25e6b192A42a44ecDDCd13796471735ACf/logo.png",
+ "symbol" : "REEF",
+ "name" : "Reef.finance",
+ "contractAddress" : "0xFE3E6a25e6b192A42a44ecDDCd13796471735ACf",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x408e41876cCCDC0F92210600ef50372656052a38/logo.png",
+ "symbol" : "REN",
+ "name" : "Republic Token",
+ "contractAddress" : "0x408e41876cCCDC0F92210600ef50372656052a38",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1985365e9f78359a9B6AD760e32412f4a445E862/logo.png",
+ "symbol" : "REP",
+ "name" : "Reputation",
+ "contractAddress" : "0x1985365e9f78359a9B6AD760e32412f4a445E862",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x557B933a7C2c45672B610F8954A3deB39a51A8Ca/logo.png",
+ "symbol" : "REVV",
+ "name" : "REVV",
+ "contractAddress" : "0x557B933a7C2c45672B610F8954A3deB39a51A8Ca",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa1d6Df714F91DeBF4e0802A542E13067f31b8262/logo.png",
+ "symbol" : "RFOX",
+ "name" : "RFOX",
+ "contractAddress" : "0xa1d6Df714F91DeBF4e0802A542E13067f31b8262",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RUFF",
+ "name" : "RUFF",
+ "contractAddress" : "0xf278c1ca969095ffddded020290cf8b5c424ace2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xf21661D0D1d76d3ECb8e1B9F1c923DBfffAe4097/logo.png",
+ "symbol" : "RIO",
+ "name" : "Realio Network",
+ "contractAddress" : "0xf21661D0D1d76d3ECb8e1B9F1c923DBfffAe4097",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RCC",
+ "name" : "Reality Clash",
+ "contractAddress" : "0x9b6443b0fb9c241a7fdac375595cea13e6b7807a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x221657776846890989a759BA2973e427DfF5C9bB/logo.png",
+ "symbol" : "REPv2",
+ "name" : "Reputation",
+ "contractAddress" : "0x221657776846890989a759BA2973e427DfF5C9bB",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "REV",
+ "name" : "Revain",
+ "contractAddress" : "0x48f775efbe4f5ece6e0df2f7b5932df56823b990",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xaf9f549774ecEDbD0966C52f250aCc548D3F36E5/logo.png",
+ "symbol" : "RFuel",
+ "name" : "Rio Fuel Token",
+ "contractAddress" : "0xaf9f549774ecEDbD0966C52f250aCc548D3F36E5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RVT",
+ "name" : "Rivetz",
+ "contractAddress" : "0x3d1ba9be9f66b8ee101911bc36d3fb562eac2244",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB4EFd85c19999D84251304bDA99E90B92300Bd93/logo.png",
+ "symbol" : "RPL",
+ "name" : "Rocket Pool",
+ "contractAddress" : "0xB4EFd85c19999D84251304bDA99E90B92300Bd93",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3845badAde8e6dFF049820680d1F14bD3903a5d0/logo.png",
+ "symbol" : "SAND",
+ "name" : "SAND",
+ "contractAddress" : "0x3845badAde8e6dFF049820680d1F14bD3903a5d0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "SENT",
+ "name" : "SENTinel",
+ "contractAddress" : "0xa44e5137293e855b1b7bc7e2c6f8cd796ffcb037",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xD23Ac27148aF6A2f339BD82D0e3CFF380b5093de/logo.png",
+ "symbol" : "SI",
+ "name" : "SIREN",
+ "contractAddress" : "0xD23Ac27148aF6A2f339BD82D0e3CFF380b5093de",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STK",
+ "name" : "STK",
+ "contractAddress" : "0xae73b38d1c9a8b274127ec30160a4927c4d71824",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SAI",
+ "name" : "Sai",
+ "contractAddress" : "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "SALT",
+ "name" : "SALT",
+ "contractAddress" : "0x4156D3342D5c385a87D264F90653733592000581",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9B02dD390a603Add5c07f9fd9175b7DABE8D63B7/logo.png",
+ "symbol" : "SPI",
+ "name" : "Shopping.io",
+ "contractAddress" : "0x9B02dD390a603Add5c07f9fd9175b7DABE8D63B7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "SNGLS",
+ "name" : "Singular DTV",
+ "contractAddress" : "0xaeC2E87E0A235266D9C5ADc9DEb4b2E29b54D009",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x37236CD05b34Cc79d3715AF2383E96dd7443dCF1/logo.png",
+ "symbol" : "SLP",
+ "name" : "Small Love Potion",
+ "contractAddress" : "0x37236CD05b34Cc79d3715AF2383E96dd7443dCF1",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x06A01a4d579479Dd5D884EBf61A31727A3d8D442/logo.png",
+ "symbol" : "Skey",
+ "name" : "SmartKey",
+ "contractAddress" : "0x06A01a4d579479Dd5D884EBf61A31727A3d8D442",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0488401c3F535193Fa8Df029d9fFe615A06E74E6/logo.png",
+ "symbol" : "SRK",
+ "name" : "SparkPoint",
+ "contractAddress" : "0x0488401c3F535193Fa8Df029d9fFe615A06E74E6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xb753428af26E81097e7fD17f40c88aaA3E04902c/logo.png",
+ "symbol" : "SFI",
+ "name" : "Spice",
+ "contractAddress" : "0xb753428af26E81097e7fD17f40c88aaA3E04902c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x00c83aeCC790e8a4453e5dD3B0B4b3680501a7A7/logo.png",
+ "symbol" : "SKL",
+ "name" : "SKALE",
+ "contractAddress" : "0x00c83aeCC790e8a4453e5dD3B0B4b3680501a7A7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x40FD72257597aA14C7231A7B1aaa29Fce868F677/logo.png",
+ "symbol" : "XOR",
+ "name" : "Sora",
+ "contractAddress" : "0x40FD72257597aA14C7231A7B1aaa29Fce868F677",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "STORJ",
+ "name" : "Storj",
+ "contractAddress" : "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xbE9375C6a420D2eEB258962efB95551A5b722803/logo.png",
+ "symbol" : "STMX",
+ "name" : "StormX",
+ "contractAddress" : "0xbE9375C6a420D2eEB258962efB95551A5b722803",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STQ",
+ "name" : "Storiqa",
+ "contractAddress" : "0x5c3a228510d246b78a3765c20221cbf3082b44a4",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STORM",
+ "name" : "Storm",
+ "contractAddress" : "0xD0a4b8946Cb52f0661273bfbC6fD0E0C75Fc6433",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x990f341946A3fdB507aE7e52d17851B87168017c/logo.png",
+ "symbol" : "STRONG",
+ "name" : "Strong",
+ "contractAddress" : "0x990f341946A3fdB507aE7e52d17851B87168017c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STX",
+ "name" : "Stox",
+ "contractAddress" : "0x006BeA43Baa3f7A6f765F14f10A1a1b08334EF45",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SNC",
+ "name" : "SunContract",
+ "contractAddress" : "0xF4134146AF2d511Dd5EA8cDB1C4AC88C57D60404",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xe53EC727dbDEB9E2d5456c3be40cFF031AB40A55/logo.png",
+ "symbol" : "SUPER",
+ "name" : "SuperFarm",
+ "contractAddress" : "0xe53EC727dbDEB9E2d5456c3be40cFF031AB40A55",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B3595068778DD592e39A122f4f5a5cF09C90fE2/logo.png",
+ "symbol" : "SUSHI",
+ "name" : "SushiSwap",
+ "contractAddress" : "0x6B3595068778DD592e39A122f4f5a5cF09C90fE2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9/logo.png",
+ "symbol" : "SXP",
+ "name" : "Swipe",
+ "contractAddress" : "0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9f7229aF0c4b9740e207Ea283b9094983f78ba04/logo.png",
+ "symbol" : "TAD",
+ "name" : "Tadpole",
+ "contractAddress" : "0x9f7229aF0c4b9740e207Ea283b9094983f78ba04",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa1eD0364D53394209D61aE8bfdb8Ff50484D8c91/logo.png",
+ "symbol" : "TBC",
+ "name" : "TeraBlock Token",
+ "contractAddress" : "0xa1eD0364D53394209D61aE8bfdb8Ff50484D8c91",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "THETA",
+ "name" : "Theta Token",
+ "contractAddress" : "0x3883f5e181fccaf8410fa61e12b59bad963fb645",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x77777FeDdddFfC19Ff86DB637967013e6C6A116C/logo.png",
+ "symbol" : "TORN",
+ "name" : "TornadoCash",
+ "contractAddress" : "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "TRX",
+ "name" : "TRON",
+ "contractAddress" : "0xf230b790e05390fc8295f4d3f60332c93bed42e2",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0202Be363B8a4820f3F4DE7FaF5224fF05943AB1/logo.png",
+ "symbol" : "UFT",
+ "name" : "UniLend Finance Token",
+ "contractAddress" : "0x0202Be363B8a4820f3F4DE7FaF5224fF05943AB1",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ "symbol" : "USDC",
+ "name" : "USDC",
+ "contractAddress" : "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STU",
+ "name" : "bitJob",
+ "contractAddress" : "0x0371a82e4a9d0a4312f3ee2ac9c6958512891372",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xBa21Ef4c9f433Ede00badEFcC2754B8E74bd538A/logo.png",
+ "symbol" : "SWFL",
+ "name" : "Swapfolio",
+ "contractAddress" : "0xBa21Ef4c9f433Ede00badEFcC2754B8E74bd538A",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa/logo.png",
+ "symbol" : "TBTC",
+ "name" : "tBTC",
+ "contractAddress" : "0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x57Ab1ec28D129707052df4dF418D58a2D46d5f51/logo.png",
+ "symbol" : "sUSD",
+ "name" : "Synth sUSD",
+ "contractAddress" : "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 2,
+ "symbol" : "TEL",
+ "name" : "Telcoin",
+ "contractAddress" : "0x467bccd9d29f223bce8043b84e8c8b282827790f",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xDcB01cc464238396E213a6fDd933E36796eAfF9f/logo.png",
+ "symbol" : "YLD",
+ "name" : "Yield",
+ "contractAddress" : "0xDcB01cc464238396E213a6fDd933E36796eAfF9f",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7F3EDcdD180Dbe4819Bd98FeE8929b5cEdB3AdEB/logo.png",
+ "symbol" : "XTK",
+ "name" : "xToken",
+ "contractAddress" : "0x7F3EDcdD180Dbe4819Bd98FeE8929b5cEdB3AdEB",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xAE1eaAE3F627AAca434127644371b67B18444051/logo.png",
+ "symbol" : "YOP",
+ "name" : "YOP",
+ "contractAddress" : "0xAE1eaAE3F627AAca434127644371b67B18444051",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xf4CD3d3Fda8d7Fd6C5a500203e38640A70Bf9577/logo.png",
+ "symbol" : "Yf-DAI",
+ "name" : "YfDAI.finance",
+ "contractAddress" : "0xf4CD3d3Fda8d7Fd6C5a500203e38640A70Bf9577",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2e2f3246b6c65CCc4239c9Ee556EC143a7E5DE2c/logo.png",
+ "symbol" : "YFIM",
+ "name" : "yfi.mobi",
+ "contractAddress" : "0x2e2f3246b6c65CCc4239c9Ee556EC143a7E5DE2c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xB1e9157c2Fdcc5a856C8DA8b2d89b6C32b3c1229/logo.png",
+ "symbol" : "ZEFU",
+ "name" : "Zenfuse Trading Platform Token",
+ "contractAddress" : "0xB1e9157c2Fdcc5a856C8DA8b2d89b6C32b3c1229",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 12,
+ "symbol" : "ZIL",
+ "name" : "Zilliqa",
+ "contractAddress" : "0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ZLA",
+ "name" : "Zilla",
+ "contractAddress" : "0xfd8971d5e8e1740ce2d0a84095fca4de729d0c16",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xE95A203B1a91a908F9B9CE46459d101078c2c3cb/logo.png",
+ "symbol" : "aEth",
+ "name" : "aEthereum",
+ "contractAddress" : "0xE95A203B1a91a908F9B9CE46459d101078c2c3cb",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7b123f53421b1bF8533339BFBdc7C98aA94163db/logo.png",
+ "symbol" : "buidl",
+ "name" : "dfohub",
+ "contractAddress" : "0x7b123f53421b1bF8533339BFBdc7C98aA94163db",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5218E472cFCFE0b64A064F055B43b4cdC9EfD3A6/logo.png",
+ "symbol" : "eRSDL",
+ "name" : "UnFederalReserveToken",
+ "contractAddress" : "0x5218E472cFCFE0b64A064F055B43b4cdC9EfD3A6",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png",
+ "symbol" : "UNI",
+ "name" : "Uniswap",
+ "contractAddress" : "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 6,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
+ "symbol" : "USDT",
+ "name" : "Tether USD",
+ "contractAddress" : "0xdAC17F958D2ee523a2206206994597C13D831ec7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "VEE",
+ "name" : "BLOCKv",
+ "contractAddress" : "0x340d2bde5eb28c1eed91b2f790723e3b160613b7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF411903cbC70a74d22900a5DE66A2dda66507255/logo.png",
+ "symbol" : "VRA",
+ "name" : "VERA",
+ "contractAddress" : "0xF411903cbC70a74d22900a5DE66A2dda66507255",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1b40183EFB4Dd766f11bDa7A7c3AD8982e998421/logo.png",
+ "symbol" : "VSP",
+ "name" : "VesperToken",
+ "contractAddress" : "0x1b40183EFB4Dd766f11bDa7A7c3AD8982e998421",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "VGX",
+ "name" : "Voyager",
+ "contractAddress" : "0x5af2be193a6abca9c8817001f45744777db30756",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 4,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x9355372396e3F6daF13359B7b607a3374cc638e0/logo.png",
+ "symbol" : "WHALE",
+ "name" : "WHALE",
+ "contractAddress" : "0x9355372396e3F6daF13359B7b607a3374cc638e0",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "WTC",
+ "name" : "Waltonchain",
+ "contractAddress" : "0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4691937a7508860F876c9c0a2a617E7d9E945D4B/logo.png",
+ "symbol" : "WOO",
+ "name" : "Wootrade Network",
+ "contractAddress" : "0x4691937a7508860F876c9c0a2a617E7d9E945D4B",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "WPR",
+ "name" : "WePower",
+ "contractAddress" : "0x4CF488387F035FF08c371515562CBa712f9015d4",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x5228a22e72ccC52d415EcFd199F99D0665E7733b/logo.png",
+ "symbol" : "pBTC",
+ "name" : "pTokens BTC",
+ "contractAddress" : "0x5228a22e72ccC52d415EcFd199F99D0665E7733b",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D/logo.png",
+ "symbol" : "renBTC",
+ "name" : "renBTC",
+ "contractAddress" : "0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x3383c5a8969Dc413bfdDc9656Eb80A1408E4bA20/logo.png",
+ "symbol" : "wANATHA",
+ "name" : "Wrapped ANATHA",
+ "contractAddress" : "0x3383c5a8969Dc413bfdDc9656Eb80A1408E4bA20",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xa0afAA285Ce85974c3C881256cB7F225e3A1178a/logo.png",
+ "symbol" : "wCRES",
+ "name" : "Wrapped CRES",
+ "contractAddress" : "0xa0afAA285Ce85974c3C881256cB7F225e3A1178a",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png",
+ "symbol" : "YFI",
+ "name" : "Yearn.Finance",
+ "contractAddress" : "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xF94b5C5651c888d928439aB6514B93944eEE6F48/logo.png",
+ "symbol" : "YLD",
+ "name" : "Yield",
+ "contractAddress" : "0xF94b5C5651c888d928439aB6514B93944eEE6F48",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 10,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xb9EF770B6A5e12E45983C5D80545258aA38F3B78/logo.png",
+ "symbol" : "ZCN",
+ "name" : "0chain",
+ "contractAddress" : "0xb9EF770B6A5e12E45983C5D80545258aA38F3B78",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2eDf094dB69d6Dcd487f1B3dB9febE2eeC0dd4c5/logo.png",
+ "symbol" : "ZEE",
+ "name" : "ZeroSwapToken",
+ "contractAddress" : "0x2eDf094dB69d6Dcd487f1B3dB9febE2eeC0dd4c5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xE41d2489571d322189246DaFA5ebDe1F4699F498/logo.png",
+ "symbol" : "ZRX",
+ "name" : "0x Protocol Token",
+ "contractAddress" : "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1337DEF18C680aF1f9f45cBcab6309562975b1dD/logo.png",
+ "symbol" : "arNXM",
+ "name" : "Armor NXM",
+ "contractAddress" : "0x1337DEF18C680aF1f9f45cBcab6309562975b1dD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "ADT",
+ "name" : "adToken",
+ "contractAddress" : "0xD0D6D6C5Fe4a677D343cC433536BB717bAe167dD",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : false
+ }
+ }
+]
diff --git a/app/src/main/assets/ethereum_tokens_testnet.json b/app/src/main/assets/ethereum_tokens_testnet.json
new file mode 100644
index 0000000000..8cd1d5a47e
--- /dev/null
+++ b/app/src/main/assets/ethereum_tokens_testnet.json
@@ -0,0 +1,46 @@
+[
+ {
+ "decimalCount" : 18,
+ "symbol" : "WEENUS",
+ "name" : "Weenus",
+ "contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "XEENUS",
+ "name" : "Xeenus",
+ "contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "YEENUS",
+ "name" : "Yeenus",
+ "contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "ZEENUS",
+ "name" : "Zeenus",
+ "contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
+ "blockchain" : {
+ "key" : "ethereum",
+ "curve" : "secp256k1",
+ "testnet" : true
+ }
+ }
+]
diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config
index 0cf3af21e6..e80938f7d9 160000
--- a/app/src/main/assets/tangem-app-config
+++ b/app/src/main/assets/tangem-app-config
@@ -1 +1 @@
-Subproject commit 0cf3af21e6f441e6323a1ba127ee7518168797df
+Subproject commit e80938f7d9ba378a91ac544a9170597e64afa696
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 544e1d7cda..c0d6abea79 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -7,20 +7,18 @@ import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
-import com.tangem.CardFilter
-import com.tangem.Config
import com.tangem.TangemSdk
-import com.tangem.commands.common.card.CardType
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.common.DialogManager
import com.tangem.tap.common.IntentHandler
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.redux.NotificationsHandler
+import com.tangem.tap.common.redux.global.AndroidResources
+import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
-import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.CoroutineExceptionHandler
@@ -30,7 +28,6 @@ import kotlinx.coroutines.Job
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.ref.WeakReference
-import java.util.*
import kotlin.coroutines.CoroutineContext
lateinit var tangemSdk: TangemSdk
@@ -64,24 +61,40 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
- store.state.globalState.feedbackManager?.updateAcivity(this)
- requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ systemActions()
+ store.state.globalState.feedbackManager?.updateActivity(this)
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
- tangemSdk = TangemSdk.init(
- this, Config(cardFilter = CardFilter(EnumSet.allOf(CardType::class.java)))
- )
- tangemSdkManager = TangemSdkManager(this)
+ tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
+ tangemSdkManager = TangemSdkManager(tangemSdk, this)
+
+ store.dispatch(GlobalAction.SetResources(getAndroidResources()))
store.dispatch(WalletConnectAction.RestoreSessions)
}
+ private fun getAndroidResources(): AndroidResources {
+ return AndroidResources(
+ AndroidResources.RString(
+ R.string.copy_toast_msg,
+ R.string.details_notification_erase_wallet_not_possible
+ )
+ )
+ }
+
+ private fun systemActions() {
+ // makes the status bar text dark
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ }
+
override fun onResume() {
super.onResume()
notificationsHandler = NotificationsHandler(fragment_container)
- if (supportFragmentManager.backStackEntryCount == 0 ||
- store.state.globalState.scanNoteResponse == null
- ) {
- store.dispatch(HomeAction.CheckIfFirstLaunch)
+
+ val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
+ val isScannedBefore = store.state.globalState.scanResponse != null
+ val isOnboardingServiceActive = store.state.globalState.onboardingManager != null
+ if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
}
intentHandler.handleIntent(intent)
diff --git a/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt
new file mode 100644
index 0000000000..a30af0f5d6
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt
@@ -0,0 +1,21 @@
+package com.tangem.tap.common
+
+import android.content.Context
+import android.net.Uri
+import androidx.browser.customtabs.CustomTabColorSchemeParams
+import androidx.browser.customtabs.CustomTabsIntent
+import com.tangem.tap.common.extensions.getColorCompat
+import com.tangem.wallet.R
+
+class CustomTabsManager {
+ fun openUrl(url: String, context: Context) {
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .setDefaultColorSchemeParams(
+ CustomTabColorSchemeParams.Builder()
+ .setNavigationBarColor(context.getColorCompat(R.color.toolbarColor))
+ .build()
+ )
+ .build()
+ customTabsIntent.launchUrl(context, Uri.parse(url))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
index 96992a5d7a..b3a0c345e2 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -2,9 +2,14 @@ package com.tangem.tap.common
import android.app.Dialog
import android.content.Context
+import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
+import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
+import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog
+import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
@@ -37,28 +42,39 @@ class DialogManager : StoreSubscriber {
val context = context ?: return
if (dialog != null) return
- when (state.dialog) {
+ dialog = when (state.dialog) {
+ is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
+ is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
+ is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)
is WalletConnectDialog.UnsupportedCard ->
- dialog = SimpleAlertDialog.create(
+ SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet,
context = context
)
is WalletConnectDialog.OpeningSessionRejected -> {
- dialog = SimpleAlertDialog.create(
+ SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
messageRes = R.string.wallet_connect_same_wcuri,
context = context
)
}
+ is WalletConnectDialog.SessionTimeout -> {
+ SimpleAlertDialog.create(
+ titleRes = R.string.wallet_connect,
+ messageRes = R.string.wallet_connect_error_timeout,
+ context = context
+ )
+ }
is WalletConnectDialog.ApproveWcSession ->
- dialog = ApproveWcSessionDialog.create(state.dialog.session, context)
+ ApproveWcSessionDialog.create(state.dialog.session, context)
is WalletConnectDialog.ClipboardOrScanQr ->
- dialog = ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context)
+ ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context)
is WalletConnectDialog.RequestTransaction ->
- dialog = TransactionDialog.create(state.dialog.dialogData, context)
+ TransactionDialog.create(state.dialog.dialogData, context)
is WalletConnectDialog.PersonalSign ->
- dialog = PersonalSignDialog.create(state.dialog.data, context)
+ PersonalSignDialog.create(state.dialog.data, context)
+ else -> null
}
dialog?.show()
}
diff --git a/app/src/main/java/com/tangem/tap/common/Handler.kt b/app/src/main/java/com/tangem/tap/common/Handler.kt
new file mode 100644
index 0000000000..f36d4d1541
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/Handler.kt
@@ -0,0 +1,26 @@
+package com.tangem.tap.common
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+
+private val uiHandler = Handler(Looper.getMainLooper())
+private val backgroundHandler = Handler(HandlerThread("AppMainHandlerThread").apply { start() }.looper)
+
+fun postUi(ms: Long = 0, func: Runnable) {
+ if (ms == 0L) uiHandler.post { func.run() } else uiHandler.postDelayed(func, ms)
+}
+
+fun postBackground(ms: Long = 0, func: Runnable) {
+ if (ms == 0L) backgroundHandler.post { func.run() } else backgroundHandler.postDelayed(func, ms)
+}
+
+fun post(ms: Long = 0, func: Runnable) {
+ if (ms == 0L) {
+ func.run()
+ } else {
+ val currentLooper = Looper.myLooper() ?: return
+
+ Handler(currentLooper).postDelayed(func, ms)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
index f30a481c98..7058973e1a 100644
--- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
@@ -5,16 +5,20 @@ import android.nfc.NfcAdapter
import android.nfc.Tag
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.topup.TradeCryptoHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.home.redux.HomeAction
+import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
+import timber.log.Timber
class IntentHandler {
fun handleIntent(intent: Intent?) {
handleBackgroundScan(intent)
handleWalletConnectLink(intent)
+ handleSellCurrencyCallback(intent)
}
private fun handleBackgroundScan(intent: Intent?) {
@@ -38,4 +42,30 @@ class IntentHandler {
}
}
+ private fun handleSellCurrencyCallback(intent: Intent?) {
+ try {
+ val transactionID =
+ intent?.data?.getQueryParameter(TradeCryptoHelper.TRANSACTION_ID_PARAM) ?: return
+ val currency =
+ intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_CODE_PARAM) ?: return
+ val amount =
+ intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_AMOUNT_PARAM) ?: return
+ val destinationAddress =
+ intent.data?.getQueryParameter(TradeCryptoHelper.DEPOSIT_WALLET_ADDRESS_PARAM)
+ ?: return
+
+ Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
+
+ store.dispatch(WalletAction.TradeCryptoAction.SendCrypto(
+ currencyId = currency,
+ amount = amount,
+ destinationAddress = destinationAddress,
+ transactionId = transactionID
+ ))
+ } catch (exception: Exception) {
+ Timber.d("Not Moonpay URL")
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
index 4395bd0922..8ddf2f5ad9 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
@@ -8,4 +8,8 @@ enum class AnalyticsEvent(val event: String) {
APP_RATING_DISMISS("dismiss_rate_app_warning"),
APP_RATING_NEGATIVE("negative_rate_app_feedback"),
APP_RATING_POSITIVE("positive_rate_app_feedback"),
+ WC_SUCCESS_RESPONSE("wallet_connect_success_response"),
+ WC_INVALID_REQUEST("wallet_connect_invalid_request"),
+ WC_NEW_SESSION("wallet_connect_new_session"),
+ WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"),
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
index 1dc3586808..1022f85c5a 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
@@ -1,8 +1,15 @@
package com.tangem.tap.common.analytics
-import com.tangem.commands.common.card.Card
+import com.tangem.common.card.Card
+import com.tangem.common.core.TangemSdkError
interface AnalyticsHandler {
- fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
+ fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null)
+ fun logCardSdkError(
+ error: TangemSdkError,
+ actionToLog: FirebaseAnalyticsHandler.ActionToLog,
+ parameters: Map? = null,
+ card: Card? = null
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
index d1d3ce7a38..33383e1089 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
@@ -3,34 +3,141 @@ package com.tangem.tap.common.analytics
import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.ktx.analytics
+import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
-import com.tangem.commands.common.card.Card
+import com.tangem.common.card.Card
+import com.tangem.common.core.TangemSdkError
+import com.tangem.tap.common.extensions.filterNotNull
object FirebaseAnalyticsHandler : AnalyticsHandler {
- override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
- Firebase.analytics.logEvent(event.event, setCardData(card))
+ override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: String?) {
+ Firebase.analytics.logEvent(event.event, setData(card, blockchain))
}
fun logException(name: String, throwable: Throwable) {
Firebase.analytics.logEvent(name, bundleOf(
- "message" to (throwable.message ?: "none"),
- "cause_message" to (throwable.cause?.message ?: "none"),
- "stack_trace" to throwable.stackTraceToString()
+ "message" to (throwable.message ?: "none"),
+ "cause_message" to (throwable.cause?.message ?: "none"),
+ "stack_trace" to throwable.stackTraceToString()
))
}
- private fun setCardData(card: Card?): Bundle {
+ override fun logCardSdkError(
+ error: TangemSdkError,
+ actionToLog: ActionToLog,
+ parameters: Map?,
+ card: Card?,
+ ) {
+ if (error is TangemSdkError.UserCancelled) return
+
+ val params = parameters?.toMutableMap() ?: mutableMapOf()
+ if (card != null) params + getParamsFromCard(card)
+ params[AnalyticsParam.ACTION] = actionToLog.key
+ params[AnalyticsParam.ERROR_CODE] = error.code.toString()
+ params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
+ params[AnalyticsParam.ERROR_KEY] = "TangemSdkError"
+
+ params.forEach {
+ FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
+ }
+ val cardError = TangemSdk.map(error)
+ FirebaseCrashlytics.getInstance().recordException(cardError)
+ }
+
+ private fun getParamsFromCard(card: Card): Map {
+ return mapOf(
+ AnalyticsParam.FIRMWARE to card.firmwareVersion.stringValue,
+ AnalyticsParam.BATCH_ID to card.batchId
+ ).filterNotNull()
+ }
+
+ private fun setData(card: Card?, blockchain: String?): Bundle {
if (card == null) return bundleOf()
return bundleOf(
- AnalyticsParam.BLOCKCHAIN.param to card.cardData?.blockchainName,
- AnalyticsParam.BATCH_ID.param to card.cardData?.batchId,
- AnalyticsParam.FIRMWARE.param to card.firmwareVersion.version
+ AnalyticsParam.BLOCKCHAIN.param to (blockchain),
+ AnalyticsParam.BATCH_ID.param to card.batchId,
+ AnalyticsParam.FIRMWARE.param to card.firmwareVersion.stringValue
)
}
- private enum class AnalyticsParam(val param: String) {
+ fun logWcEvent(event: WcAnalyticsEvent) {
+ when (event) {
+ is WcAnalyticsEvent.Action -> {
+
+ Firebase.analytics.logEvent(
+ AnalyticsEvent.WC_SUCCESS_RESPONSE.event, bundleOf(
+ AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
+ )
+ )
+ }
+ is WcAnalyticsEvent.Error -> {
+ mapOf(
+ AnalyticsParam.WALLET_CONNECT_ACTION to event.action?.name,
+ AnalyticsParam.ERROR_DESCRIPTION to event.error.message
+ )
+ .filterNotNull()
+ .forEach {
+ FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value)
+ }
+ FirebaseCrashlytics.getInstance().recordException(event.error)
+ }
+ is WcAnalyticsEvent.InvalidRequest ->
+ Firebase.analytics.logEvent(
+ AnalyticsEvent.WC_INVALID_REQUEST.event, bundleOf(
+ AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
+ )
+ )
+ is WcAnalyticsEvent.Session -> {
+ val analyticsEvent = when (event.event) {
+ WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
+ WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
+ }
+ Firebase.analytics.logEvent(
+ analyticsEvent.event, bundleOf(
+ AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
+ )
+ )
+ }
+ }
+
+ }
+
+ enum class AnalyticsParam(val param: String) {
BLOCKCHAIN("blockchain"),
BATCH_ID("batch_id"),
FIRMWARE("firmware"),
+ ACTION("action"),
+ ERROR_DESCRIPTION("error_description"),
+ ERROR_CODE("error_code"),
+ NEW_SECURITY_OPTION("new_security_option"),
+ ERROR_KEY("Tangem SDK error key"),
+ WALLET_CONNECT_ACTION("wallet_connect_action"),
+ WALLET_CONNECT_REQUEST("wallet_connect_request"),
+ WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"),
}
-}
\ No newline at end of file
+
+ enum class ActionToLog(val key: String) {
+ Scan("tap_scan_task"),
+ SendTransaction("send_transaction"),
+ WalletConnectSign("wallet_connect_personal_sign"),
+ WalletConnectTransaction("wallet_connect_tx_sign"),
+ ReadPinSettings("read_pin_settings"),
+ ChangeSecOptions("change_sec_options"),
+ CreateWallet("create_wallet"),
+ PurgeWallet("purge_wallet"),
+ WriteIssuerData("write_issuer_data"),
+ }
+
+ enum class WcSessionEvent { Disconnect, Connect }
+
+ sealed class WcAnalyticsEvent {
+ data class Error(val error: Throwable, val action: WcAction?) :
+ WcAnalyticsEvent()
+
+ data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent()
+ data class Action(val action: WcAction) : WcAnalyticsEvent()
+ data class InvalidRequest(val json: String?) : WcAnalyticsEvent()
+ }
+
+ enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt
new file mode 100644
index 0000000000..0d6b58db28
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt
@@ -0,0 +1,81 @@
+package com.tangem.tap.common.analytics
+
+import com.tangem.common.core.TangemSdkError
+
+object TangemSdk {
+
+ // This mapping is performed to group errors in FirebaseCrashlytics.
+ // At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
+ fun map(error: TangemSdkError): TangemSdkError {
+ return when (error) {
+ is TangemSdkError.TagLost -> TangemSdkError.TagLost()
+ is TangemSdkError.ExtendedLengthNotSupported -> TangemSdkError.ExtendedLengthNotSupported()
+ is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
+ is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
+ is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
+ error.customMessage)
+ is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
+ is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
+ error.customMessage)
+ is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
+ error.customMessage)
+ is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
+ is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
+ is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
+ is TangemSdkError.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand()
+ is TangemSdkError.InvalidState -> TangemSdkError.InvalidState()
+ is TangemSdkError.InsNotSupported -> TangemSdkError.InsNotSupported()
+ is TangemSdkError.InvalidParams -> TangemSdkError.InvalidParams()
+ is TangemSdkError.NeedEncryption -> TangemSdkError.NeedEncryption()
+ is TangemSdkError.FileNotFound -> TangemSdkError.FileNotFound()
+ is TangemSdkError.WalletNotFound -> TangemSdkError.WalletNotFound()
+ is TangemSdkError.AlreadyPersonalized -> TangemSdkError.AlreadyPersonalized()
+ is TangemSdkError.CannotBeDepersonalized -> TangemSdkError.CannotBeDepersonalized()
+ is TangemSdkError.AccessCodeRequired -> TangemSdkError.AccessCodeRequired()
+ is TangemSdkError.CardReadWrongWallet -> TangemSdkError.CardReadWrongWallet()
+ is TangemSdkError.CardWithMaxZeroWallets -> TangemSdkError.CardWithMaxZeroWallets()
+ is TangemSdkError.AlreadyCreated -> TangemSdkError.AlreadyCreated()
+ is TangemSdkError.MaxNumberOfWalletsCreated -> TangemSdkError.MaxNumberOfWalletsCreated()
+ is TangemSdkError.PurgeWalletProhibited -> TangemSdkError.PurgeWalletProhibited()
+ is TangemSdkError.AccessCodeCannotBeChanged -> TangemSdkError.AccessCodeCannotBeChanged()
+ is TangemSdkError.PasscodeCannotBeChanged -> TangemSdkError.PasscodeCannotBeChanged()
+ is TangemSdkError.AccessCodeCannotBeDefault -> TangemSdkError.AccessCodeCannotBeDefault()
+ is TangemSdkError.NoRemainingSignatures -> TangemSdkError.NoRemainingSignatures()
+ is TangemSdkError.EmptyHashes -> TangemSdkError.EmptyHashes()
+ is TangemSdkError.HashSizeMustBeEqual -> TangemSdkError.HashSizeMustBeEqual()
+ is TangemSdkError.WalletIsNotCreated -> TangemSdkError.WalletIsNotCreated()
+ is TangemSdkError.SignHashesNotAvailable -> TangemSdkError.SignHashesNotAvailable()
+ is TangemSdkError.TooManyHashesInOneTransaction -> TangemSdkError.TooManyHashesInOneTransaction()
+ is TangemSdkError.ExtendedDataSizeTooLarge -> TangemSdkError.ExtendedDataSizeTooLarge()
+ is TangemSdkError.NotPersonalized -> TangemSdkError.NotPersonalized()
+ is TangemSdkError.NotActivated -> TangemSdkError.NotActivated()
+ is TangemSdkError.WalletIsPurged -> TangemSdkError.WalletIsPurged()
+ is TangemSdkError.PasscodeRequired -> TangemSdkError.PasscodeRequired()
+ is TangemSdkError.VerificationFailed -> TangemSdkError.VerificationFailed()
+ is TangemSdkError.DataSizeTooLarge -> TangemSdkError.DataSizeTooLarge()
+ is TangemSdkError.MissingCounter -> TangemSdkError.MissingCounter()
+ is TangemSdkError.OverwritingDataIsProhibited -> TangemSdkError.OverwritingDataIsProhibited()
+ is TangemSdkError.DataCannotBeWritten -> TangemSdkError.DataCannotBeWritten()
+ is TangemSdkError.MissingIssuerPubicKey -> TangemSdkError.MissingIssuerPubicKey()
+ is TangemSdkError.CardVerificationFailed -> TangemSdkError.CardVerificationFailed()
+ is TangemSdkError.WrongAccessCode -> TangemSdkError.WrongAccessCode()
+ is TangemSdkError.WrongPasscode -> TangemSdkError.WrongPasscode()
+ is TangemSdkError.UnknownError -> TangemSdkError.UnknownError()
+ is TangemSdkError.UserCancelled -> TangemSdkError.UserCancelled()
+ is TangemSdkError.Busy -> TangemSdkError.Busy()
+ is TangemSdkError.MissingPreflightRead -> TangemSdkError.MissingPreflightRead()
+ is TangemSdkError.WrongCardNumber -> TangemSdkError.WrongCardNumber()
+ is TangemSdkError.WrongCardType -> TangemSdkError.WrongCardType()
+ is TangemSdkError.CardError -> TangemSdkError.CardError()
+ is TangemSdkError.NotSupportedFirmwareVersion -> TangemSdkError.NotSupportedFirmwareVersion()
+ is TangemSdkError.WalletError -> TangemSdkError.WalletError()
+ is TangemSdkError.WalletCannotBeCreated -> TangemSdkError.WalletCannotBeCreated()
+ is TangemSdkError.UnsupportedCurve -> TangemSdkError.UnsupportedCurve()
+ is TangemSdkError.UnsupportedWalletConfig -> TangemSdkError.UnsupportedWalletConfig()
+ is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError()
+ is TangemSdkError.NetworkError -> TangemSdkError.NetworkError(error.customMessage)
+ is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.throwable)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt
index 986a170564..a2d4127c8f 100644
--- a/app/src/main/java/com/tangem/tap/common/entities/Button.kt
+++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt
@@ -1,3 +1,17 @@
package com.tangem.tap.common.entities
-open class Button(val enabled: Boolean)
\ No newline at end of file
+import com.tangem.tap.features.send.redux.states.ButtonState
+import com.tangem.tap.features.wallet.redux.ProgressState
+
+open class Button(val enabled: Boolean)
+
+open class IndeterminateProgressButton(
+ val state: ButtonState
+) : Button(state != ButtonState.DISABLED) {
+
+ val progressState: ProgressState
+ get() = when (state) {
+ ButtonState.PROGRESS -> ProgressState.Loading
+ else -> ProgressState.Done
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
new file mode 100644
index 0000000000..8ed146440b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
@@ -0,0 +1,63 @@
+package com.tangem.tap.common.extensions
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import androidx.core.app.ShareCompat
+import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider
+import java.io.File
+
+/**
+ * required since targetAndroid=30
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+fun Activity.sendEmail(
+ email: String,
+ subject: String,
+ message: String,
+ file: File? = null,
+ onFail: ((Exception) -> Unit)? = null
+) {
+ fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
+ val builder = ShareCompat.IntentBuilder.from(this)
+ .setType("message/rfc822")
+ .setEmailTo(arrayOf(recipient))
+ .setSubject(subject)
+ .setText(text)
+ file?.let { builder.setStream(FileProvider.getUriForFile(this, "${packageName}.provider", it)) }
+ return builder.intent
+ }
+
+ val originalIntent = createEmailShareIntent(email, subject, message, file)
+ val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
+
+ val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0)
+ val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
+
+ val targetedIntents = originalIntentResults
+ .filter { originalResult ->
+ emailFilterIntentResults.any {
+ originalResult.activityInfo.packageName == it.activityInfo.packageName
+ }
+ }
+ .map {
+ createEmailShareIntent(email, subject, message, file).apply {
+ setPackage(it.activityInfo.packageName)
+ }
+ }
+ .toMutableList()
+ try {
+ val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ ContextCompat.startActivity(this, chooserIntent, null)
+ } catch (ex: Exception) {
+ onFail?.invoke(ex)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
index d1c417e842..8492ae44f1 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
@@ -14,4 +14,18 @@ fun MutableList.removeBy(predicate: (T) -> Boolean): Boolean {
val toRemove = this.filter(predicate)
this.removeAll(toRemove)
return toRemove.isNotEmpty()
+}
+
+fun MutableList.replaceBy(item: T, predicate: (T) -> Boolean): Boolean {
+ val toRemove = this.filter(predicate)
+ if (toRemove.isEmpty()) return false
+
+ val indexes = toRemove.map { indexOf(it) }
+ this.removeAll(toRemove)
+ indexes.forEach { this.add(it, item) }
+ return true
+}
+
+fun MutableList.replaceByOrAdd(item: T, predicate: (T) -> Boolean) {
+ if (!replaceBy(item, predicate)) add(item)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt b/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt
new file mode 100644
index 0000000000..63c62dfdc2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.common.extensions
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+/**
+[REDACTED_AUTHOR]
+ */
+suspend fun withMainContext(block: suspend CoroutineScope.() -> T) {
+ withContext(Dispatchers.Main, block)
+}
+
+suspend fun withIOContext(block: suspend CoroutineScope.() -> T) {
+ withContext(Dispatchers.IO, block)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Map.kt b/app/src/main/java/com/tangem/tap/common/extensions/Map.kt
new file mode 100644
index 0000000000..e970306219
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Map.kt
@@ -0,0 +1,4 @@
+package com.tangem.tap.common.extensions
+
+fun Map.filterNotNull(): Map =
+ filter { it.key != null && it.value != null } as Map
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
index d56ae0af85..f96f16f5ee 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
@@ -1,29 +1,48 @@
package com.tangem.tap.common.extensions
+import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
+import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.details.ui.DetailsConfirmFragment
import com.tangem.tap.features.details.ui.DetailsFragment
import com.tangem.tap.features.details.ui.DetailsSecurityFragment
-import com.tangem.tap.features.details.ui.twins.CreateTwinWalletFragment
-import com.tangem.tap.features.details.ui.twins.TwinWalletWarningFragment
import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
+import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
+import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
+import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
+import com.tangem.tap.features.onboarding.products.wallet.OnboardingWalletFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
-import com.tangem.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment
import com.tangem.wallet.R
-fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) {
+fun FragmentActivity.openFragment(
+ screen: AppScreen,
+ addToBackstack: Boolean,
+ fgShareTransition: FragmentShareTransition? = null
+) {
val transaction = this.supportFragmentManager.beginTransaction()
- transaction.replace(R.id.fragment_container, fragmentFactory(screen), screen.name)
- if (addToBackStack && screen != AppScreen.Home) transaction.addToBackStack(null)
+ val fragment = fragmentFactory(screen)
+ fgShareTransition?.apply {
+ fragment.sharedElementEnterTransition = enterTransitionSet
+ fragment.sharedElementReturnTransition = exitTransitionSet
+ transaction.setReorderingAllowed(true)
+ shareElements.forEach { shareElement ->
+ shareElement.wView.get()?.let { view ->
+ transaction.addSharedElement(view, shareElement.elementName)
+ }
+ }
+ }
+ transaction.replace(R.id.fragment_container, fragment, screen.name)
+ if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
transaction.commitAllowingStateLoss()
}
@@ -38,18 +57,29 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? {
return tag?.let { AppScreen.valueOf(tag) }
}
+fun FragmentActivity.addOnBackPressedDispatcher(
+ isEnabled: Boolean = true,
+ onBackPressed: VoidCallback
+): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) {
+ override fun handleOnBackPressed() {
+ onBackPressed()
+ }
+}).also { this.onBackPressedDispatcher.addCallback(it) }
+
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
+// AppScreen.Home -> TestLeapfrogFragment()
AppScreen.Home -> HomeFragment()
+ AppScreen.OnboardingNote -> OnboardingNoteFragment()
+ AppScreen.OnboardingWallet -> OnboardingWalletFragment()
+ AppScreen.OnboardingTwins -> TwinsCardsFragment()
+ AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
AppScreen.Wallet -> WalletFragment()
AppScreen.Send -> SendFragment()
AppScreen.Details -> DetailsFragment()
AppScreen.DetailsConfirm -> DetailsConfirmFragment()
AppScreen.DetailsSecurity -> DetailsSecurityFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
- AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment()
- AppScreen.CreateTwinWallet -> CreateTwinWalletFragment()
- AppScreen.TwinsOnboarding -> TwinsOnboardingFragment()
AppScreen.AddTokens -> AddTokensFragment()
AppScreen.WalletDetails -> WalletDetailsFragment()
AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment()
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt
index e12711e3d0..be71883ecc 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt
@@ -1,7 +1,9 @@
package com.tangem.tap.common.extensions
+import android.graphics.PorterDuff
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.content.ContextCompat
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
@@ -13,9 +15,8 @@ fun Picasso.loadCurrenciesIcon(
imageView: ImageView,
textView: TextView,
token: Token? = null,
- blockchain: Blockchain?,
+ blockchain: Blockchain,
) {
- val blockchain = blockchain ?: Blockchain.Ethereum
val url = if (token != null) {
IconsUtil.getTokenIconUri(blockchain, token)
@@ -48,6 +49,7 @@ fun Picasso.loadCurrenciesIcon(
imageView.colorFilter = null
textView.text = null
}
+ if (blockchain.isTestnet()) imageView.tint(R.color.tint)
}
})
}
@@ -70,12 +72,20 @@ private fun setOfflineCurrencyImage(
} else {
setBlockchainImage(imageView, textView, blockchain)
}
+ if (blockchain.isTestnet()) imageView.tint(R.color.tint)
+}
+
+fun ImageView.tint(colorRes: Int) {
+// val color = ContextCompat.getColor(context, colorRes);
+// ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color));
+ this.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.DARKEN);
+
}
private fun setBlockchainImage(
imageView: ImageView,
textView: TextView,
- blockchain: Blockchain
+ blockchain: Blockchain,
) {
imageView.setImageResource(blockchain.getIconRes())
imageView.colorFilter = null
@@ -85,7 +95,7 @@ private fun setBlockchainImage(
private fun setTokenImage(
imageView: ImageView,
textView: TextView,
- token: Token
+ token: Token,
) {
imageView.setImageResource(R.drawable.shape_circle)
imageView.setColorFilter(token.getColor())
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
index a792b8b25e..330457dbf6 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
@@ -23,9 +23,17 @@ fun BigDecimal.toFormattedString(
}
fun BigDecimal.toFormattedCurrencyString(
- decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN
+ decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN,
+ limitNumberOfDecimals: Boolean = true
): String {
- val formattedAmount = this.toFormattedString(decimals = decimals, roundingMode = roundingMode)
+ val decimalsForRounding = if (limitNumberOfDecimals){
+ if (decimals > 8) 8 else decimals
+ } else {
+ decimals
+ }
+ val formattedAmount = this.toFormattedString(
+ decimals = decimalsForRounding, roundingMode = roundingMode
+ )
return "$formattedAmount $currency"
}
@@ -37,7 +45,7 @@ fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrenc
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
val fiatValue = rateValue.multiply(this)
- return fiatValue.setScale(2, RoundingMode.DOWN)
+ return fiatValue.setScale(2, RoundingMode.HALF_UP)
}
fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: FiatCurrencyName): String {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
index 99e6d8fe5d..83aa152f76 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
@@ -1,6 +1,10 @@
package com.tangem.tap.common.extensions
-import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.StateDialog
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
@@ -8,8 +12,65 @@ import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Store
-fun Store.dispatchOnMain(action: Action) {
+fun Store<*>.dispatchOnMain(action: Action) {
scope.launch(Dispatchers.Main) {
store.dispatch(action)
}
-}
\ No newline at end of file
+}
+
+suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse, addAnalytics: Boolean = true) {
+ store.state.globalState.tapWalletManager.onCardScanned(scanResponse, addAnalytics)
+}
+
+fun Store<*>.dispatchOpenUrl(url: String) {
+ store.dispatch(NavigationAction.OpenUrl(url))
+}
+
+fun Store<*>.dispatchShare(url: String) {
+ store.dispatch(NavigationAction.Share(url))
+}
+
+fun Store<*>.dispatchNotification(resId: Int) {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.ShowNotification(resId))
+ }
+}
+
+fun Store<*>.dispatchToastNotification(resId: Int) {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.ShowToastNotification(resId))
+ }
+}
+
+
+fun Store<*>.dispatchErrorNotification(error: TapError) {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.ShowErrorNotification(error))
+ }
+}
+
+/**
+ * @param fatal used to indicate errors that should not normally have occurred
+ */
+fun Store<*>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) {
+ val prefix = if (fatal) "FATAL ERROR: " else "DEBUG ERROR: "
+ dispatchDebugErrorNotification(TapError.CustomError("$prefix $message"))
+}
+
+fun Store<*>.dispatchDebugErrorNotification(error: TapError) {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.DebugShowErrorNotification(error))
+ }
+}
+
+fun Store<*>.dispatchDialogShow(dialog: StateDialog) {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.ShowDialog(dialog))
+ }
+}
+
+fun Store<*>.dispatchDialogHide() {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.HideDialog)
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Toast.kt b/app/src/main/java/com/tangem/tap/common/extensions/Toast.kt
new file mode 100644
index 0000000000..23d969af40
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Toast.kt
@@ -0,0 +1,31 @@
+package com.tangem.tap.common.extensions
+
+import android.content.Context
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+
+fun Context.toast(message: String, length: Int = Toast.LENGTH_LONG) {
+ Toast.makeText(this, message, length).show()
+}
+
+fun Context.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
+ Toast.makeText(this, this.getString(messageRes), length).show()
+}
+
+fun View.toast(message: String, length: Int = Toast.LENGTH_LONG) {
+ context.toast(message, length)
+}
+
+fun View.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
+ context.toast(context.getString(messageRes), length)
+}
+
+fun Fragment.toast(message: String, length: Int = Toast.LENGTH_LONG) {
+ context?.toast(message, length)
+}
+
+fun Fragment.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
+ context?.let { it.toast(it.getString(messageRes), length) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt b/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt
new file mode 100644
index 0000000000..69fbba97b6
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt
@@ -0,0 +1,24 @@
+package com.tangem.tap.common.extensions
+
+import androidx.transition.Transition
+
+/**
+[REDACTED_AUTHOR]
+ */
+inline fun Transition.addListener(
+ crossinline onStart: (animator: Transition) -> Unit = {},
+ crossinline onEnd: (animator: Transition) -> Unit = {},
+ crossinline onCancel: (animator: Transition) -> Unit = {},
+ crossinline onPause: (animator: Transition) -> Unit = {},
+ crossinline onRepeat: (animator: Transition) -> Unit = {}
+): Transition.TransitionListener {
+ val listener = object : Transition.TransitionListener {
+ override fun onTransitionStart(transition: Transition) = onStart(transition)
+ override fun onTransitionEnd(transition: Transition) = onEnd(transition)
+ override fun onTransitionCancel(transition: Transition) = onCancel(transition)
+ override fun onTransitionPause(transition: Transition) = onPause(transition)
+ override fun onTransitionResume(transition: Transition) = onRepeat(transition)
+ }
+ addListener(listener)
+ return listener
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
index b43c49b307..e468ec2203 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
@@ -5,6 +5,7 @@ import android.content.*
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.Build
+import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
@@ -16,6 +17,7 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
+import com.tangem.common.extensions.VoidCallback
fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? {
return ContextCompat.getDrawable(requireContext(), drawableResId)
@@ -30,6 +32,11 @@ fun Fragment.getColor(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(requireContext(), colorRes)
}
+@ColorInt
+fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
+ return ContextCompat.getColor(this, colorRes)
+}
+
fun View.getString(@StringRes id: Int): String {
return context.getString(id)
}
@@ -71,8 +78,8 @@ fun Context.dpToPixels(dp: Int): Int =
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
).toInt()
-tailrec fun Context?.getActivity(): Activity? = this as? Activity ?:
- (this as? ContextWrapper)?.baseContext?.getActivity()
+tailrec fun Context?.getActivity(): Activity? = this as? Activity
+ ?: (this as? ContextWrapper)?.baseContext?.getActivity()
fun MaterialCardView.setMargins(
marginLeftDp: Int = 16,
@@ -136,4 +143,19 @@ fun Context.shareText(text: String) {
fun Fragment.shareText(text: String) {
requireContext().shareText(text)
+}
+
+fun Context.safeStartActivity(
+ intent: Intent,
+ options: Bundle? = null,
+ fallback: ((ActivityNotFoundException) -> Unit)? = null,
+ finally: VoidCallback? = null
+) {
+ try {
+ this.startActivity(intent, options)
+ } catch (ex: ActivityNotFoundException) {
+ fallback?.invoke(ex)
+ } finally {
+ finally?.invoke()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt
index fb9c20bfdb..81be964cb1 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.common.extensions
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.view.ViewParent
import androidx.core.view.forEach
@@ -15,11 +16,8 @@ import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
-fun ViewGroup.inflate(viewToInflate: Int, rootView: ViewGroup?, parent: ViewGroup) {
- if (rootView == null) {
- val inflatedView = LayoutInflater.from(context).inflate(viewToInflate, rootView)
- parent.addView(inflatedView)
- }
+fun ViewGroup.inflate(viewToInflate: Int, attachToRoot: Boolean = false): View {
+ return LayoutInflater.from(context).inflate(viewToInflate, this, attachToRoot)
}
fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()) {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
new file mode 100644
index 0000000000..70a4fcc286
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
@@ -0,0 +1,59 @@
+package com.tangem.tap.common.extensions
+
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.services.Result
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.extensions.amountToCreateAccount
+import com.tangem.tap.domain.extensions.isNoAccountError
+import com.tangem.tap.domain.getFirstToken
+import com.tangem.tap.domain.topup.TradeCryptoHelper
+import com.tangem.tap.features.wallet.redux.AddressData
+import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
+import com.tangem.tap.network.NetworkConnectivity
+import com.tangem.tap.store
+
+/**
+[REDACTED_AUTHOR]
+ */
+suspend fun WalletManager.safeUpdate(): Result = try {
+ update()
+ Result.Success(wallet)
+} catch (exception: Exception) {
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
+ Result.Failure(TapError.NoInternetConnection)
+ } else {
+ val blockchain = wallet.blockchain
+ val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken())
+
+ if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) {
+ Result.Failure(TapError.WalletManagerUpdate.NoAccountError(amountToCreateAccount.toString()))
+ } else {
+ val message = exception.localizedMessage ?: "An error has occurred. Try later"
+ Result.Failure(TapError.WalletManagerUpdate.InternalError(message))
+ }
+ }
+}
+
+fun WalletManager?.getToUpUrl(): String? {
+ val wallet = this?.wallet ?: return null
+ val config = store.state.globalState.configManager?.config ?: return null
+ val defaultAddress = wallet.address
+
+ return TradeCryptoHelper.getUrl(
+ TradeCryptoHelper.Action.Buy,
+ wallet.blockchain,
+ wallet.blockchain.currency,
+ defaultAddress,
+ config.moonPayApiKey,
+ config.moonPayApiSecretKey
+ )
+}
+
+fun WalletManager?.getAddressData(): AddressData? {
+ val wallet = this?.wallet ?: return null
+
+ val addressDataList = wallet.createAddressesData()
+ return if (addressDataList.isEmpty()) null
+ else addressDataList[0]
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt
index c3b1d139a3..9220859e8b 100644
--- a/app/src/main/java/com/tangem/tap/common/images/Picasso.kt
+++ b/app/src/main/java/com/tangem/tap/common/images/Picasso.kt
@@ -3,9 +3,12 @@ package com.tangem.tap.common.images
import android.app.Application
import com.squareup.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
+import com.tangem.wallet.BuildConfig
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import timber.log.Timber
import java.io.File
import java.util.concurrent.TimeUnit
@@ -17,12 +20,16 @@ class PicassoHelper {
val picasso = Picasso.Builder(application)
.downloader(OkHttp3Downloader(getOkHttpForPicasso(application)))
.build()
+ picasso.isLoggingEnabled = BuildConfig.DEBUG
+ picasso.setIndicatorsEnabled(BuildConfig.DEBUG)
Picasso.setSingletonInstance(picasso)
}
private fun getOkHttpForPicasso(application: Application): OkHttpClient {
val okHttpBuilder = OkHttpClient.Builder()
okHttpBuilder.cache(Cache(File(application.filesDir, "artworks"), Long.MAX_VALUE))
+ okHttpBuilder.callTimeout(15000, TimeUnit.MILLISECONDS)
+
okHttpBuilder.addInterceptor { chain ->
val cacheControl = CacheControl.Builder()
.maxStale(KEEP_CACHE_MAX_DAYS, TimeUnit.DAYS)
@@ -33,7 +40,25 @@ class PicassoHelper {
.build()
chain.proceed(neverExpireRequest)
}
+ addDebugInterceptors(okHttpBuilder)
+
return okHttpBuilder.build()
}
+
+ private fun addDebugInterceptors(okHttpBuilder: OkHttpClient.Builder) {
+ if (!BuildConfig.DEBUG) return
+
+ val picassoInterceptor = HttpLoggingInterceptor(PicassoOkHttpLogger()).apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+ okHttpBuilder.addInterceptor(picassoInterceptor)
+ }
+
+ }
+}
+
+private class PicassoOkHttpLogger : HttpLoggingInterceptor.Logger {
+ override fun log(message: String) {
+ Timber.d(message)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt
new file mode 100644
index 0000000000..a95488d44f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt
@@ -0,0 +1,147 @@
+package com.tangem.tap.common.leapfrogWidget
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.view.View
+import android.view.animation.DecelerateInterpolator
+import android.view.animation.LinearInterpolator
+
+/**
+[REDACTED_AUTHOR]
+ */
+//fun View.setForeground(hasForegroundAtEnd: Boolean) {
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+// foreground = getForegroundDrawable()
+// }
+//}
+
+//fun View.getForegroundDrawable(): Drawable? {
+// return AppCompatResources.getDrawable(context, R.drawable.shape_rectangle_rounded_8_op_20)?.also {
+// it.alpha = 60
+// }
+//}
+
+typealias ProgressListener = (Int) -> Unit
+
+fun createProgressListener(duration: Long, listener: ProgressListener?): Animator {
+ val valueAnimator = ValueAnimator.ofInt(0, 100)
+ valueAnimator.duration = duration
+ valueAnimator.addUpdateListener { listener?.invoke((it.animatedValue as? Int) ?: 0) }
+ return valueAnimator
+}
+
+fun View.unfoldAnimation(animDuration: Long, properties: LeapViewProperties): AnimatorSet {
+ val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation)
+ translate.duration = animDuration
+ return AnimatorSet().apply { playTogether(translate) }
+}
+
+fun View.foldAnimation(animDuration: Long, properties: LeapViewProperties): AnimatorSet {
+ val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation)
+ translate.duration = animDuration
+
+ return AnimatorSet().apply {
+ playSequentially(translate)
+ }
+}
+
+fun View.leapAnimation(animDuration: Long, properties: LeapViewProperties, overLift: Float): AnimatorSet {
+ val halfDuration = animDuration / 2
+
+ val upTo = (height.toFloat() + overLift) * -1
+ val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo)
+ translateUp.duration = halfDuration
+ translateUp.interpolator = LinearInterpolator()
+
+ val scaleDuration = halfDuration - halfDuration / 5
+ val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale)
+ val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale)
+ val scale = AnimatorSet().apply {
+ startDelay = scaleDuration
+ duration = scaleDuration
+ interpolator = LinearInterpolator()
+ playTogether(scaleX, scaleY)
+ }
+
+ val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd, halfDuration, 100)
+ val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation)
+ translateDown.startDelay = halfDuration
+ translateDown.duration = halfDuration
+ translateDown.interpolator = LinearInterpolator()
+
+ return AnimatorSet().apply {
+ playTogether(translateUp, scale, elevation, translateDown)
+ }
+}
+
+fun View.leapBackAnimation(animDuration: Long, properties: LeapViewProperties, calculator: PropertyCalculator): AnimatorSet {
+ val halfDuration = animDuration / 2
+
+ val upTo = ((height).toFloat() - calculator.yTranslationFactor) * -1
+ val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo)
+ translateUp.duration = halfDuration
+ translateUp.interpolator = LinearInterpolator()
+
+ val scaleDuration = halfDuration - halfDuration / 5
+ val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale)
+ val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale)
+ val scale = AnimatorSet().apply {
+ startDelay = scaleDuration
+ duration = scaleDuration
+ interpolator = LinearInterpolator()
+ playTogether(scaleX, scaleY)
+ }
+
+ val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd, halfDuration, 100)
+ val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation)
+ translateDown.startDelay = halfDuration
+ translateDown.duration = halfDuration
+
+ return AnimatorSet().apply {
+ playTogether(translateUp, scale, elevation, translateDown)
+ }
+}
+
+fun View.pullUpAnimation(leapDuration: Long, properties: LeapViewProperties): AnimatorSet {
+ val pullDuration = leapDuration / 2
+ val delayDuration = leapDuration / 2
+
+ val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation)
+ val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale)
+ val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale)
+ val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd)
+
+ return AnimatorSet().apply {
+ startDelay = delayDuration
+ duration = pullDuration
+ interpolator = DecelerateInterpolator()
+ playTogether(translateUp, scaleX, scaleY, elevation)
+ }
+}
+
+fun View.pullDownAnimation(leapDuration: Long, properties: LeapViewProperties): AnimatorSet {
+ val pullDuration = leapDuration / 2
+ val delayDuration = leapDuration / 4
+
+ val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation)
+ val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale)
+ val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale)
+ val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd)
+
+ return AnimatorSet().apply {
+ startDelay = delayDuration
+ duration = pullDuration
+ interpolator = DecelerateInterpolator()
+ playTogether(translate, scaleX, scaleY, elevation)
+ }
+}
+
+fun View.elevationAnimator(start: Float, end: Float, startDelay: Long? = null, duration: Long? = null): Animator {
+ val elevationAnimator = ValueAnimator.ofFloat(start, end)
+ elevationAnimator.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: start }
+ startDelay?.let { elevationAnimator.startDelay = it }
+ duration?.let { elevationAnimator.duration = it }
+ return elevationAnimator
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt
new file mode 100644
index 0000000000..be2d63b664
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt
@@ -0,0 +1,144 @@
+package com.tangem.tap.common.leapfrogWidget
+
+import android.view.View
+
+/**
+[REDACTED_AUTHOR]
+ */
+class LeapView(
+ val view: View,
+ val index: Int,
+ position: Int,
+ private val maximalPosition: Int,
+ private val calculator: PropertyCalculator,
+) {
+ var state: LeapViewState
+ private set
+
+ val initialState: LeapViewState
+
+ init {
+ val initialProperties = createInitialProperty(position)
+ state = LeapViewState(index, position, position, initialProperties)
+ initialState = state.copy()
+ }
+
+ fun initView() {
+ applyState(initialState)
+ }
+
+ fun leap(): LeapFrogAnimation {
+ val previousPosition = state.currentPosition
+ val currentPosition = when (previousPosition) {
+ 0 -> maximalPosition
+ else -> state.currentPosition - 1
+ }
+
+ state = state.copy(
+ currentPosition = currentPosition,
+ previousPosition = previousPosition,
+ properties = createLeapAnimationProperty(previousPosition, currentPosition)
+ )
+
+ return when {
+ previousPosition == 0 && currentPosition == maximalPosition -> LeapFrogAnimation.LEAP
+ else -> LeapFrogAnimation.PULL
+ }
+ }
+
+ fun leapBack(): LeapFrogAnimation {
+ val previousPosition = state.currentPosition
+ val currentPosition = when (previousPosition) {
+ maximalPosition -> 0
+ else -> state.currentPosition + 1
+ }
+ state = state.copy(
+ currentPosition = currentPosition,
+ previousPosition = previousPosition,
+ properties = createLeapAnimationProperty(previousPosition, currentPosition)
+ )
+
+ return when {
+ previousPosition == maximalPosition && currentPosition == 0 -> LeapFrogAnimation.LEAP
+ else -> LeapFrogAnimation.PULL
+ }
+ }
+
+ fun fold() {
+ state = state.copy(properties = state.properties.toFold()
+ )
+ }
+
+ fun unfold() {
+ state = state.copy(properties = state.properties.toUnfold(calculator))
+ }
+
+ fun applyState(state: LeapViewState) {
+ this.state = state
+ setProperties(this.state.properties)
+ }
+
+ private fun setProperties(properties: LeapViewProperties) {
+ view.elevation = properties.elevationEnd
+ view.translationY = properties.yTranslation
+ view.scaleX = properties.scale
+ view.scaleY = properties.scale
+// initialProperties.hasForeground
+ }
+
+ private fun createInitialProperty(endPosition: Int): LeapViewProperties {
+ return LeapViewProperties(
+ endPosition,
+ endPosition,
+ calculator.scale(endPosition),
+ calculator.elevation(endPosition, maximalPosition + 1),
+ calculator.elevation(endPosition, maximalPosition + 1),
+ calculator.yTranslation(0),
+ calculator.hasForeground(endPosition),
+ )
+ }
+
+ private fun createLeapAnimationProperty(startPosition: Int, endPosition: Int): LeapViewProperties {
+ return LeapViewProperties(
+ startPosition,
+ endPosition,
+ calculator.scale(endPosition),
+ calculator.elevation(startPosition, maximalPosition + 1),
+ calculator.elevation(endPosition, maximalPosition + 1),
+ calculator.yTranslation(endPosition),
+ calculator.hasForeground(endPosition),
+ )
+ }
+
+ private fun LeapViewProperties.toUnfold(calculator: PropertyCalculator): LeapViewProperties {
+ return this.copy(yTranslation = calculator.yTranslation(this.positionEnd))
+ }
+
+ private fun LeapViewProperties.toFold(): LeapViewProperties {
+ return this.copy(yTranslation = 0f)
+ }
+
+ override fun toString(): String = "index: $index, position: ${state.currentPosition}, code: ${view.hashCode()}"
+
+}
+
+data class LeapViewState(
+ val index: Int,
+ val currentPosition: Int,
+ val previousPosition: Int,
+ val properties: LeapViewProperties
+)
+
+enum class LeapFrogAnimation {
+ LEAP, PULL
+}
+
+data class LeapViewProperties(
+ val positionStart: Int,
+ val positionEnd: Int,
+ val scale: Float,
+ val elevationStart: Float,
+ val elevationEnd: Float,
+ val yTranslation: Float,
+ val hasForeground: Boolean,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt
new file mode 100644
index 0000000000..e83f7b8b92
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt
@@ -0,0 +1,249 @@
+package com.tangem.tap.common.leapfrogWidget
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.FrameLayout
+import androidx.core.animation.doOnEnd
+import androidx.core.view.children
+import com.tangem.common.extensions.VoidCallback
+import com.tangem.tangem_sdk_new.extensions.dpToPx
+
+/**
+[REDACTED_AUTHOR]
+ */
+class LeapfrogWidget(
+ private val parentContainer: FrameLayout,
+ private val calculator: PropertyCalculator = PropertyCalculator(),
+) {
+ private val foldAnimationDuration = 300L
+ private val leapAnimationDuration = 500L
+ private val leapBackAnimationDuration = 500L
+
+ private val leapViews = mutableListOf()
+
+ private var foldUnfoldProgress: Int = 100
+ private var leapProgress: Int = 100
+ private var leapBackProgress: Int = 100
+
+ init {
+ calculator.setTranslationConverter { parentContainer.dpToPx(it) }
+ if (viewIsFullFledged()) {
+ val maxPosition = parentContainer.childCount - 1
+ parentContainer.children.forEachIndexed { index, view ->
+ val initialPosition = maxPosition - index
+ leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator))
+ }
+ }
+ }
+
+ /**
+ * Apply initial properties to the views
+ */
+ fun initViews() {
+ leapViews.forEach { it.initView() }
+ }
+
+ fun fold(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ val animator = foldAnimator(animate)
+ if (animator == null) {
+ onEnd()
+ } else {
+ animator.doOnEnd { onEnd() }
+ animator.start()
+ }
+ }
+
+ fun unfold(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ val animator = unfoldAnimator(animate)
+ if (animator == null) {
+ onEnd()
+ } else {
+ animator.doOnEnd { onEnd() }
+ animator.start()
+ }
+ }
+
+ fun foldAnimator(animate: Boolean = true): Animator? {
+ if (!canFoldUnfold()) return null
+
+ return createFoldUnfoldAnimator(true, animate, foldAnimationDuration)
+ }
+
+ fun unfoldAnimator(animate: Boolean = true): Animator? {
+ if (!canFoldUnfold()) return null
+
+ return createFoldUnfoldAnimator(false, animate, foldAnimationDuration)
+ }
+
+ private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, animate: Boolean, duration: Long): Animator {
+ val animatorsList = mutableListOf()
+ val animateDuration = if (animate) duration else 0
+ leapViews.forEach {
+ if (isFoldAnimation) {
+ it.fold()
+ animatorsList.add(it.view.foldAnimation(animateDuration, it.state.properties))
+ } else {
+ it.unfold()
+ animatorsList.add(it.view.unfoldAnimation(animateDuration, it.state.properties))
+ }
+ }
+ animatorsList.add(createProgressListener(animateDuration) { foldUnfoldProgress = it })
+
+ val animator = AnimatorSet()
+ animator.interpolator = AccelerateDecelerateInterpolator()
+ animator.playTogether(animatorsList.toList())
+ return animator
+ }
+
+ fun leap(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ if (!canLeap()) {
+ onEnd()
+ return
+ }
+
+ val duration = if (animate) leapAnimationDuration else 0
+ val animatorsList = mutableListOf()
+ leapViews.forEach {
+ when (it.leap()) {
+ LeapFrogAnimation.LEAP -> {
+ val overLift = calculator.overLift(leapViews.size)
+ animatorsList.add(it.view.leapAnimation(duration, it.state.properties, overLift))
+ }
+ LeapFrogAnimation.PULL -> {
+ animatorsList.add(it.view.pullUpAnimation(duration, it.state.properties))
+ }
+ }
+ }
+ animatorsList.add(createProgressListener(duration) { leapProgress = it })
+
+ val animator = AnimatorSet()
+ animator.playTogether(animatorsList.toList())
+ animator.doOnEnd { onEnd() }
+ animator.start()
+ }
+
+ fun leapBack(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ if (!canLeapBack()) {
+ onEnd()
+ return
+ }
+
+ val duration = if (animate) leapBackAnimationDuration else 0
+ val animatorsList = mutableListOf()
+ leapViews.forEach {
+ when (it.leapBack()) {
+ LeapFrogAnimation.LEAP -> {
+ animatorsList.add(it.view.leapBackAnimation(duration, it.state.properties, calculator))
+ }
+ LeapFrogAnimation.PULL -> {
+ animatorsList.add(it.view.pullDownAnimation(duration, it.state.properties))
+ }
+ }
+ }
+ animatorsList.add(createProgressListener(duration) { leapBackProgress = it })
+
+ val animator = AnimatorSet()
+ animator.playTogether(animatorsList.toList())
+ animator.doOnEnd { onEnd() }
+ animator.start()
+ }
+
+ fun getViewsCount(): Int = parentContainer.childCount
+
+ fun getViewPositionByIndex(index: Int): Int = leapViews.first { it.index == index }.state.currentPosition
+
+ fun getViewByPosition(position: Int): LeapView = leapViews.first { it.state.currentPosition == position }
+
+ fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(leapViews.map { it.state })
+
+ fun applyState(state: LeapfrogWidgetState) {
+ state.leapViewStates.forEach { viewState ->
+ leapViews.firstOrNull { it.index == viewState.index }?.applyState(viewState)
+ }
+ }
+
+ private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1
+
+ private fun canFoldUnfold(): Boolean {
+ return when {
+ !viewIsFullFledged() -> false
+ foldUnfoldInProgress() -> false
+ leapInProgress() || leapBackInProgress() -> false
+ else -> true
+ }
+ }
+
+ private fun canLeap(): Boolean {
+ return when {
+ !viewIsFullFledged() -> false
+ leapBackInProgress() -> false
+ leapProgress < 70 -> false
+ else -> true
+ }
+ }
+
+ private fun canLeapBack(): Boolean {
+ return when {
+ !viewIsFullFledged() -> false
+ leapInProgress() -> false
+ leapBackProgress < 70 -> false
+ else -> true
+ }
+ }
+
+ private fun foldUnfoldInProgress(): Boolean = foldUnfoldProgress != 100
+ private fun leapInProgress(): Boolean = leapProgress != 100
+ private fun leapBackInProgress(): Boolean = leapBackProgress != 100
+}
+
+data class LeapfrogWidgetState(
+ val leapViewStates: List
+)
+
+class PropertyCalculator(
+ val elevationFactor: Float = 1f,
+ val decreaseScaleFactor: Float = 0.15f,
+ val yTranslationFactor: Float = 35f,
+ val decreaseOverLiftingFactor: Float = 0.85f,
+) {
+ // 0 view it is view on the top of stack, but it is last in parent.children
+
+ private var translationValueConverter: ((Float) -> Float)? = null
+ fun setTranslationConverter(converter: (Float) -> Float) {
+ translationValueConverter = converter
+ }
+
+ fun scale(position: Int): Float {
+ return 1f - decreaseScaleFactor * position
+ }
+
+ fun elevation(position: Int, count: Int): Float {
+ return elevationFactor * (count - 1 - position)
+ }
+
+ fun yTranslation(position: Int): Float {
+ val yTranslation = yTranslationFactor * position
+ return convertTranslation(yTranslation)
+ }
+
+ fun hasForeground(position: Int): Boolean {
+ return position != 0
+ }
+
+ fun overLift(viewCount: Int): Float {
+ val initialOverLift = when {
+ yTranslationFactor < 0f -> yTranslationFactor * decreaseOverLiftingFactor * viewCount * -1
+ else -> 10f
+ }
+ val scaleOverLift = when {
+ decreaseScaleFactor == 0f -> initialOverLift
+ else -> initialOverLift * decreaseOverLiftingFactor - initialOverLift * decreaseScaleFactor
+ }
+ return convertTranslation(scaleOverLift)
+ }
+
+ private fun convertTranslation(value: Float): Float {
+ return translationValueConverter?.invoke(value) ?: value
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
new file mode 100644
index 0000000000..1a41ff35be
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
@@ -0,0 +1,68 @@
+package com.tangem.tap.common.leapfrogWidget
+
+import android.os.Bundle
+import android.view.View
+import android.widget.FrameLayout
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.tangem.tap.domain.twins.TwinsCardWidget
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.test_leapfrog_fragment.*
+
+class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
+
+ private lateinit var twinsCardWidget: TwinsCardWidget
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val inflater = TransitionInflater.from(requireContext())
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val leapfrogContainer: FrameLayout = view.findViewById(R.id.leapfrog_views_container)
+ val leapfrog = LeapfrogWidget(leapfrogContainer)
+ twinsCardWidget = TwinsCardWidget(leapfrog) { 200f }
+
+ btn_twin_welcome.setOnClickListener {
+ twinsCardWidget.toWelcome()
+ }
+ btn_twin_to_leapfrog.setOnClickListener {
+ twinsCardWidget.toLeapfrog()
+ }
+ btn_twin_activate.setOnClickListener {
+ twinsCardWidget.toActivate()
+ }
+
+
+ btn_lp_init.setOnClickListener {
+ leapfrog.initViews()
+ }
+ btn_lp_unfold.setOnClickListener {
+ leapfrog.unfold()
+ }
+ btn_lp_fold.setOnClickListener {
+ leapfrog.fold()
+ }
+ btn_lp_leap.setOnClickListener {
+ leapfrog.leap()
+ }
+ btn_lp_leap_back.setOnClickListener {
+ leapfrog.leapBack()
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ leapfrogWidgetState?.let { twinsCardWidget.leapfrogWidget.applyState(it) }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ leapfrogWidgetState = twinsCardWidget.leapfrogWidget.getState()
+ }
+}
+
+private var leapfrogWidgetState: LeapfrogWidgetState? = null
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
index 57ed1eaf2c..d797ff9010 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -6,6 +6,10 @@ import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
import com.tangem.tap.features.home.redux.HomeReducer
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
+import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
+import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
@@ -16,15 +20,19 @@ fun appReducer(action: Action, state: AppState?): AppState {
if (action is AppAction.RestoreState) return action.state
return AppState(
- navigationState = NavigationReducer.reduce(action, state),
- globalState = globalReducer(action, state),
- homeState = HomeReducer.reduce(action, state),
- walletState = WalletReducer.reduce(action, state),
- sendState = SendScreenReducer.reduce(action, state.sendState),
- detailsState = DetailsReducer.reduce(action, state),
- disclaimerState = DisclaimerReducer.reduce(action, state),
- tokensState = TokensReducer.reduce(action, state),
- walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState)
+ navigationState = NavigationReducer.reduce(action, state),
+ globalState = globalReducer(action, state),
+ homeState = HomeReducer.reduce(action, state),
+ onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
+ onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
+ onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
+ walletState = WalletReducer.reduce(action, state),
+ twinCardsState = TwinCardsReducer.reduce(action, state),
+ sendState = SendScreenReducer.reduce(action, state.sendState),
+ detailsState = DetailsReducer.reduce(action, state),
+ disclaimerState = DisclaimerReducer.reduce(action, state),
+ tokensState = TokensReducer.reduce(action, state),
+ walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState)
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
index 02deb7d980..39b221f517 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.common.redux
+import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState
-import com.tangem.tap.common.redux.global.globalMiddleware
import com.tangem.tap.common.redux.navigation.NavigationState
import com.tangem.tap.common.redux.navigation.navigationMiddleware
import com.tangem.tap.features.details.redux.DetailsMiddleware
@@ -12,7 +12,15 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
import com.tangem.tap.features.home.redux.HomeMiddleware
import com.tangem.tap.features.home.redux.HomeState
-import com.tangem.tap.features.send.redux.middlewares.sendMiddleware
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
+import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsMiddleware
+import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsMiddleware
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
+import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
+import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
+import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
@@ -25,7 +33,11 @@ data class AppState(
val navigationState: NavigationState = NavigationState(),
val globalState: GlobalState = GlobalState(),
val homeState: HomeState = HomeState(),
+ val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
+ val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
+ val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
val walletState: WalletState = WalletState(),
+ val twinCardsState: TwinCardsState = TwinCardsState(),
val sendState: SendState = SendState(),
val detailsState: DetailsState = DetailsState(),
val disclaimerState: DisclaimerState = DisclaimerState(),
@@ -36,14 +48,19 @@ data class AppState(
companion object {
fun getMiddleware(): List> {
return listOf(
- logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware,
- HomeMiddleware().homeMiddleware,
- WalletMiddleware().walletMiddleware,
- sendMiddleware,
- DetailsMiddleware().detailsMiddleware,
- DisclaimerMiddleware().disclaimerMiddleware,
- TokensMiddleware().tokensMiddleware,
- WalletConnectMiddleware().walletConnectMiddleware
+ logMiddleware, navigationMiddleware, notificationsMiddleware,
+ GlobalMiddleware.handler,
+ HomeMiddleware.handler,
+ OnboardingNoteMiddleware.handler,
+ OnboardingWalletMiddleware.handler,
+ OnboardingOtherCardsMiddleware.handler,
+ WalletMiddleware().walletMiddleware,
+ TwinCardsMiddleware.handler,
+ SendMiddleware().sendMiddleware,
+ DetailsMiddleware().detailsMiddleware,
+ DisclaimerMiddleware().disclaimerMiddleware,
+ TokensMiddleware().tokensMiddleware,
+ WalletConnectMiddleware().walletConnectMiddleware
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
index 8a55bcfafe..2ace551205 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
@@ -9,6 +9,7 @@ import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.assembleErrors
import com.tangem.tap.notificationsHandler
+import com.tangem.wallet.BuildConfig
import org.rekotlin.Action
import org.rekotlin.Middleware
import java.lang.ref.WeakReference
@@ -63,23 +64,29 @@ fun getMessageString(context: Context, message: Int, args: List?): String {
val notificationsMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
- when (action) {
- is NotificationAction -> notificationsHandler?.showNotification(action.messageResource)
- is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
- is ErrorAction -> {
- when (action.error) {
- is MultiMessageError -> {
- val multiError = action.error as MultiMessageError
- notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder)
- }
- else -> {
- val args = (action.error as? ArgError)?.args ?: listOf()
- notificationsHandler?.showNotification(action.error.localizedMessage, args)
- }
- }
+ handleNotificationAction(action)
+ next(action)
+ }
+ }
+}
+
+private fun handleNotificationAction(action: Action) {
+ if (action is Debug && !BuildConfig.DEBUG) return
+
+ when (action) {
+ is NotificationAction -> notificationsHandler?.showNotification(action.messageResource)
+ is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
+ is ErrorAction -> {
+ when (action.error) {
+ is MultiMessageError -> {
+ val multiError = action.error as MultiMessageError
+ notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder)
+ }
+ else -> {
+ val args = (action.error as? ArgError)?.args ?: listOf()
+ notificationsHandler?.showNotification(action.error.messageResource, args)
}
}
- next(action)
}
}
}
@@ -94,4 +101,10 @@ interface NotificationAction : Action {
interface ErrorAction : Action {
val error: TapError
-}
\ No newline at end of file
+}
+
+// Processed only in the debug builds
+interface Debug
+interface DebugNotification : Debug, NotificationAction
+interface DebugToastNotification : Debug, ToastNotificationAction
+interface DebugErrorAction : Debug, ErrorAction
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
new file mode 100644
index 0000000000..bcde7dae3b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
@@ -0,0 +1,17 @@
+package com.tangem.tap.common.redux
+
+import com.tangem.tap.features.wallet.redux.AddressData
+import com.tangem.tap.features.wallet.redux.Currency
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface StateDialog
+
+sealed class AppDialog : StateDialog {
+ object ScanFailsDialog : AppDialog()
+ data class AddressInfoDialog(
+ val currency: Currency,
+ val addressData: AddressData,
+ ) : AppDialog()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index 419fa5737d..a1e1e7b2b1 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
@@ -2,24 +2,55 @@ package com.tangem.tap.common.redux.global
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
+import com.tangem.common.core.TangemError
+import com.tangem.tap.common.redux.*
+import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.EmailData
import com.tangem.tap.features.feedback.FeedbackManager
+import com.tangem.tap.network.moonpay.MoonpayStatus
import org.rekotlin.Action
sealed class GlobalAction : Action {
+ // notifications
+ data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction
+ data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction
+ data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
+ data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
+
+ // android resources
+ data class SetResources(val resources: AndroidResources) : GlobalAction()
+
+ // dialogs
+ data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
+ object HideDialog : GlobalAction()
+
+ sealed class Onboarding {
+ data class Start(val scanResponse: ScanResponse) : GlobalAction()
+ object Stop : GlobalAction()
+ }
+
+ data class ScanCard(
+ val onSuccess: ((ScanResponse) -> Unit)? = null,
+ val onFailure: ((TangemError) -> Unit)? = null,
+ val messageResId: Int? = null,
+ ) : GlobalAction()
+
object ScanFailsCounter {
- data class ChooseBehavior(val result: CompletionResult) : GlobalAction()
+ data class ChooseBehavior(val result: CompletionResult) : GlobalAction()
object Reset : GlobalAction()
object Increment : GlobalAction()
}
- data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction()
+ data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction()
+
+ data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction()
+
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
object RestoreAppCurrency : GlobalAction() {
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
@@ -41,6 +72,7 @@ sealed class GlobalAction : Action {
data class SendFeedback(val emailData: EmailData) : GlobalAction()
data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction()
- data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
- object HideDialog : GlobalAction()
+ object GetMoonPayStatus : GlobalAction() {
+ data class Success(val moonPayStatus: MoonpayStatus) : GlobalAction()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index 2f79a5bbcc..06cc349269 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
@@ -1,17 +1,33 @@
package com.tangem.tap.common.redux.global
-import com.tangem.TangemSdkError
import com.tangem.common.CompletionResult
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.services.Result
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.dispatchDialogShow
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.withMainContext
+import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.network.moonpay.MoonpayService
import com.tangem.tap.preferencesStorage
+import com.tangem.tap.scope
import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
+import kotlinx.coroutines.launch
import org.rekotlin.Middleware
+import timber.log.Timber
-val globalMiddleware: Middleware = { dispatch, appState ->
+class GlobalMiddleware {
+ companion object {
+ val handler = globalMiddlewareHandler
+ }
+}
+
+private val globalMiddlewareHandler: Middleware = { dispatch, appState ->
{ nextDispatch ->
{ action ->
when (action) {
@@ -22,8 +38,7 @@ val globalMiddleware: Middleware = { dispatch, appState ->
if (action.result.error is TangemSdkError.UserCancelled) {
store.dispatch(GlobalAction.ScanFailsCounter.Increment)
if (store.state.globalState.scanCardFailsCounter >= 2) {
- store.dispatch(HomeAction.ShowDialog.ScanFails)
- store.dispatch(WalletAction.ShowDialog.ScanFails)
+ store.dispatchDialogShow(AppDialog.ScanFailsDialog)
}
} else {
store.dispatch(GlobalAction.ScanFailsCounter.Reset)
@@ -33,7 +48,7 @@ val globalMiddleware: Middleware = { dispatch, appState ->
}
is GlobalAction.RestoreAppCurrency -> {
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
- preferencesStorage.getAppCurrency()
+ preferencesStorage.getAppCurrency()
))
}
is GlobalAction.HideWarningMessage -> {
@@ -58,7 +73,36 @@ val globalMiddleware: Middleware = { dispatch, appState ->
}
is GlobalAction.UpdateFeedbackInfo -> {
store.state.globalState.feedbackManager?.infoHolder
- ?.setWalletsInfo(action.walletManagers)
+ ?.setWalletsInfo(action.walletManagers)
+ }
+ is GlobalAction.GetMoonPayStatus -> {
+ val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey
+ if (apiKey != null) {
+ scope.launch {
+ val result = MoonpayService().getMoonpayStatus(apiKey)
+ when (result) {
+ is Result.Success -> {
+ store.dispatchOnMain(GlobalAction.GetMoonPayStatus.Success(result.data))
+ }
+ is Result.Failure -> Timber.e(result.error)
+ }
+ }
+ }
+ }
+ is GlobalAction.ScanCard -> {
+ scope.launch {
+ val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId)
+ store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
+ withMainContext {
+ when (result) {
+ is CompletionResult.Success -> {
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
+ action.onSuccess?.invoke(result.data)
+ }
+ is CompletionResult.Failure -> action.onFailure?.invoke(result.error)
+ }
+ }
+ }
}
}
nextDispatch(action)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
index b7208034f7..993830329a 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
@@ -1,8 +1,9 @@
package com.tangem.tap.common.redux.global
-import com.tangem.commands.wallet.WalletIndex
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.features.details.redux.SecurityOption
+import com.tangem.tap.features.onboarding.OnboardingManager
+import com.tangem.tap.network.moonpay.MoonpayStatus
+import com.tangem.tap.preferencesStorage
import org.rekotlin.Action
fun globalReducer(action: Action, state: AppState): GlobalState {
@@ -12,6 +13,17 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
val globalState = state.globalState
return when (action) {
+ is GlobalAction.SetResources -> {
+ globalState.copy(resources = action.resources)
+ }
+ is GlobalAction.Onboarding.Start -> {
+ val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage
+ val onboardingState = OnboardingManager(action.scanResponse, usedCardsPrefStorage)
+ globalState.copy(onboardingManager = onboardingState)
+ }
+ is GlobalAction.Onboarding.Stop -> {
+ globalState.copy(onboardingManager = null)
+ }
is GlobalAction.ScanFailsCounter.Increment -> {
globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
}
@@ -19,7 +31,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState.copy(scanCardFailsCounter = 0)
}
is GlobalAction.SaveScanNoteResponse ->
- globalState.copy(scanNoteResponse = action.scanNoteResponse)
+ globalState.copy(scanResponse = action.scanResponse)
is GlobalAction.ChangeAppCurrency -> {
globalState.copy(appCurrency = action.appCurrency)
}
@@ -30,39 +42,15 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
- is GlobalAction.UpdateSecurityOptions -> {
- val card = when (action.securityOption) {
- SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy(
- isPin1Default = true, isPin2Default = true
- )
- SecurityOption.PassCode -> globalState.scanNoteResponse?.card?.copy(
- isPin1Default = true, isPin2Default = false
- )
- SecurityOption.AccessCode -> globalState.scanNoteResponse?.card?.copy(
- isPin1Default = false, isPin2Default = true
- )
- }
- if (card != null) {
- globalState.copy(scanNoteResponse = globalState.scanNoteResponse?.copy(card = card))
- } else {
- globalState
- }
- }
is GlobalAction.UpdateWalletSignedHashes -> {
- val wallet = globalState.scanNoteResponse?.card
- ?.wallet(WalletIndex.PublicKey(action.walletPublicKey))
- ?.copy(
- signedHashes = action.walletSignedHashes,
- remainingSignatures = action.remainingSignatures
- )
- val card = globalState.scanNoteResponse?.card
- wallet?.let { globalState.scanNoteResponse.card.updateWallet(wallet) }
+ val card = globalState.scanResponse?.card ?: return globalState
+ val wallet = card.wallet(action.walletPublicKey) ?: return globalState
- if (card != null) {
- globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card))
- } else {
- globalState
- }
+ val newCardInstance = card.updateWallet(wallet.copy(
+ totalSignedHashes = action.walletSignedHashes,
+ remainingSignatures = action.remainingSignatures
+ ))
+ globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
}
is GlobalAction.SetFeedbackManager -> {
globalState.copy(feedbackManager = action.feedbackManager)
@@ -73,6 +61,23 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.HideDialog -> {
globalState.copy(dialog = null)
}
+ is GlobalAction.GetMoonPayStatus.Success -> {
+ val fiatExchangeIsEnabled = globalState.configManager?.config?.isTopUpEnabled ?: false
+ val moonpayStatus = if (fiatExchangeIsEnabled) {
+ action.moonPayStatus
+ } else {
+ MoonpayStatus(
+ isBuyAllowed = false,
+ isSellAllowed = false,
+ availableToBuy = emptyList(),
+ availableToSell = emptyList()
+ )
+ }
+ globalState.copy(moonpayStatus = moonpayStatus)
+ }
+ is GlobalAction.SetIfCardVerifiedOnline ->
+ globalState.copy(cardVerifiedOnline = action.verified)
+
else -> globalState
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index c7e173310b..484142c611 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
@@ -1,32 +1,43 @@
package com.tangem.tap.common.redux.global
-import com.tangem.commands.common.network.TangemService
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
+import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.feedback.FeedbackManager
+import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
+import com.tangem.tap.network.moonpay.MoonpayStatus
import org.rekotlin.StateType
data class GlobalState(
- val scanNoteResponse: ScanNoteResponse? = null,
- val tapWalletManager: TapWalletManager = TapWalletManager(),
- val payIdManager: PayIdManager = PayIdManager(),
- val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
- val tangemService: TangemService = TangemService(),
- val configManager: ConfigManager? = null,
- val warningManager: WarningMessagesManager? = null,
- val feedbackManager: FeedbackManager? = null,
- val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
- val scanCardFailsCounter: Int = 0,
- val dialog: StateDialog? = null
+ val scanResponse: ScanResponse? = null,
+ val onboardingManager: OnboardingManager? = null,
+ val cardVerifiedOnline: Boolean = false,
+ val tapWalletManager: TapWalletManager = TapWalletManager(),
+ val payIdManager: PayIdManager = PayIdManager(),
+ val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
+ val configManager: ConfigManager? = null,
+ val warningManager: WarningMessagesManager? = null,
+ val feedbackManager: FeedbackManager? = null,
+ val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
+ val scanCardFailsCounter: Int = 0,
+ val dialog: StateDialog? = null,
+ val moonpayStatus: MoonpayStatus? = null,
+ val resources: AndroidResources = AndroidResources(),
) : StateType
+
+data class AndroidResources(
+ val strings: RString = RString()
+) {
+ data class RString(
+ val addressWasCopied: Int = -1,
+ val walletIsNotEmpty: Int = -1
+ )
+}
typealias CryptoCurrencyName = String
-typealias FiatCurrencyName = String
-
-
-interface StateDialog
\ No newline at end of file
+typealias FiatCurrencyName = String
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt
new file mode 100644
index 0000000000..69547d020f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt
@@ -0,0 +1,35 @@
+package com.tangem.tap.common.redux.navigation
+
+import android.view.View
+import androidx.transition.TransitionSet
+import java.lang.ref.WeakReference
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class FragmentShareTransition(
+ val shareElements: List,
+ val enterTransitionSet: TransitionSet,
+ val exitTransitionSet: TransitionSet
+)
+
+/**
+ * For ease of use, the name is used as transitionName\name into the FragmentTransaction.addSharedElement
+ */
+class ShareElement(view: View, name: String? = null) {
+
+ val wView: WeakReference
+ val elementName: String
+
+ init {
+ name?.let { view.transitionName = it }
+ elementName = view.transitionName
+ ?: throw UnsupportedOperationException("ShareElement require the name")
+ wView = WeakReference(view)
+ }
+
+ companion object {
+ val imvFrontCard = "imv_front_card"
+ val imvBackCard = "imv_back_card"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt
index 5cbdb915f6..214a034270 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt
@@ -5,11 +5,18 @@ import org.rekotlin.Action
import java.lang.ref.WeakReference
sealed class NavigationAction : Action {
- data class NavigateTo(val screen: AppScreen, val addToBackstack: Boolean = true) :
- NavigationAction()
+ data class NavigateTo(
+ val screen: AppScreen,
+ val fragmentShareTransition: FragmentShareTransition? = null,
+ val addToBackstack: Boolean = true
+ ) : NavigationAction()
data class PopBackTo(val screen: AppScreen? = null) : NavigationAction()
+ data class OpenUrl(val url: String) : NavigationAction()
+
+ data class Share(val data: String) : NavigationAction()
+
data class ActivityCreated(val activity: WeakReference) : NavigationAction()
object ActivityDestroyed : NavigationAction()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
index 8c4d5e9fea..e8e0168455 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
@@ -1,25 +1,40 @@
package com.tangem.tap.common.redux.navigation
+import com.tangem.tap.common.CustomTabsManager
import com.tangem.tap.common.extensions.openFragment
import com.tangem.tap.common.extensions.popBackTo
+import com.tangem.tap.common.extensions.shareText
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.store
import org.rekotlin.Middleware
val navigationMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
if (action is NavigationAction) {
- val navState = store.state.navigationState
+ val navState = state()?.navigationState
when (action) {
is NavigationAction.NavigateTo -> {
- navState.activity?.get()?.openFragment(action.screen, action.addToBackstack)
+ navState?.activity?.get()?.openFragment(
+ action.screen,
+ action.addToBackstack,
+ action.fragmentShareTransition
+ )
}
is NavigationAction.PopBackTo -> {
if (action.screen == AppScreen.Home) {
- navState.activity?.get()?.popBackTo(null, true)
+ navState?.activity?.get()?.popBackTo(null, true)
} else {
- navState.activity?.get()?.popBackTo(action.screen)
+ navState?.activity?.get()?.popBackTo(action.screen)
+ }
+ }
+ is NavigationAction.OpenUrl -> {
+ navState?.activity?.get()?.let {
+ CustomTabsManager().openUrl(action.url, it)
+ }
+ }
+ is NavigationAction.Share -> {
+ navState?.activity?.get()?.let {
+ it.shareText(action.data)
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
index a4ce784435..e742aa2e6b 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
@@ -20,12 +20,12 @@ private fun internalReduce(action: Action, state: AppState): NavigationState {
navState.copy(backStack = navState.backStack + navigationAction.screen)
}
is NavigationAction.PopBackTo -> {
- val screen =
- navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen()
+ val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen()
val index = navState.backStack.lastIndexOf(screen) + 1
state.navigationState.copy(backStack = navState.backStack.subList(0, index))
}
is NavigationAction.ActivityCreated -> navState.copy(activity = navigationAction.activity)
is NavigationAction.ActivityDestroyed -> navState.copy(activity = null)
+ else -> navState
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
index ed59b9b2d5..61e3d263da 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
@@ -10,7 +10,13 @@ data class NavigationState(
) : StateType
enum class AppScreen {
- Home, Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
- CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding, AddTokens, WalletConnectSessions,
+ Home,
+ Disclaimer,
+ OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
+ Wallet, WalletDetails,
+ Send,
+ Details, DetailsConfirm, DetailsSecurity,
+ AddTokens,
+ WalletConnectSessions,
QrScan
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt
index c6cb5f22bd..b4eacfff4e 100644
--- a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt
@@ -5,6 +5,7 @@ import android.view.View
import com.google.android.material.button.MaterialButton
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
+import com.tangem.tap.features.wallet.redux.ProgressState
/**
[REDACTED_AUTHOR]
@@ -12,7 +13,7 @@ import com.tangem.tap.common.extensions.show
open class IndeterminateProgressButtonWidget(
private val button: MaterialButton,
private val progress: View,
- initialState: ProgressState = ProgressState.None
+ initialState: ProgressState = ProgressState.Done
) : ViewStateWidget {
private var initialButtonText: CharSequence = button.text
@@ -23,12 +24,14 @@ open class IndeterminateProgressButtonWidget(
changeState(initialState)
}
+ override val mainView: View = button
+
override fun changeState(state: WidgetState) {
val progressState = state as? ProgressState ?: return
when (progressState) {
- is ProgressState.None -> switchToNone()
- is ProgressState.Progress -> switchToProgress()
+ ProgressState.Done, ProgressState.Error -> switchToNone()
+ ProgressState.Loading -> switchToProgress()
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt
new file mode 100644
index 0000000000..277f839c05
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt
@@ -0,0 +1,101 @@
+package com.tangem.tap.common.toggleWidget
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.*
+import android.widget.ViewSwitcher
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class RefreshBalanceWidget(
+ private val root: ViewGroup
+) : ViewStateWidget {
+
+ var isShowing: Boolean? = null
+ private set
+
+ override val mainView: View = root.findViewById(R.id.btn_refresh_balance)
+
+
+ private val viewSwitcher: ViewSwitcher by lazy { mainView.findViewById(R.id.switcher_refresh_arrow_to_progress) }
+
+ private val arrowView = mainView.findViewById(R.id.imv_arrow_refresh)
+
+ private var progressViewAnimation: Animation? = null
+
+ init {
+ viewSwitcher.inAnimation = AlphaAnimation(0f, 1f).apply {
+ duration = 400
+ interpolator = LinearInterpolator()
+ }
+ viewSwitcher.outAnimation = AlphaAnimation(1f, 0f).apply {
+ duration = 400
+ interpolator = LinearInterpolator()
+ }
+ }
+
+ override fun changeState(state: WidgetState) {
+ val progressState = state as? ProgressState ?: return
+
+ val currentStateByView = getState()
+ when {
+ currentStateByView == progressState -> return
+ currentStateByView == ProgressState.Done -> if (progressState == ProgressState.Error) return
+ }
+
+ animateState(progressState)
+ viewSwitcher.showNext()
+ }
+
+ private fun animateState(state: ProgressState) {
+ when (state) {
+ ProgressState.Done, ProgressState.Error -> {
+ progressViewAnimation?.cancel()
+ progressViewAnimation = null
+ }
+ ProgressState.Loading -> {
+ progressViewAnimation = RotateAnimation(
+ 0f, 360f,
+ Animation.RELATIVE_TO_SELF, 0.5f,
+ Animation.RELATIVE_TO_SELF, 0.5f
+ )
+ progressViewAnimation?.duration = 700
+ progressViewAnimation?.interpolator = AccelerateInterpolator()
+ progressViewAnimation?.repeatCount = -1
+ progressViewAnimation?.let { arrowView.startAnimation(it) }
+ }
+ }
+ }
+
+ fun show(show: Boolean) {
+ if (show == isShowing) return
+
+ isShowing = show
+ if (show) {
+ mainView.startAnimation(ShowAnimation())
+ } else {
+ mainView.startAnimation(AlphaAnimation(1f, 0f).apply { fillAfter = true })
+ }
+ }
+
+ private fun isArrowRefreshActive(): Boolean = viewSwitcher.currentView.id == R.id.fl_arrow_refresh_container
+
+ private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading
+}
+
+class ShowAnimation : AnimationSet(true) {
+ init {
+ addAnimation(ScaleAnimation(
+ 0f, 1f,
+ 0f, 1f,
+ Animation.RELATIVE_TO_SELF, 0.5f,
+ Animation.RELATIVE_TO_SELF, 0.5f)
+ )
+ addAnimation(AlphaAnimation(0f, 1f))
+ duration = 300
+ interpolator = AnticipateOvershootInterpolator()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt
index 246268de7a..48ba0a7291 100644
--- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.toggleWidget
+import android.view.View
+
/**
[REDACTED_AUTHOR]
@@ -7,10 +9,6 @@ package com.tangem.tap.common.toggleWidget
interface WidgetState
interface ViewStateWidget {
+ val mainView: View
fun changeState(state: WidgetState)
-}
-
-sealed class ProgressState : WidgetState {
- object None : ProgressState()
- data class Progress(val progress: Int = 0) : ProgressState()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt
new file mode 100644
index 0000000000..32785f0147
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt
@@ -0,0 +1,44 @@
+package com.tangem.tap.common.transitions
+
+import androidx.transition.*
+
+/**
+[REDACTED_AUTHOR]
+ */
+class FrontCardEnterTransition : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER;
+ addTransition(Fade())
+ addTransition(ChangeImageTransform()) // изменения внутри ImageView
+ addTransition(ChangeTransform()) // изменение размеров, углов накона
+ addTransition(ChangeBounds()) // изменение положения
+ }
+}
+
+class FrontCardExitTransition : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER;
+ addTransition(Fade())
+ addTransition(ChangeImageTransform())
+ addTransition(ChangeTransform())
+ addTransition(ChangeBounds())
+ }
+}
+
+class HomeToOnboardingTransition : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER;
+ addTransition(Fade())
+ addTransition(ChangeTransform())
+ addTransition(ChangeBounds())
+ }
+}
+
+class InternalNoteLayoutTransition : TransitionSet() {
+ init {
+ ordering = ORDERING_TOGETHER;
+ addTransition(ChangeTransform())
+ addTransition(ChangeBounds())
+ addTransition(Fade())
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
index a6e329ca04..cfd5c2b5c2 100644
--- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.Blockchain
-import com.tangem.commands.common.network.Result
+import com.tangem.common.services.Result
import com.tangem.tap.network.payid.PayIdVerifyService
import com.tangem.tap.network.payid.VerifyPayIdResponse
import kotlinx.coroutines.Dispatchers
diff --git a/app/src/main/java/com/tangem/tap/domain/ProductType.kt b/app/src/main/java/com/tangem/tap/domain/ProductType.kt
new file mode 100644
index 0000000000..07cbea8905
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/ProductType.kt
@@ -0,0 +1,8 @@
+package com.tangem.tap.domain
+
+/**
+[REDACTED_AUTHOR]
+ */
+enum class ProductType {
+ Note, Wallet, Twins, Other
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
index af401f69bc..27cb46c500 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -1,88 +1,110 @@
package com.tangem.tap.domain
-import androidx.activity.ComponentActivity
-import com.tangem.*
-import com.tangem.commands.CommandResponse
-import com.tangem.commands.PinType
-import com.tangem.commands.SetPinCommand
-import com.tangem.commands.SetPinResponse
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.CardType
-import com.tangem.commands.wallet.PurgeWalletCommand
-import com.tangem.commands.wallet.PurgeWalletResponse
+import CreateProductWalletAndRescanTask
+import android.content.Context
+import com.tangem.Message
+import com.tangem.TangemSdk
+import com.tangem.common.CardFilter
import com.tangem.common.CompletionResult
-import com.tangem.common.TangemSdkConstants
-import com.tangem.common.extensions.calculateSha256
-import com.tangem.tangem_sdk_new.extensions.init
+import com.tangem.common.SuccessResponse
+import com.tangem.common.card.Card
+import com.tangem.common.card.FirmwareVersion
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.Config
+import com.tangem.common.core.TangemSdkError
+import com.tangem.operations.CommandResponse
+import com.tangem.operations.pins.CheckUserCodesCommand
+import com.tangem.operations.pins.CheckUserCodesResponse
+import com.tangem.operations.pins.SetUserCodeCommand
+import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsHandler
-import com.tangem.tap.domain.extensions.getDefaultWalletIndex
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
-import com.tangem.tap.domain.tasks.ScanNoteResponse
-import com.tangem.tap.domain.tasks.ScanNoteTask
-import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.domain.tasks.product.ScanProductTask
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
-class TangemSdkManager(val activity: ComponentActivity) {
- private val tangemSdk = TangemSdk.init(
- activity, Config(cardFilter = CardFilter(EnumSet.allOf(CardType::class.java)))
- )
+class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
- suspend fun scanNote(
- analyticsHandler: AnalyticsHandler, messageRes: Int? = null
- ): CompletionResult {
+ suspend fun scanProduct(
+ analyticsHandler: AnalyticsHandler,
+ messageRes: Int? = null,
+ ): CompletionResult {
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
- return runTaskAsyncReturnOnMain(ScanNoteTask(),
- initialMessage = Message(
- activity.getString(messageRes ?: R.string.initial_message_scan_header)
- ))
+
+ val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
+ return runTaskAsyncReturnOnMain(ScanProductTask(), null, message)
+ .also { sendScanFailuresToAnalytics(analyticsHandler, it) }
+ }
+
+ suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult {
+ return runTaskAsync(
+ CreateProductWalletAndRescanTask(scanResponse.productType),
+ scanResponse.card.cardId,
+ Message(context.getString(R.string.initial_message_create_wallet_body))
+ )
+ }
+
+ private fun sendScanFailuresToAnalytics(
+ analyticsHandler: AnalyticsHandler,
+ result: CompletionResult
+ ) {
+ if (result is CompletionResult.Failure && result.error is TangemSdkError) {
+ (result.error as? TangemSdkError)?.let { error ->
+ analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan)
+ }
+ }
}
suspend fun createWallet(cardId: String?): CompletionResult {
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId,
- initialMessage = Message(activity.getString(R.string.initial_message_create_wallet_body)))
+ initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)))
}
- suspend fun eraseWallet(cardId: String?): CompletionResult {
- return runTaskAsyncReturnOnMain(PurgeWalletCommand(
- TangemSdkConstants.getDefaultWalletIndex()),
- cardId,
- initialMessage = Message(activity.getString(R.string.initial_message_purge_wallet_body)))
+ suspend fun eraseWallet(card: Card): CompletionResult {
+ return runTaskAsyncReturnOnMain(
+ PurgeWalletCommand(card.wallets.first().publicKey),
+ card.cardId,
+ initialMessage = Message(context.getString(R.string.initial_message_purge_wallet_body)))
}
- suspend fun setPasscode(cardId: String?): CompletionResult {
- return runTaskAsyncReturnOnMain(SetPinCommand(
- pinType = PinType.Pin2,
- newPin1 = tangemSdk.config.defaultPin1.calculateSha256(),
- newPin2 = null
- ), cardId, initialMessage = Message(activity.getString(R.string.initial_message_change_passcode_body)))
+ suspend fun setPasscode(cardId: String?): CompletionResult {
+ return runTaskAsyncReturnOnMain(
+ SetUserCodeCommand.changePasscode(null),
+ cardId,
+ initialMessage = Message(context.getString(R.string.initial_message_change_passcode_body)))
}
- suspend fun setAccessCode(cardId: String?): CompletionResult {
- return runTaskAsyncReturnOnMain(SetPinCommand(
- pinType = PinType.Pin1,
- newPin1 = null,
- newPin2 = tangemSdk.config.defaultPin2.calculateSha256()
- ), cardId, initialMessage = Message(activity.getString(R.string.initial_message_change_access_code_body)))
+ suspend fun setAccessCode(cardId: String?): CompletionResult {
+ return runTaskAsyncReturnOnMain(
+ SetUserCodeCommand.changeAccessCode(null),
+ cardId,
+ initialMessage = Message(context.getString(R.string.initial_message_change_access_code_body)))
}
- suspend fun setLongTap(cardId: String?): CompletionResult {
- return runTaskAsyncReturnOnMain(SetPinCommand(
- pinType = PinType.Pin1,
- newPin1 = tangemSdk.config.defaultPin1.calculateSha256(),
- newPin2 = tangemSdk.config.defaultPin2.calculateSha256()
- ), cardId, initialMessage = Message(activity.getString(R.string.initial_message_tap_header)))
+ suspend fun setLongTap(cardId: String?): CompletionResult {
+ return runTaskAsyncReturnOnMain(
+ SetUserCodeCommand.resetUserCodes(),
+ cardId,
+ initialMessage = Message(context.getString(R.string.initial_message_tap_header)))
+ }
+
+ suspend fun checkUserCodes(cardId: String?): CompletionResult {
+ return runTaskAsyncReturnOnMain(
+ CheckUserCodesCommand(),
+ cardId,
+ initialMessage = Message(context.getString(R.string.initial_message_tap_header)))
}
suspend fun runTaskAsync(
- runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null,
+ runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null,
): CompletionResult =
- withContext(Dispatchers.IO) {
+ withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
@@ -91,13 +113,23 @@ class TangemSdkManager(val activity: ComponentActivity) {
}
private suspend fun runTaskAsyncReturnOnMain(
- runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null,
+ runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null,
): CompletionResult {
val result = runTaskAsync(runnable, cardId, initialMessage)
return withContext(Dispatchers.Main) { result }
}
- fun changeDisplayedCardIdNumbersCount(card: Card) {
- tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null
+ fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse) {
+ tangemSdk.config.cardIdDisplayedNumbersCount = if (scanResponse.isTangemTwins()) 4 else null
+ }
+
+ companion object {
+ val config = Config(
+ linkedTerminal = true,
+ allowUntrustedCards = true,
+ filter = CardFilter(
+ allowedCardTypes = FirmwareVersion.FirmwareType.values().toList()
+ )
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
index 7a3556b33a..978ca5288f 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
@@ -3,26 +3,25 @@ package com.tangem.tap.domain
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
-import com.tangem.commands.SignCommand
-import com.tangem.commands.SignResponse
-import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.CompletionResult
+import com.tangem.tap.domain.tasks.SignHashTask
+import com.tangem.tap.domain.tasks.SignHashesTask
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSigner(
private val tangemSdk: TangemSdk,
private val initialMessage: Message,
- private val signerCallback: (SignResponse) -> Unit
+ private val signerCallback: (TangemSignerResponse) -> Unit,
) : TransactionSigner {
override suspend fun sign(
hash: ByteArray,
cardId: String,
- walletPublicKey: ByteArray
+ walletPublicKey: ByteArray,
): CompletionResult =
suspendCoroutine { continuation ->
- val command = SignCommand(arrayOf(hash), WalletIndex.PublicKey(walletPublicKey))
+ val command = SignHashTask(hash, walletPublicKey)
tangemSdk.startSessionWithRunnable(
runnable = command,
cardId = cardId,
@@ -30,8 +29,13 @@ class TangemSigner(
) { result ->
when (result) {
is CompletionResult.Success -> {
- signerCallback(result.data)
- continuation.resume(CompletionResult.Success(result.data.signatures.first()))
+ signerCallback(
+ TangemSignerResponse(
+ result.data.totalSignedHashes,
+ result.data.remainingSignatures
+ )
+ )
+ continuation.resume(CompletionResult.Success(result.data.signature))
}
is CompletionResult.Failure ->
continuation.resume(CompletionResult.Failure(result.error))
@@ -43,18 +47,23 @@ class TangemSigner(
override suspend fun sign(
hashes: List,
cardId: String,
- walletPublicKey: ByteArray
+ walletPublicKey: ByteArray,
): CompletionResult> =
suspendCoroutine { continuation ->
- val command = SignCommand(hashes.toTypedArray(), WalletIndex.PublicKey(walletPublicKey))
+ val task = SignHashesTask(hashes, walletPublicKey)
tangemSdk.startSessionWithRunnable(
- runnable = command,
+ runnable = task,
cardId = cardId,
initialMessage = initialMessage,
) { result ->
when (result) {
is CompletionResult.Success -> {
- signerCallback(result.data)
+ signerCallback(
+ TangemSignerResponse(
+ result.data.totalSignedHashes,
+ result.data.remainingSignatures
+ )
+ )
continuation.resume(CompletionResult.Success(result.data.signatures))
}
is CompletionResult.Failure ->
@@ -62,4 +71,9 @@ class TangemSigner(
}
}
}
-}
\ No newline at end of file
+}
+
+data class TangemSignerResponse(
+ val totalSignedHashes: Int?,
+ val remainingSignatures: Int?,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt
deleted file mode 100644
index b9ec4705a5..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.tangem.tap.domain
-
-class TapBlockchainConfig {
- companion object {
- val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT"
- val blockcypherTokens: Set = setOf(
- "aa8184b0e0894b88a5688e01b3dc1e82",
- "56c4ca23c6484c8f8864c32fde4def8d",
- "66a8a37c5e9d4d2c9bb191acfe7f93aa"
- )
- val infuraProjectId: String = "613a0b14833145968b1f656240c7d245"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
index 521ed462b4..f4b680ec9f 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.domain
import androidx.annotation.StringRes
-import com.tangem.TangemError
+import com.tangem.common.core.TangemError
import com.tangem.wallet.R
interface TapErrors
@@ -16,12 +16,13 @@ interface MultiMessageError : TapErrors {
}
sealed class TapError(
- @StringRes val localizedMessage: Int,
- override val args: List? = null
+ @StringRes val messageResource: Int,
+ override val args: List? = null
) : Throwable(), TapErrors, ArgError {
object UnknownError : TapError(R.string.send_error_unknown)
- data class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
+ open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
+ object ScanCardError : TapError(R.string.scan_card_error)
object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created)
object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message)
object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty)
@@ -38,8 +39,15 @@ sealed class TapError(
object DustChange : TapError(R.string.send_error_dust_change)
data class CreateAccountUnderfunded(override val args: List) : TapError(R.string.send_error_no_target_account)
- sealed class XmlError {
- object AssetAccountNotCreated: TapError(R.string.send_error_no_account_xlm)
+ sealed class XmlError {
+ object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm)
+ }
+
+ sealed class WalletManagerUpdate {
+ class NoAccountError(amountToCreateAccount: String): CustomError(amountToCreateAccount)
+ class InternalError(message: String): CustomError(message)
+ object BlockchainIsUnreachable: TapError(R.string.wallet_balance_blockchain_unreachable)
+ object BlockchainIsUnreachableTryLater: TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
}
data class ValidateTransactionErrors(
@@ -60,7 +68,7 @@ fun TapErrors.assembleErrors(): MutableList?>> {
val idList = mutableListOf?>>()
when (this) {
is MultiMessageError -> this.errorList.forEach { idList.addAll(it.assembleErrors()) }
- is TapError -> idList.add(Pair(this.localizedMessage, this.args))
+ is TapError -> idList.add(Pair(this.messageResource, this.args))
}
return idList
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index a67ec007e0..10f1a76a87 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,21 +1,21 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.*
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.CardStatus
-import com.tangem.commands.common.network.Result
+import com.tangem.common.card.Card
+import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
+import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
+import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.*
-import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CardCurrencies
-import com.tangem.tap.domain.twins.TwinsHelper
-import com.tangem.tap.domain.twins.isTwinCard
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
@@ -24,7 +24,6 @@ import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import timber.log.Timber
import java.math.BigDecimal
@@ -38,38 +37,26 @@ class TapWalletManager {
by lazy { WalletManagerFactory(blockchainSdkConfig) }
suspend fun loadWalletData(walletManager: WalletManager) {
- val result = try {
- walletManager.update()
- Result.Success(walletManager.wallet)
- } catch (exception: Exception) {
- Result.Failure(exception)
- }
- handleUpdateWalletResult(result, walletManager)
+ handleUpdateWalletResult(walletManager.safeUpdate(), walletManager)
}
suspend fun updateWallet(walletManager: WalletManager) {
- val result = try {
- walletManager.update()
- Result.Success(walletManager.wallet)
- } catch (exception: Exception) {
- Result.Failure(exception)
- }
+ val result = walletManager.safeUpdate()
withContext(Dispatchers.Main) {
when (result) {
is Result.Success ->
store.dispatch(WalletAction.UpdateWallet.Success(result.data))
is Result.Failure ->
- store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage))
+ store.dispatch(WalletAction.UpdateWallet.Failure(result.error.localizedMessage))
}
}
}
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
- Timber.d(wallet.getTokens().toString())
val currencies = wallet.getTokens()
- .map { Currency.Token(it, wallet.blockchain) }
- .plus(Currency.Blockchain(wallet.blockchain))
+ .map { Currency.Token(it) }
+ .plus(Currency.Blockchain(wallet.blockchain))
loadFiatRate(fiatCurrency, currencies)
}
@@ -86,9 +73,9 @@ class TapWalletManager {
handleFiatRatesResult(results)
}
- suspend fun onCardScanned(data: ScanNoteResponse, addAnalyticsEvent: Boolean = false) {
+ suspend fun onCardScanned(data: ScanResponse, addAnalyticsEvent: Boolean = false) {
if (addAnalyticsEvent) {
- FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain)
}
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
updateConfigManager(data)
@@ -96,17 +83,10 @@ class TapWalletManager {
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
+ store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
- if (data.card.isTwinCard()) {
- val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId)
- if (cardNumber != null) {
- store.dispatch(WalletAction.TwinsAction.SetTwinCard(
- cardNumber, isCreatingTwinCardsAllowed = true
- ))
- }
- }
- val blockchain = data.card.getBlockchain()
+ val blockchain = data.getBlockchain()
if (blockchain == Blockchain.Ethereum ||
blockchain == Blockchain.EthereumTestnet) {
store.dispatch(TokensAction.LoadCardTokens)
@@ -115,14 +95,14 @@ class TapWalletManager {
}
}
- private fun updateConfigManager(data: ScanNoteResponse) {
+ fun updateConfigManager(data: ScanResponse) {
val configManager = store.state.globalState.configManager
- val blockchain = data.card.getBlockchain()
+ val blockchain = data.getBlockchain()
if (data.card.isStart2Coin) {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
configManager?.turnOff(ConfigManager.isTopUpEnabled)
} else if (blockchain == Blockchain.Bitcoin
- || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
+ || data.walletData?.blockchain == Blockchain.Bitcoin.id) {
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
} else {
@@ -131,77 +111,65 @@ class TapWalletManager {
}
}
- suspend fun loadData(data: ScanNoteResponse) {
+ suspend fun loadData(data: ScanResponse) {
withContext(Dispatchers.Main) {
- val artworkId = data.verifyResponse?.artworkInfo?.id
- when {
- data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
- store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
- store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
+ store.dispatch(WalletAction.LoadCardInfo(data.card))
+ getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
+ store.dispatch(it)
+ return@withContext
+ }
+
+ val blockchain = data.getBlockchain()
+ val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
+
+ if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
+ val primaryToken = data.getPrimaryToken()
+
+ store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
+ if (primaryToken != null) {
+ primaryWalletManager.addToken(primaryToken)
+ store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
}
- data.card.getStatus() == CardStatus.Empty ||
- (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
- store.dispatch(WalletAction.EmptyWallet)
- store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
+ if (data.card.isMultiwalletAllowed) {
+ loadMultiWalletData(data.card, blockchain, primaryWalletManager)
+ } else {
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
}
- else -> {
- val config = store.state.globalState.configManager?.config ?: return@withContext
- val blockchain = data.card.getBlockchain()
- val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
-
- if (blockchain != null && primaryWalletManager != null) {
- val primaryToken = data.card.getToken()
-
- store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
- if (primaryToken != null) {
- primaryWalletManager.addToken(primaryToken)
- store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
- }
- if (data.card.isMultiwalletAllowed) {
- loadMultiWalletData(data.card, blockchain, primaryWalletManager)
- } else {
- store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
- store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
- }
-
- } else {
- if (data.card.isMultiwalletAllowed) {
- loadMultiWalletData(data.card, blockchain, null)
- }
- }
- store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
- store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
- store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
- store.dispatch(WalletAction.LoadFiatRate())
+ } else {
+ if (data.card.isMultiwalletAllowed) {
+ loadMultiWalletData(data.card, blockchain, null)
}
}
- store.dispatch(WalletAction.Warnings.CheckIfNeeded)
+ val moonPayStatus = store.state.globalState.moonpayStatus
+ store.dispatch(WalletAction.LoadWallet(moonPayStatus))
+ store.dispatch(WalletAction.LoadFiatRate())
}
}
private fun loadMultiWalletData(
- card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
+ card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
) {
- val presetTokens = primaryWalletManager?.presetTokens ?: emptySet()
+ val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList()
val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
if (savedCurrencies == null) {
if (primaryBlockchain != null && primaryWalletManager != null) {
store.dispatch(WalletAction.MultiWallet.SaveCurrencies(
- CardCurrencies(
- blockchains = setOf(primaryBlockchain), tokens = presetTokens
- )))
+ CardCurrencies(
+ blockchains = listOf(primaryBlockchain), tokens = primaryTokens
+ )))
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)))
- store.dispatch(WalletAction.MultiWallet.AddTokens(presetTokens.toList()))
+ store.dispatch(WalletAction.MultiWallet.AddTokens(primaryTokens.toList()))
} else {
- val blockchains = setOf(Blockchain.Bitcoin, Blockchain.Ethereum)
+ val blockchains = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
store.dispatch(WalletAction.MultiWallet.SaveCurrencies(
- CardCurrencies(blockchains = blockchains, tokens = emptySet())
+ CardCurrencies(blockchains = blockchains, tokens = emptyList())
))
val walletManagers =
- walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList())
+ walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList())
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList()))
}
@@ -210,12 +178,12 @@ class TapWalletManager {
} else {
val blockchains = savedCurrencies.blockchains.toList()
val walletManagers = if (
- presetTokens.isNotEmpty() &&
- primaryWalletManager != null && primaryBlockchain != null
+ primaryTokens.isNotEmpty() &&
+ primaryWalletManager != null && primaryBlockchain != null
) {
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
walletManagerFactory.makeWalletManagersForApp(card, blockchainsWithoutPrimary)
- .plus(primaryWalletManager)
+ .plus(primaryWalletManager)
} else {
walletManagerFactory.makeWalletManagersForApp(card, blockchains)
}
@@ -226,26 +194,36 @@ class TapWalletManager {
}
}
- suspend fun reloadData(data: ScanNoteResponse) {
+ suspend fun reloadData(data: ScanResponse) {
withContext(Dispatchers.Main) {
- when {
- data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
- store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
- }
- data.card.getStatus() == CardStatus.Empty ||
- (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
- store.dispatch(WalletAction.EmptyWallet)
- }
- else -> {
- if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
- store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
- return@withContext
- }
- val config = store.state.globalState.configManager?.config ?: return@withContext
- store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
- store.dispatch(WalletAction.LoadFiatRate())
- }
+ getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
+ store.dispatch(it)
+ return@withContext
}
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
+ store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
+ return@withContext
+ }
+
+ val moonPayStatus = store.state.globalState.moonpayStatus
+ store.dispatch(WalletAction.LoadWallet(moonPayStatus))
+ store.dispatch(WalletAction.LoadFiatRate())
+ }
+ }
+
+ private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? {
+ return when {
+ // check order is important
+ data.isTangemTwins() && !data.twinsIsTwinned() -> {
+ WalletAction.EmptyWallet
+ }
+ data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
+ WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
+ }
+ data.card.wallets.isEmpty() -> {
+ WalletAction.EmptyWallet
+ }
+ else -> null
}
}
@@ -254,23 +232,23 @@ class TapWalletManager {
when (result) {
is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data))
is Result.Failure -> {
- if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
- store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
- return@withContext
- }
- val error = result.error
- val blockchain = walletManager.wallet.blockchain
- if (error != null && blockchain.isNoAccountError(error)) {
- val token = walletManager.wallet.getFirstToken()
- val amountToCreateAccount = blockchain.amountToCreateAccount(token)
- if (amountToCreateAccount != null) {
- store.dispatch(WalletAction.LoadWallet.NoAccount(
- walletManager.wallet,
- amountToCreateAccount.toString()))
- return@withContext
+ val error = (result.error as? TapError) ?: TapError.UnknownError
+ when (error) {
+ TapError.NoInternetConnection -> {
+ store.dispatch(WalletAction.LoadData.Failure(error))
+ }
+ is TapError.WalletManagerUpdate.NoAccountError -> {
+ store.dispatch(WalletAction.LoadWallet
+ .NoAccount(walletManager.wallet, error.customMessage))
+ }
+ is TapError.WalletManagerUpdate.InternalError -> {
+ store.dispatch(WalletAction.LoadWallet
+ .Failure(walletManager.wallet, error.customMessage))
+ }
+ else -> {
+ store.dispatchDebugErrorNotification(error)
}
}
- store.dispatch(WalletAction.LoadWallet.Failure(walletManager.wallet, result.error?.localizedMessage))
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
index b64b862627..d734698fd5 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
@@ -1,12 +1,13 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.Blockchain
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.EllipticCurve
-import com.tangem.commands.common.card.masks.Product
-import com.tangem.tap.domain.TapWorkarounds.isNote
+import com.tangem.common.card.Card
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
+import com.tangem.tap.domain.TapWorkarounds.isTangemNote
import com.tangem.tap.domain.extensions.getSingleWallet
+import com.tangem.tap.domain.twins.isTangemTwin
import java.util.*
object TapWorkarounds {
@@ -16,41 +17,28 @@ object TapWorkarounds {
}
val Card.isStart2Coin: Boolean
- get() = isStart2CoinIssuer(cardData?.issuerName)
+ get() = isStart2CoinIssuer(issuer.name)
+
+ val Card.isTestCard: Boolean
+ get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
fun Card.isExcluded(): Boolean {
- val cardData = this.cardData ?: return false
- val productMask = cardData.productMask
- val excludedBatch = excludedBatches.contains(cardData.batchId)
- val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.toUpperCase(Locale.US))
- val excludedProductMask = (productMask != null && // product mask is on cards v2.30 and later
- !productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard))
- return excludedBatch || excludedIssuerName || excludedProductMask
-
+ val excludedBatch = excludedBatches.contains(batchId)
+ val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
+ return excludedBatch || excludedIssuerName
}
- fun Card.isNote(): Boolean {
- return notesBatches.contains(cardData?.batchId)
- }
+ @Deprecated("Use ScanResponse.isTangemNote")
+ fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId)
- fun Card.isMultiCurrencyWallet(): Boolean {
- return multiCurrencyWalletsBatches.contains(cardData?.batchId)
- }
+ @Deprecated("Use ScanResponse.isTangemWallet")
+ fun isTangemWallet(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
- val Card.noteCurrency: Blockchain?
- get() {
- return when (cardData?.batchId) {
- "AB01" -> Blockchain.Bitcoin
- "AB02" -> Blockchain.Ethereum
- "AB03" -> Blockchain.CardanoShelley
- "AB04" -> Blockchain.Dogecoin
- "AB05" -> Blockchain.Binance
- "AB06" -> Blockchain.XRP
- else -> null
- }
- }
+ fun getTangemNoteBlockchain(card: Card): Blockchain? = tangemNoteBatches[card.batchId]
private const val START_2_COIN_ISSUER = "start2coin"
+ private const val TEST_CARD_BATCH = "99FF"
+ private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val excludedBatches = listOf(
"0027",
@@ -63,23 +51,23 @@ object TapWorkarounds {
"TTM BANK"
)
- private val notesBatches = listOf(
- "AB01",
- "AB02",
- "AB03",
- "AB04",
- "AB05",
- "AB06",
- )
+ private val tangemWalletBatches = listOf("AC01")
- private val multiCurrencyWalletsBatches = listOf("AC01")
+ private val tangemNoteBatches = mapOf(
+ "AB01" to Blockchain.Bitcoin,
+ "AB02" to Blockchain.Ethereum,
+ "AB03" to Blockchain.CardanoShelley,
+ "AB04" to Blockchain.Dogecoin,
+ "AB05" to Blockchain.BSC,
+ "AB06" to Blockchain.XRP,
+ )
}
+val DELAY_SDK_DIALOG_CLOSE = 1400L
+
val Card.isMultiwalletAllowed: Boolean
get() {
- return cardData?.productMask?.contains(Product.TwinCard) != true
- && !isStart2Coin
- && !isNote()
- && (firmwareVersion.major >= 4 ||
+ return !isTangemTwin() && !isStart2Coin && !isTangemNote(this)
+ && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt b/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt
deleted file mode 100644
index dfa301ab78..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.tangem.tap.domain
-
-import android.net.Uri
-import android.util.Base64
-import com.tangem.tap.common.redux.global.CryptoCurrencyName
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
-
-class TopUpHelper {
-
- companion object {
-// const val REDIRECT_URL = "https://success.tangem.com"
-
- private const val BASE_URL = "https://buy.moonpay.io"
- private const val API_KEY_PATH = "?apiKey="
- private const val CURRENCY_PATH = "¤cyCode="
- private const val WALLET_ADDRESS_PATH = "&walletAddress="
-// private const val REDIRECT_URL_PATH = "&redirectUrl="
- private const val SIGNATURE_PATH = "&signature="
-
- fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String, apiKey: String, secretKey: String): String {
- val originalQuery = API_KEY_PATH + apiKey.urlEncode() +
- CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
- WALLET_ADDRESS_PATH + walletAddress.urlEncode()
-// REDIRECT_URL_PATH + REDIRECT_URL.urlEncode()
- val signature = createSignature(originalQuery, secretKey)
-
- return BASE_URL + originalQuery + SIGNATURE_PATH + signature.urlEncode()
- }
-
- private fun String.urlEncode(): String {
- return Uri.encode(this)
- }
-
-
- private fun createSignature(data: String, key: String): String {
- val sha256Hmac = Mac.getInstance("HmacSHA256")
- val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
- sha256Hmac.init(secretKey)
- val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
- return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt
new file mode 100644
index 0000000000..c06d9b0001
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt
@@ -0,0 +1,49 @@
+package com.tangem.tap.domain
+
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.Target
+import com.tangem.common.services.Result
+
+/**
+[REDACTED_AUTHOR]
+ */
+class UrlBitmapLoader {
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ fun loadBitmap(url: String, callback: (Result) -> Unit) {
+ val target = DownloadTarget(callback)
+ protectedFromGarbageCollectorTargets.add(target)
+ mainHandler.post { Picasso.get().load(url).into(target) }
+ }
+
+ fun loadBitmap(url: String, target: DownloadTarget) {
+ protectedFromGarbageCollectorTargets.add(target)
+ Picasso.get().load(url).into(target)
+ }
+
+}
+
+private val protectedFromGarbageCollectorTargets = mutableListOf()
+
+// It adds the ability to trigger multiple downloads with a unique callback.
+open class DownloadTarget(
+ val callback: (Result) -> Unit,
+) : Target {
+
+ override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
+ callback(Result.Success(bitmap))
+ protectedFromGarbageCollectorTargets.remove(this)
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ callback.invoke(Result.Failure(e ?: Exception("Unknown exception")))
+ protectedFromGarbageCollectorTargets.remove(this)
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index 58096022fd..cae1861582 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -1,25 +1,26 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
-import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
+import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
*/
data class Config(
- val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
- val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
- val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
- val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
- val isSendingToPayIdEnabled: Boolean = true,
- val isTopUpEnabled: Boolean = false,
- val isCreatingTwinCardsAllowed: Boolean = false
+ val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
+ val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
+ val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
+ val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
+ val isSendingToPayIdEnabled: Boolean = true,
+ val isTopUpEnabled: Boolean = false,
+ @Deprecated("Not relevant since version 3.23")
+ val isCreatingTwinCardsAllowed: Boolean = false
)
class ConfigManager(
- private val localLoader: Loader,
- private val remoteLoader: Loader
+ private val localLoader: Loader,
+ private val remoteLoader: Loader
) {
var config: Config = Config()
@@ -49,7 +50,8 @@ class ConfigManager(
fun resetToDefault(name: String) {
when (name) {
- isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
+ isSendingToPayIdEnabled -> config =
+ config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
isCreatingTwinCardsAllowed -> config =
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
@@ -60,38 +62,40 @@ class ConfigManager(
val model = featureModel ?: return
config = config.copy(
- isTopUpEnabled = model.isTopUpEnabled,
- isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
- isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
+ isTopUpEnabled = model.isTopUpEnabled,
+ isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
+ isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
)
defaultConfig = defaultConfig.copy(
- isTopUpEnabled = model.isTopUpEnabled,
- isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
- isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
+ isTopUpEnabled = model.isTopUpEnabled,
+ isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
+ isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
)
}
private fun setupKey(configValues: ConfigValueModel?) {
val values = configValues ?: return
config = config.copy(
- coinMarketCapKey = values.coinMarketCapKey,
- moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey,
- blockchainSdkConfig = BlockchainSdkConfig(
- blockchairApiKey = values.blockchairApiKey,
- blockcypherTokens = values.blockcypherTokens,
- infuraProjectId = values.infuraProjectId
- )
+ coinMarketCapKey = values.coinMarketCapKey,
+ moonPayApiKey = values.moonPayApiKey,
+ moonPayApiSecretKey = values.moonPayApiSecretKey,
+ blockchainSdkConfig = BlockchainSdkConfig(
+ blockchairApiKey = values.blockchairApiKey,
+ blockchairAuthorizationToken = values.blockchairAuthorizationToken,
+ blockcypherTokens = values.blockcypherTokens,
+ infuraProjectId = values.infuraProjectId
+ )
)
defaultConfig = defaultConfig.copy(
- coinMarketCapKey = values.coinMarketCapKey,
- moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey,
- blockchainSdkConfig = BlockchainSdkConfig(
- blockchairApiKey = values.blockchairApiKey,
- blockcypherTokens = values.blockcypherTokens,
- infuraProjectId = values.infuraProjectId
- )
+ coinMarketCapKey = values.coinMarketCapKey,
+ moonPayApiKey = values.moonPayApiKey,
+ moonPayApiSecretKey = values.moonPayApiSecretKey,
+ blockchainSdkConfig = BlockchainSdkConfig(
+ blockchairApiKey = values.blockchairApiKey,
+ blockchairAuthorizationToken = values.blockchairAuthorizationToken,
+ blockcypherTokens = values.blockcypherTokens,
+ infuraProjectId = values.infuraProjectId
+ )
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index 69c5f0a8b0..c71a4a16dd 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -16,6 +16,7 @@ class ConfigValueModel(
val moonPayApiKey: String,
val moonPayApiSecretKey: String,
val blockchairApiKey: String?,
+ val blockchairAuthorizationToken: String?,
val blockcypherTokens: Set?,
val infuraProjectId: String?,
)
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
index 3c3ad5f5ce..500f3844b3 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
@@ -1,6 +1,5 @@
package com.tangem.tap.domain.configurable.warningMessage
-import android.view.View
import androidx.annotation.StringRes
import com.squareup.moshi.Json
import com.tangem.blockchain.common.Blockchain
@@ -46,7 +45,9 @@ data class WarningMessage(
@Json(name = "temporary")
Temporary, // можно скрыть (кнопка ОК)
- AppRating
+ AppRating,
+
+ TestCard
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
index fcc45992dd..3cdf14dfea 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
-import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
+import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.R
@@ -157,6 +157,18 @@ class WarningMessagesManager(
messageFormatArg = remainingSignatures.toString()
)
+ fun testCardWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.TestCard,
+ priority = WarningMessage.Priority.Critical,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.alert_title,
+ R.string.warning_testnet_card_message,
+ WarningMessage.Origin.Local
+ )
+
const val REMAINING_SIGNATURES_WARNING = 10
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt
index a89f4fe24c..5ec3ea7672 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
-import com.tangem.commands.common.card.EllipticCurve
import java.math.BigDecimal
@@ -14,10 +13,10 @@ fun Blockchain.isNoAccountError(exception: Throwable): Boolean {
}
}
-fun Blockchain.amountToCreateAccount(token: Token? = null): Double? {
+fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
return when (this) {
- Blockchain.Stellar -> if (token?.symbol == NODL) 1.5 else 1.toDouble()
- Blockchain.XRP -> 20.toDouble()
+ Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE
+ Blockchain.XRP -> BigDecimal(10)
else -> null
}
}
@@ -26,17 +25,4 @@ fun Blockchain.minimalAmount(): BigDecimal {
return 1.toBigDecimal().movePointLeft(decimals())
}
-fun Blockchain.getSupportedCurves(): List? {
- return when (this) {
- Blockchain.Unknown -> null
- Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.BitcoinCash, Blockchain.Litecoin,
- Blockchain.Ducatus, Blockchain.Ethereum, Blockchain.EthereumTestnet, Blockchain.RSK,
- Blockchain.Binance, Blockchain.BinanceTestnet -> listOf(EllipticCurve.Secp256k1)
- Blockchain.Tezos, Blockchain.XRP -> listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
- Blockchain.Cardano, Blockchain.CardanoShelley, Blockchain.Stellar ->
- listOf(EllipticCurve.Ed25519)
- else -> listOf(EllipticCurve.Secp256k1)
- }
-}
-
private const val NODL = "NODL"
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt
index dfe0e1e2a7..124ae27387 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt
@@ -1,49 +1,71 @@
package com.tangem.tap.domain.extensions
-import com.tangem.FirmwareConstraints
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Token
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.CardStatus
-import com.tangem.commands.wallet.CardWallet
-import com.tangem.commands.wallet.WalletStatus
-import com.tangem.common.TangemSdkConstants
-import com.tangem.tap.domain.TapWorkarounds.noteCurrency
-
-fun Card.getToken(): Token? {
- val symbol = cardData?.tokenSymbol ?: return null
- val contractAddress = cardData?.tokenContractAddress ?: return null
- val decimals = cardData?.tokenDecimal ?: return null
- if (symbol.isBlank() || contractAddress.isBlank()) return null
- return Token(symbol, contractAddress, decimals)
-}
-
-fun Card.getBlockchain(): Blockchain? {
- if (noteCurrency != null) return noteCurrency
- val blockchainName: String = cardData?.blockchainName ?: return null
- return Blockchain.fromId(blockchainName)
-}
+import com.tangem.common.card.Card
+import com.tangem.common.card.CardWallet
+import com.tangem.common.extensions.toHexString
+import com.tangem.common.services.Result
+import com.tangem.operations.attestation.CardVerifyAndGetInfo
+import com.tangem.operations.attestation.OnlineCardVerifier
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.domain.twins.getTwinCardNumber
+import com.tangem.tap.features.wallet.redux.Artwork
fun Card.getSingleWallet(): CardWallet? {
- return wallet(TangemSdkConstants.getDefaultWalletIndex())
+ return wallets.firstOrNull()
}
-fun Card.getStatus(): CardStatus {
- if (firmwareVersion < FirmwareConstraints.AvailabilityVersions.walletData) return status!!
+fun Card.hasWallets(): Boolean = wallets.isNotEmpty()
- return if (wallets.any { it.status == WalletStatus.Loaded }) {
- CardStatus.Loaded
- } else {
- CardStatus.Empty
- }
-}
+fun Card.hasNoWallets(): Boolean = wallets.isEmpty()
+
+fun Card.hasSingleWallet(): Boolean = wallets.size == 1
fun Card.hasSignedHashes(): Boolean {
- return wallets.any { it.status == WalletStatus.Loaded && it.signedHashes ?: 0 > 0 }
+ return wallets.any { it.totalSignedHashes ?: 0 > 0 }
}
fun Card.signedHashesCount(): Int {
- return wallets.map { it.signedHashes ?: 0 }.sum()
+ return wallets.map { it.totalSignedHashes ?: 0 }.sum()
+}
+
+suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String {
+ fun ifAnyError(): String {
+ return when {
+ cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
+ cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
+ else -> {
+ when (getTwinCardNumber()) {
+ TwinCardNumber.First -> Artwork.TWIN_CARD_1
+ TwinCardNumber.Second -> Artwork.TWIN_CARD_2
+ else -> Artwork.DEFAULT_IMG_URL
+ }
+ }
+ }
+ }
+
+ val cardInfoResult = cardInfo ?: OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)
+ return when (cardInfoResult) {
+ is Result.Success -> {
+ val artworkId = cardInfoResult.data.artwork?.id
+ if (artworkId == null || artworkId.isEmpty()) {
+ ifAnyError()
+ } else {
+ OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
+ }
+ }
+ is Result.Failure -> ifAnyError()
+ }
+}
+
+fun Card.getArtworkUrl(artworkId: String?): String? {
+ return when {
+ artworkId != null -> {
+ OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
+ }
+ cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
+ cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
+ else -> null
+ }
}
val Card.remainingSignatures: Int?
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt b/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt
new file mode 100644
index 0000000000..d204a2051d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt
@@ -0,0 +1,41 @@
+package com.tangem.tap.domain.extensions
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.network.moonpay.MoonpayStatus
+import com.tangem.tap.store
+
+/**
+[REDACTED_AUTHOR]
+ */
+fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean {
+ if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
+ if (!isBuyAllowed) return false
+
+ return when (currency) {
+ is Currency.Blockchain -> {
+ if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) {
+ false
+ } else {
+ availableToBuy.contains(currency.currencySymbol)
+ }
+ }
+ is Currency.Token -> false
+ }
+}
+
+fun MoonpayStatus.sellIsAllowed(currency: Currency): Boolean {
+ if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
+ if (!isSellAllowed) return false
+
+ return when (currency) {
+ is Currency.Blockchain -> {
+ if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) {
+ false
+ } else {
+ availableToSell.contains(currency.currencySymbol)
+ }
+ }
+ is Currency.Token -> false
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/TangemSdkConstants.kt b/app/src/main/java/com/tangem/tap/domain/extensions/TangemSdkConstants.kt
deleted file mode 100644
index c8710e3aeb..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/extensions/TangemSdkConstants.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.tangem.tap.domain.extensions
-
-import com.tangem.commands.wallet.WalletIndex
-import com.tangem.common.TangemSdkConstants
-
-fun TangemSdkConstants.Companion.getDefaultWalletIndex(): WalletIndex {
- return WalletIndex.Index(oldCardDefaultWalletIndex)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
index 68a0ee5b84..aa85331ab2 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
@@ -3,12 +3,12 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.EllipticCurve
-import com.tangem.commands.wallet.CardWallet
+import com.tangem.common.card.Card
+import com.tangem.common.card.CardWallet
+import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
-import com.tangem.tap.domain.tasks.ScanNoteResponse
-import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.domain.TapWorkarounds.isTestCard
+import com.tangem.tap.domain.tasks.product.ScanResponse
fun WalletManagerFactory.makeWalletManagerForApp(
card: Card,
@@ -19,34 +19,52 @@ fun WalletManagerFactory.makeWalletManagerForApp(
val wallet = selectWallet(wallets)
val publicKey = wallet?.publicKey ?: return null
val curveToUse = wallet.curve ?: return null
- return makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
+ return if (card.isTestCard) {
+ blockchain.getTestnetVersion()?.let {
+ makeWalletManager(card.cardId, publicKey, it, curveToUse)
+ }
+ } else {
+ makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
+ }
}
private fun selectWallet(wallets: List): CardWallet? {
- return when (wallets.size) {
- 0 -> null
- 1 -> wallets[0]
- else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
- }
+ return when (wallets.size) {
+ 0 -> null
+ 1 -> wallets[0]
+ else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
+ }
}
fun WalletManagerFactory.makeWalletManagersForApp(
card: Card, blockchains: List,
): List {
- return blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) }
+ return if (card.isTestCard) {
+ blockchains.mapNotNull { blockchain ->
+ blockchain.getTestnetVersion()?.let { makeWalletManagerForApp(card, it) }
+ }
+ } else {
+ blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) }
+ }
}
fun WalletManagerFactory.makePrimaryWalletManager(
- data: ScanNoteResponse,
+ data: ScanResponse,
): WalletManager? {
val card = data.card
- val blockchain = card.getBlockchain()
+ val blockchain = if (card.isTestCard) {
+ data.getBlockchain().getTestnetVersion()
+ } else {
+ data.getBlockchain()
+ }
val supportedCurves = blockchain?.getSupportedCurves() ?: return null
+
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(wallets)
val publicKey = wallet?.publicKey ?: return null
- val curveToUse = wallet.curve ?: return null
- return if (card.isTwinCard() && data.secondTwinPublicKey != null) {
+ val curveToUse = wallet.curve
+
+ return if (data.isTangemTwins() && data.secondTwinPublicKey != null) {
makeMultisigWalletManager(
cardId = card.cardId,
walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(),
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt
index 19b87612a0..1487f07e37 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt
@@ -1,28 +1,28 @@
package com.tangem.tap.domain.tasks
-import com.tangem.CardSession
-import com.tangem.CardSessionRunnable
-import com.tangem.FirmwareConstraints
-import com.tangem.TangemSdkError
-import com.tangem.commands.common.card.Card
import com.tangem.common.CompletionResult
-import com.tangem.tasks.CreateWalletTask
-import com.tangem.tasks.PreflightReadCapable
-import com.tangem.tasks.PreflightReadSettings
-import com.tangem.tasks.PreflightReadTask
+import com.tangem.common.card.Card
+import com.tangem.common.card.FirmwareVersion
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.extensions.guard
+import com.tangem.operations.PreflightReadMode
+import com.tangem.operations.PreflightReadTask
+import com.tangem.operations.wallet.CreateWalletCommand
-class CreateWalletAndRescanTask : CardSessionRunnable, PreflightReadCapable {
- override val requiresPin2 = false
- override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
+@Deprecated("Use CreateProductWalletAndRescanTask instead")
+class CreateWalletAndRescanTask : CardSessionRunnable {
override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
- val firmwareVerion = session.environment.card?.firmwareVersion
- if (firmwareVerion == null) {
+ val card = session.environment.card.guard {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
}
- val task = if (firmwareVerion < FirmwareConstraints.AvailabilityVersions.walletData) {
- CreateWalletTask()
+ val firmwareVersion = card.firmwareVersion
+
+ val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
+ CreateWalletCommand(card.supportedCurves.first())
} else {
CreateWalletsTask()
}
@@ -30,7 +30,7 @@ class CreateWalletAndRescanTask : CardSessionRunnable, PreflightReadCapabl
task.run(session) { result ->
when (result) {
is CompletionResult.Success ->
- PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback)
+ PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt
index b72cf26ae3..6de34f651a 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt
@@ -1,51 +1,44 @@
package com.tangem.tap.domain.tasks
-import com.tangem.CardSession
-import com.tangem.CardSessionRunnable
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.EllipticCurve
-import com.tangem.commands.wallet.CreateWalletCommand
-import com.tangem.commands.wallet.WalletConfig
import com.tangem.common.CompletionResult
-import com.tangem.tasks.PreflightReadSettings
-import com.tangem.tasks.PreflightReadTask
+import com.tangem.common.card.Card
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.operations.PreflightReadMode
+import com.tangem.operations.PreflightReadTask
+import com.tangem.operations.wallet.CreateWalletCommand
-class CreateWalletsTask(wallets: List = emptyList()) : CardSessionRunnable {
- override val requiresPin2 = false
+@Deprecated("Use CreateProductWalletsResponse instead")
+class CreateWalletsTask(curves: List? = null) : CardSessionRunnable {
- val wallets = if (wallets.isEmpty()) {
- listOf(
- WalletConfig(null, null, EllipticCurve.Secp256k1, null),
- WalletConfig(null, null, EllipticCurve.Ed25519, null),
- WalletConfig(null, null, EllipticCurve.Secp256r1, null),
- )
- } else {
- wallets
- }
+ private val curves = curves ?: listOf(
+ EllipticCurve.Secp256k1,
+ EllipticCurve.Ed25519,
+ EllipticCurve.Secp256r1,
+ )
- var index = 0
+ private var index = 0
override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
- val walletConfig = wallets[index]
- createWallet(walletConfig, session, callback)
+ val curve = curves[index]
+ createWallet(curve, session, callback)
}
private fun createWallet(
- walletConfig: WalletConfig, session: CardSession,
- callback: (result: CompletionResult) -> Unit
+ curve: EllipticCurve, session: CardSession,
+ callback: (result: CompletionResult) -> Unit
) {
- CreateWalletCommand(
- walletConfig, walletIndexValue = walletConfig.curveId!!.toWalletIndex()
- ).run(session) { result ->
+ CreateWalletCommand(curve).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
- if (index == wallets.lastIndex) {
- PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback)
+ if (index == curves.lastIndex) {
+ PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
return@run
}
index += 1
- createWallet(wallets[index], session, callback)
+ createWallet(curves[index], session, callback)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
@@ -53,12 +46,4 @@ class CreateWalletsTask(wallets: List = emptyList()) : CardSession
}
}
}
-}
-
-private fun EllipticCurve.toWalletIndex(): Int {
- return when (this) {
- EllipticCurve.Secp256k1 -> 0
- EllipticCurve.Ed25519 -> 1
- EllipticCurve.Secp256r1 -> 2
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
deleted file mode 100644
index 066158592c..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-package com.tangem.tap.domain.tasks
-
-import com.tangem.*
-import com.tangem.blockchain.common.BlockchainSdkConfig
-import com.tangem.blockchain.common.WalletManagerFactory
-import com.tangem.commands.CommandResponse
-import com.tangem.commands.ReadIssuerDataCommand
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.CardStatus
-import com.tangem.commands.common.card.EllipticCurve
-import com.tangem.commands.verification.VerifyCardCommand
-import com.tangem.commands.verification.VerifyCardResponse
-import com.tangem.commands.wallet.WalletConfig
-import com.tangem.common.CompletionResult
-import com.tangem.common.extensions.toHexString
-import com.tangem.tap.domain.TapSdkError
-import com.tangem.tap.domain.TapWorkarounds.isExcluded
-import com.tangem.tap.domain.TapWorkarounds.isMultiCurrencyWallet
-import com.tangem.tap.domain.TapWorkarounds.isNote
-import com.tangem.tap.domain.extensions.getSingleWallet
-import com.tangem.tap.domain.extensions.getStatus
-import com.tangem.tap.domain.twins.TwinCardsManager
-import com.tangem.tap.domain.twins.isTwinCard
-import com.tangem.tap.store
-import com.tangem.tasks.PreflightReadCapable
-import com.tangem.tasks.PreflightReadSettings
-import com.tangem.tasks.ScanTask
-import com.tangem.wallet.R
-
-data class ScanNoteResponse(
- val card: Card,
- val verifyResponse: VerifyCardResponse? = null,
- val secondTwinPublicKey: String? = null,
-) : CommandResponse
-
-class ScanNoteTask(val card: Card? = null) : CardSessionRunnable, PreflightReadCapable {
- override val requiresPin2 = false
-
- override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
-
- override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
- ScanTask().run(session) { result ->
- when (result) {
- is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
-
- is CompletionResult.Success -> {
- val card = this.card?.copy(
- isPin1Default = result.data.isPin1Default,
- isPin2Default = result.data.isPin2Default,
- ) ?: result.data
-
- val error = getErrorIfExcludedCard(card)
- if (error != null) {
- callback(CompletionResult.Failure(error))
- return@run
- }
-
- verifyCard(card, session, callback)
- }
- }
- }
- }
-
- private fun verifyCard(
- card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit
- ) {
- VerifyCardCommand(true).run(session) { verifyResult ->
- when (verifyResult) {
- is CompletionResult.Success -> {
- if (card.isTwinCard()) {
- dealWithTwinCard(card, session, verifyResult.data, callback)
- } else if (card.firmwareVersion.major >= 4) {
- createMissingWalletsIfNeeded(card, session, verifyResult.data, callback)
- } else {
- callback(CompletionResult.Success(ScanNoteResponse(card, verifyResult.data)))
- }
- }
- is CompletionResult.Failure -> {
- callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
- }
- }
- }
- }
-
- private fun createMissingWalletsIfNeeded(
- card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
- callback: (result: CompletionResult) -> Unit
- ) {
- if (card.getStatus() == CardStatus.Empty) {
- callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse)))
- return
- }
-
- val curvesPresent = card.wallets.map { it.curve }
- val curvesToCreate = EllipticCurve.values().subtract(curvesPresent)
-
- if (curvesToCreate.isEmpty()) {
- callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse)))
- return
- }
-
- val configs = curvesToCreate.map { curve ->
- WalletConfig(
- isReusable = null, prohibitPurgeWallet = null, curveId = curve,
- signingMethods = null
- )
- }
- CreateWalletsTask(configs).run(session) { result ->
- when (result) {
- is CompletionResult.Success ->
- callback(CompletionResult.Success(ScanNoteResponse(result.data, verifyResponse)))
- is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
- }
- }
-
- }
-
- private fun dealWithTwinCard(
- card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
- callback: (result: CompletionResult) -> Unit
- ) {
- ReadIssuerDataCommand().run(session) { readDataResult ->
- when (readDataResult) {
- is CompletionResult.Success -> {
- val publicKey = card.getSingleWallet()?.publicKey
- if (publicKey == null) {
- callback(CompletionResult.Success(ScanNoteResponse(card, null)))
- return@run
- }
- val verified = TwinCardsManager.verifyTwinPublicKey(
- readDataResult.data.issuerData, publicKey
- )
- if (verified) {
- val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
- callback(CompletionResult.Success(
- ScanNoteResponse(card, verifyResponse, twinPublicKey.toHexString())
- ))
- return@run
- } else {
- callback(CompletionResult.Success(ScanNoteResponse(card, null)))
- }
- }
- is CompletionResult.Failure ->
- callback(CompletionResult.Success(ScanNoteResponse(card, null)))
- }
- }
- }
-
- private fun getErrorIfExcludedCard(card: Card): TangemError? {
- if (card.isExcluded()) return TapSdkError.CardForDifferentApp
- if (card.status == CardStatus.Purged) return TangemSdkError.WalletIsPurged()
- if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized()
- // Disable new cards on the old version of the app // TODO: remove when cards are supported
- if (card.isMultiCurrencyWallet() || card.isNote()) return UpdateAppToUseThisCard()
- return null
- }
-
- private fun getWalletManagerFactory(): WalletManagerFactory {
- val blockchainSdkConfig = store.state.globalState.configManager?.config
- ?.blockchainSdkConfig ?: BlockchainSdkConfig()
- return WalletManagerFactory(blockchainSdkConfig)
- }
-
-}
-
-class UpdateAppToUseThisCard : TangemError {
- override val code: Int = 50005
- override var customMessage: String = code.toString()
- override val messageResId: Int = R.string.error_update_app
-}
-
-
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt
new file mode 100644
index 0000000000..3c24418b42
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.domain.tasks
+
+import com.tangem.common.CompletionResult
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.CompletionCallback
+import com.tangem.operations.CommandResponse
+import com.tangem.operations.sign.SignHashCommand
+
+class TangemSignHashResponse(
+ val signature: ByteArray,
+ val totalSignedHashes: Int?,
+ val remainingSignatures: Int?,
+) : CommandResponse
+
+class SignHashTask(
+ private val hash: ByteArray,
+ private val walletPublicKey: ByteArray,
+) : CardSessionRunnable {
+ override fun run(session: CardSession, callback: CompletionCallback) {
+ SignHashCommand(hash, walletPublicKey).run(session) { response ->
+
+ when (response) {
+ is CompletionResult.Success -> {
+ callback(CompletionResult.Success(TangemSignHashResponse(
+ response.data.signature,
+ response.data.totalSignedHashes,
+ session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
+ )))
+ }
+ is CompletionResult.Failure ->
+ callback(CompletionResult.Failure(response.error))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt
new file mode 100644
index 0000000000..2755d9aa82
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt
@@ -0,0 +1,35 @@
+package com.tangem.tap.domain.tasks
+
+import com.tangem.common.CompletionResult
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.CompletionCallback
+import com.tangem.operations.CommandResponse
+import com.tangem.operations.sign.SignHashesCommand
+
+class TangemSignHashesResponse(
+ val signatures: List,
+ val totalSignedHashes: Int?,
+ val remainingSignatures: Int?,
+) : CommandResponse
+
+class SignHashesTask(
+ private val hashes: Collection,
+ private val walletPublicKey: ByteArray,
+) : CardSessionRunnable {
+ override fun run(session: CardSession, callback: CompletionCallback) {
+ SignHashesCommand(hashes.toTypedArray(), walletPublicKey).run(session) { response ->
+ when (response) {
+ is CompletionResult.Success -> {
+ callback(CompletionResult.Success(TangemSignHashesResponse(
+ response.data.signatures,
+ response.data.totalSignedHashes,
+ session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
+ )))
+ }
+ is CompletionResult.Failure ->
+ callback(CompletionResult.Failure(response.error))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt
new file mode 100644
index 0000000000..f1024cdf11
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt
@@ -0,0 +1,119 @@
+import com.tangem.common.CompletionResult
+import com.tangem.common.card.Card
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.card.FirmwareVersion
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.extensions.guard
+import com.tangem.operations.PreflightReadMode
+import com.tangem.operations.PreflightReadTask
+import com.tangem.operations.wallet.CreateWalletCommand
+import com.tangem.operations.wallet.CreateWalletResponse
+import com.tangem.tap.domain.ProductType
+import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
+import com.tangem.tap.domain.tasks.product.CreateProductWalletsTask
+import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
+import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
+
+/**
+[REDACTED_AUTHOR]
+ */
+class CreateProductWalletAndRescanTask(
+ private val type: ProductType,
+) : CardSessionRunnable {
+
+ override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
+ val card = session.environment.card.guard {
+ callback(CompletionResult.Failure(TangemSdkError.CardError()))
+ return
+ }
+ CreateProductWalletTask(card, type).run(session) { result ->
+ when (result) {
+ is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback)
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
+ }
+ }
+ }
+}
+
+private class CreateProductWalletTask(
+ private val card: Card,
+ private val type: ProductType,
+) : CardSessionRunnable {
+
+ override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
+ val commandProcessor = when (type) {
+ ProductType.Note -> CreateWalletTangemNote()
+ ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
+ ProductType.Wallet -> CreateWalletTangemWallet()
+ else -> CreateWalletOtherCards()
+ }
+ commandProcessor.proceed(card, session, callback)
+ }
+}
+
+private class CreateWalletTangemNote : ProductCommandProcessor {
+ override fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit
+ ) {
+ if (card.supportedCurves.isEmpty()) {
+ callback(CompletionResult.Failure(TangemSdkError.CardError()))
+ return
+ }
+
+ val curvesSupportedByBlockchain = getTangemNoteBlockchain(card)?.getSupportedCurves()
+ if (curvesSupportedByBlockchain == null || curvesSupportedByBlockchain.isEmpty()) {
+ callback(CompletionResult.Failure(TangemSdkError.CardError()))
+ return
+ }
+
+ val intersectCurves = card.supportedCurves.intersect(curvesSupportedByBlockchain).toList()
+ if (intersectCurves.isEmpty()) {
+ callback(CompletionResult.Failure(TangemSdkError.CardError()))
+ } else {
+ val curve = if (intersectCurves.contains(EllipticCurve.Secp256k1)) {
+ EllipticCurve.Secp256k1
+ } else {
+ intersectCurves[0]
+ }
+ CreateWalletCommand(curve).run(session, callback)
+
+ }
+ }
+}
+
+private class CreateWalletTangemWallet : ProductCommandProcessor {
+ override fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit
+ ) {
+ //TODO:
+ }
+}
+
+private class CreateWalletOtherCards : ProductCommandProcessor {
+
+ override fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit
+ ) {
+ val firmwareVersion = card.firmwareVersion
+ val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
+ CreateProductWalletsTask(listOf(card.supportedCurves.first()))
+ } else {
+ CreateProductWalletsTask(card.getCurvesForNonCreatedWallets())
+ }
+
+ task.run(session) { result ->
+ when (result) {
+ is CompletionResult.Success -> callback(CompletionResult.Success(result.data.createWalletResponses[0]))
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt
new file mode 100644
index 0000000000..fc00138a57
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletsTask.kt
@@ -0,0 +1,49 @@
+package com.tangem.tap.domain.tasks.product
+
+import com.tangem.common.CompletionResult
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.operations.CommandResponse
+import com.tangem.operations.wallet.CreateWalletCommand
+import com.tangem.operations.wallet.CreateWalletResponse
+
+/**
+[REDACTED_AUTHOR]
+ */
+class CreateProductWalletsResponse(
+ val createWalletResponses: List
+) : CommandResponse
+
+class CreateProductWalletsTask(
+ private val curves: List,
+) : CardSessionRunnable {
+
+ private val createdWalletsResponses = mutableListOf()
+
+ override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
+ val curve = curves[createdWalletsResponses.size]
+ createWallet(curve, session, callback)
+ }
+
+ private fun createWallet(
+ curve: EllipticCurve,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit
+ ) {
+
+ CreateWalletCommand(curve).run(session) { result ->
+ when (result) {
+ is CompletionResult.Success -> {
+ createdWalletsResponses.add(result.data)
+ if (createdWalletsResponses.size == curves.size) {
+ callback(CompletionResult.Success(CreateProductWalletsResponse(createdWalletsResponses)))
+ return@run
+ }
+ createWallet(curves[createdWalletsResponses.size], session, callback)
+ }
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt
new file mode 100644
index 0000000000..c0810ff887
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.domain.tasks.product
+
+import com.tangem.common.CompletionResult
+import com.tangem.common.card.Card
+import com.tangem.common.core.CardSession
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface ProductCommandProcessor {
+ fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
new file mode 100644
index 0000000000..74dc69a99c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
@@ -0,0 +1,198 @@
+package com.tangem.tap.domain.tasks.product
+
+import com.tangem.*
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.common.CompletionResult
+import com.tangem.common.card.Card
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.card.FirmwareVersion
+import com.tangem.common.card.WalletData
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.TangemError
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.extensions.guard
+import com.tangem.common.extensions.toHexString
+import com.tangem.operations.CommandResponse
+import com.tangem.operations.PreflightReadMode
+import com.tangem.operations.PreflightReadTask
+import com.tangem.operations.ScanTask
+import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
+import com.tangem.tap.domain.ProductType
+import com.tangem.tap.domain.TapSdkError
+import com.tangem.tap.domain.TapWorkarounds
+import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
+import com.tangem.tap.domain.TapWorkarounds.isExcluded
+import com.tangem.tap.domain.extensions.getSingleWallet
+import com.tangem.tap.domain.twins.TwinsHelper
+
+data class ScanResponse(
+ val card: Card,
+ val productType: ProductType,
+ val walletData: WalletData?,
+ val secondTwinPublicKey: String? = null,
+) : CommandResponse {
+
+ fun getBlockchain(): Blockchain {
+ if (productType == ProductType.Note) return getTangemNoteBlockchain(card) ?: return Blockchain.Unknown
+ val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown
+ return Blockchain.fromId(blockchainName)
+ }
+
+ fun getPrimaryToken(): Token? {
+ val cardToken = walletData?.token ?: return null
+ return Token(
+ cardToken.name,
+ cardToken.symbol,
+ cardToken.contractAddress,
+ cardToken.decimals,
+ Blockchain.fromId(walletData.blockchain)
+ )
+ }
+
+ fun isTangemNote(): Boolean = productType == ProductType.Note
+ fun isTangemWallet(): Boolean = productType == ProductType.Wallet
+ fun isTangemTwins(): Boolean = productType == ProductType.Twins
+ fun isTangemOtherCards(): Boolean = productType == ProductType.Other
+
+ fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
+}
+
+class ScanProductTask(val card: Card? = null) : CardSessionRunnable {
+
+ override fun run(
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit,
+ ) {
+ val card = this.card ?: session.environment.card.guard {
+ callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
+ return
+ }
+
+ val error = getErrorIfExcludedCard(card)
+ if (error != null) {
+ callback(CompletionResult.Failure(error))
+ return
+ }
+
+ val commandProcessor = when {
+ TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
+ card.isTangemTwins() -> ScanTwinProcessor()
+ TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor()
+ else -> ScanOtherCardsProcessor()
+ }
+ commandProcessor.proceed(card, session) { processorResult ->
+ when (processorResult) {
+ is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult ->
+ when (scanTaskResult) {
+ is CompletionResult.Success -> callback(CompletionResult.Success(processorResult.data))
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(scanTaskResult.error))
+ }
+ }
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(processorResult.error))
+ }
+ }
+ }
+
+ private fun getErrorIfExcludedCard(card: Card): TangemError? {
+ if (card.isExcluded()) return TapSdkError.CardForDifferentApp
+
+ return null
+ }
+}
+
+private fun Card.isTangemTwins(): Boolean = TwinsHelper.getTwinCardNumber(cardId) != null
+
+private class ScanNoteProcessor : ProductCommandProcessor {
+ override fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit
+ ) {
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Note, session.environment.walletData)))
+ }
+}
+
+private class ScanWalletProcessor : ProductCommandProcessor {
+ override fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit
+ ) {
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
+ }
+}
+
+private class ScanTwinProcessor : ProductCommandProcessor {
+ override fun proceed(
+ card: Card,
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit,
+ ) {
+ ReadIssuerDataCommand().run(session) { readDataResult ->
+ when (readDataResult) {
+ is CompletionResult.Success -> {
+ val publicKey = card.getSingleWallet()?.publicKey
+ if (publicKey == null) {
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null)))
+ return@run
+ }
+ val verified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey)
+ if (verified) {
+ val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
+ val walletData = session.environment.walletData
+ val response = ScanResponse(card, ProductType.Twins, walletData, twinPublicKey.toHexString())
+ callback(CompletionResult.Success(response))
+ } else {
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null)))
+ }
+ }
+ is CompletionResult.Failure ->
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null)))
+ }
+ }
+ }
+
+}
+
+private class ScanOtherCardsProcessor : ProductCommandProcessor {
+
+ override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit) {
+ val walletData = session.environment.walletData
+
+ if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData)))
+ return
+ }
+
+ val curvesToCreate = card.getCurvesForNonCreatedWallets()
+ if (curvesToCreate.isEmpty()) {
+ callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData)))
+ return
+ }
+
+ CreateProductWalletsTask(curvesToCreate).run(session) { result ->
+ when (result) {
+ is CompletionResult.Success -> {
+ PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult ->
+ when (readResult) {
+ is CompletionResult.Success -> {
+ val response = ScanResponse(readResult.data, ProductType.Other, walletData)
+ callback(CompletionResult.Success(response))
+ }
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
+ }
+ }
+ }
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
+ }
+ }
+ }
+}
+
+fun Card.getCurvesForNonCreatedWallets(): List {
+ val curvesPresent = wallets.map { it.curve }
+ val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent)
+ return curvesForNonCreatedWallets.toList()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
index c5d9d91396..032c0b97ca 100644
--- a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
+++ b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
@@ -3,7 +3,7 @@ package com.tangem.tap.domain.termsOfUse
import android.content.res.Resources
import android.net.Uri
import androidx.core.os.ConfigurationCompat
-import com.tangem.commands.common.card.Card
+import com.tangem.common.card.Card
import java.util.*
/**
@@ -13,8 +13,8 @@ class CardTou {
private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)
fun getUrl(card: Card): Uri? {
- val issuerName = card.cardData?.issuerName ?: return null
- if (issuerName.toLowerCase() != "start2coin") return null
+ val issuerName = card.issuer.name ?: return null
+ if (issuerName.lowercase(Locale.getDefault()) != "start2coin") return null
val baseUrl = "https://app.tangem.com/tou/"
val regionCode = regionCode(card.cardId) ?: "fr"
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
index 754d7ea239..9a6735ae2e 100644
--- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
@@ -2,22 +2,26 @@ package com.tangem.tap.domain.tokens
import android.app.Application
import android.content.Context
+import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
-import com.tangem.commands.common.card.FirmwareVersion
+import com.tangem.common.card.FirmwareVersion
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.network.createMoshi
class CurrenciesRepository(val context: Application) {
private val moshi = createMoshi()
- private val tokensAdapter: JsonAdapter> = moshi.adapter(
- Types.newParameterizedType(Set::class.java, TokenDao::class.java)
+ private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, Blockchain::class.java)
)
- private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
- Types.newParameterizedType(Set::class.java, Blockchain::class.java)
+ private val tokensAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, TokenDao::class.java)
+ )
+ private val obsoleteTokensAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java)
)
fun loadCardCurrencies(cardId: String): CardCurrencies? {
@@ -38,49 +42,58 @@ class CurrenciesRepository(val context: Application) {
}
fun saveAddedTokens(cardId: String, tokens: Collection) {
- saveTokens(cardId, loadSavedTokens(cardId) + tokens)
+ saveTokens(cardId, loadSavedTokens(cardId) + tokens.distinct())
}
fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
val blockchains = loadSavedBlockchains(cardId) + blockchain
- saveBlockchains(cardId, blockchains)
+ saveBlockchains(cardId, blockchains.distinct())
}
fun removeToken(cardId: String, token: Token) {
- val tokens = loadSavedTokens(cardId).filterNot { it == token }.toSet()
+ val tokens = loadSavedTokens(cardId).filterNot { it == token }
saveTokens(cardId, tokens)
}
fun removeBlockchain(cardId: String, blockchain: Blockchain) {
- val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }.toSet()
+ val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }
saveBlockchains(cardId, blockchains)
}
- private fun loadSavedTokens(cardId: String): Set {
- return try {
- val json = context.readFileText(getFileNameForTokens(cardId))
- tokensAdapter.fromJson(json)!!.map { it.toToken() }.toSet()
+ private fun loadSavedTokens(cardId: String): List {
+ val json = try {
+ context.readFileText(getFileNameForTokens(cardId))
} catch (exception: Exception) {
- emptySet()
+ return emptyList()
+ }
+
+ return try {
+ tokensAdapter.fromJson(json)!!.map { it.toToken() }
+ } catch (exception: Exception) {
+ try {
+ obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() }
+ } catch (exception: Exception) {
+ emptyList()
+ }
}
}
- private fun saveTokens(cardId: String, tokens: Set) {
- val json = tokensAdapter.toJson(tokens.map { TokenDao.fromToken(it) }.toSet())
+ private fun saveTokens(cardId: String, tokens: List) {
+ val json = tokensAdapter.toJson(tokens.distinct().map { TokenDao.fromToken(it) })
context.rewriteFile(json, getFileNameForTokens(cardId))
}
- private fun loadSavedBlockchains(cardId: String): Set {
+ private fun loadSavedBlockchains(cardId: String): List {
return try {
val json = context.readFileText(getFileNameForBlockchains(cardId))
- blockchainsAdapter.fromJson(json) ?: emptySet()
+ blockchainsAdapter.fromJson(json) ?: emptyList()
} catch (exception: Exception) {
- emptySet()
+ emptyList()
}
}
- private fun saveBlockchains(cardId: String, blockchains: Set) {
- val json = blockchainsAdapter.toJson(blockchains)
+ private fun saveBlockchains(cardId: String, blockchains: List) {
+ val json = blockchainsAdapter.toJson(blockchains.distinct())
context.rewriteFile(json, getFileNameForBlockchains(cardId))
}
@@ -93,85 +106,144 @@ class CurrenciesRepository(val context: Application) {
}
}
- fun getPopularTokens(): List {
- val json = context.assets.readJsonFileToString(POPULAR_TOKENS_FILE_NAME)
- return tokensAdapter.fromJson(json)!!.map { it.toToken() }
+ fun getPopularTokens(isTestNet: Boolean = false): List {
+ val ethereumTokensFileName =
+ if (isTestNet) ETHEREUM_TESTNET_TOKENS_FILE_NAME else ETHEREUM_TOKENS_FILE_NAME
+ val bscTokensFileName =
+ if (isTestNet) BSC_TESTNET_TOKENS_FILE_NAME else BSC_TOKENS_FILE_NAME
+ val binanceTokensFileName =
+ if (isTestNet) BINANCE_TESTNET_TOKENS_FILE_NAME else BINANCE_TOKENS_FILE_NAME
+
+ val ethereumTokensJson = context.assets.readJsonFileToString(ethereumTokensFileName)
+ val bscTokensJson = context.assets.readJsonFileToString(bscTokensFileName)
+ val binanceTokensJson = context.assets.readJsonFileToString(binanceTokensFileName)
+
+ return tokensAdapter.fromJson(ethereumTokensJson)!!.map { it.toToken() } +
+ tokensAdapter.fromJson(bscTokensJson)!!.map { it.toToken() } +
+ tokensAdapter.fromJson(binanceTokensJson)!!.mapNotNull {
+ // temporary exclude Binance BEP-8 tokens
+ if (it.type != null && it.type == BINANCE_TOKEN_TYPE_BEP8) null else it.toToken()
+ }
}
- fun getBlockchains(cardFirmware: FirmwareVersion?): List {
- return if (cardFirmware == null || cardFirmware.major < 4) {
- secp256k1Blockchains
+ fun getBlockchains(
+ cardFirmware: FirmwareVersion?,
+ isTestNet: Boolean = false
+ ): List {
+ return if (cardFirmware == null || cardFirmware < FirmwareVersion.MultiWalletAvailable) {
+ Blockchain.secp256k1Blockchains(isTestNet)
} else {
- secp256k1Blockchains + ed25519Blockchains
+ Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet)
}
}
companion object {
- private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens"
+ private const val ETHEREUM_TOKENS_FILE_NAME = "ethereum_tokens"
+ private const val ETHEREUM_TESTNET_TOKENS_FILE_NAME = "ethereum_tokens_testnet"
+ private const val BSC_TOKENS_FILE_NAME = "bsc_tokens"
+ private const val BSC_TESTNET_TOKENS_FILE_NAME = "bsc_tokens_testnet"
+ private const val BINANCE_TOKENS_FILE_NAME = "binance_tokens"
+ private const val BINANCE_TESTNET_TOKENS_FILE_NAME = "binance_tokens_testnet"
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
+ private const val BINANCE_TOKEN_TYPE_BEP8 = "bep8"
+
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
fun getFileNameForBlockchains(cardId: String): String =
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
-
- private val secp256k1Blockchains = listOf(
- Blockchain.Bitcoin,
- Blockchain.BitcoinCash,
- Blockchain.Binance,
- Blockchain.BSC,
- Blockchain.Litecoin,
- Blockchain.XRP,
- Blockchain.Tezos,
- Blockchain.Ethereum,
- Blockchain.RSK,
- Blockchain.Polygon,
- Blockchain.Dogecoin,
- )
- private val ed25519Blockchains = listOf(Blockchain.CardanoShelley, Blockchain.Stellar)
}
}
-
@JsonClass(generateAdapter = true)
data class TokenDao(
val name: String,
val symbol: String,
val contractAddress: String,
val decimalCount: Int,
+ @Json(name = "blockchain")
+ val blockchainDao: BlockchainDao,
+ val type: String? = null
) {
fun toToken(): Token {
- return Token(name = name,
+ return Token(
+ name = name,
symbol = symbol,
contractAddress = contractAddress,
- decimals = decimalCount)
+ decimals = decimalCount,
+ blockchain = blockchainDao.toBlockchain()
+ )
}
companion object {
fun fromToken(token: Token): TokenDao {
- return TokenDao(name = token.name,
+ return TokenDao(
+ name = token.name,
symbol = token.symbol,
contractAddress = token.contractAddress,
- decimalCount = token.decimals)
-
+ decimalCount = token.decimals,
+ blockchainDao = BlockchainDao.fromBlockchain(token.blockchain)
+ )
}
}
}
+@JsonClass(generateAdapter = true)
+data class BlockchainDao(
+ @Json(name = "key")
+ val name: String,
+ @Json(name = "testnet")
+ val isTestNet: Boolean
+) {
+ fun toBlockchain(): Blockchain {
+ val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() }
+ ?: throw Exception("Invalid BlockchainDao")
+ return if (!isTestNet) blockchain else blockchain.getTestnetVersion()
+ ?: throw Exception("Invalid BlockchainDao")
+ }
+
+ companion object {
+ fun fromBlockchain(blockchain: Blockchain): BlockchainDao {
+ val name = blockchain.name.removeSuffix("Testnet").lowercase()
+ val isTestnet = blockchain.name.endsWith("Testnet")
+ return BlockchainDao(name, isTestnet)
+ }
+ }
+}
+
+@JsonClass(generateAdapter = true)
+data class ObsoleteTokenDao(
+ val name: String,
+ val symbol: String,
+ val contractAddress: String,
+ val decimalCount: Int,
+) {
+ fun toToken(): Token {
+ return Token(
+ name = name,
+ symbol = symbol,
+ contractAddress = contractAddress,
+ decimals = decimalCount
+ )
+ }
+}
+
@JsonClass(generateAdapter = true)
data class CardCurrenciesDao(
- val tokens: Set,
- val blockchains: Set,
+ val tokens: List,
+ val blockchains: List,
) {
fun toCardCurrencies(): CardCurrencies {
- return CardCurrencies(tokens = tokens.map { it.toToken() }.toSet(),
- blockchains = blockchains)
+ return CardCurrencies(
+ tokens = tokens.map { it.toToken() }.distinct(),
+ blockchains = blockchains
+ )
}
companion object {
fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
return CardCurrenciesDao(
- tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.toSet(),
+ tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(),
blockchains = cardCurrencies.blockchains
)
}
@@ -179,6 +251,6 @@ data class CardCurrenciesDao(
}
data class CardCurrencies(
- val tokens: Set,
- val blockchains: Set,
+ val tokens: List,
+ val blockchains: List,
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt
new file mode 100644
index 0000000000..775d3748a7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt
@@ -0,0 +1,45 @@
+package com.tangem.tap.domain.topup
+
+import com.tangem.Message
+import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.extensions.Result
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.domain.TangemSigner
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdk
+import java.math.BigDecimal
+
+class TopUpManager {
+ suspend fun topUpTestErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
+ walletManager.update()
+
+ val amountToSend = Amount(walletManager.wallet.blockchain)
+ val destinationAddress = token.contractAddress
+
+ val feeResult = walletManager.getFee(amountToSend,
+ destinationAddress) as? Result.Success ?: return
+ val fee = feeResult.data[0]
+
+ if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) {
+ return
+ }
+
+ val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
+
+ val signer = TangemSigner(
+ tangemSdk = tangemSdk, Message()
+ ) { signResponse ->
+ store.dispatch(
+ GlobalAction.UpdateWalletSignedHashes(
+ walletSignedHashes = signResponse.totalSignedHashes,
+ walletPublicKey = walletManager.wallet.publicKey,
+ remainingSignatures = signResponse.remainingSignatures
+ )
+ )
+ }
+ walletManager.send(transaction, signer)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt
new file mode 100644
index 0000000000..c8599f6d4c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt
@@ -0,0 +1,93 @@
+package com.tangem.tap.domain.topup
+
+import android.net.Uri
+import android.util.Base64
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+class TradeCryptoHelper {
+
+ companion object {
+
+ const val TRANSACTION_ID_PARAM = "transactionId"
+ const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
+ const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
+ const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
+
+ private const val REDIRECT_URL_BUY = "tangem://success.tangem.com"
+ private const val REDIRECT_URL_SELL_REQUEST = "tangem://sell-request.tangem.com"
+
+ private const val BASE_URL_BUY =
+ "https://buy.moonpay.io"
+// "https://buy-staging.moonpay.io" // TESTNET
+ private const val BASE_URL_SELL =
+ "https://sell.moonpay.com"
+// "https://sell-staging.moonpay.com" // TESTNET
+
+ private const val API_KEY_PATH = "?apiKey="
+ private const val CURRENCY_PATH = "¤cyCode="
+ private const val WALLET_ADDRESS_PATH = "&walletAddress="
+
+ private const val REFUND_WALLET_ADDRESS_PATH = "&refundWalletAddress="
+ private const val BASE_CURRENCY_PATH = "&baseCurrencyCode="
+
+ private const val REDIRECT_URL_PATH = "&redirectURL="
+ private const val SIGNATURE_PATH = "&signature="
+
+ private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId="
+
+ fun getUrl(
+ action: Action,
+ blockchain: Blockchain?,
+ cryptoCurrencyName: CryptoCurrencyName,
+ walletAddress: String,
+ apiKey: String,
+ secretKey: String,
+ ): String {
+ val originalQuery: String
+ val baseUrl: String
+ when (action) {
+ Action.Buy -> {
+ if (blockchain?.isTestnet() == true) {
+ return blockchain.getTestnetTopUpUrl() ?: ""
+ }
+ baseUrl = BASE_URL_BUY
+ originalQuery = API_KEY_PATH + apiKey.urlEncode() +
+ CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
+ WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
+ REDIRECT_URL_PATH + REDIRECT_URL_BUY.urlEncode()
+ }
+ Action.Sell -> {
+ baseUrl = BASE_URL_SELL
+ originalQuery = API_KEY_PATH + apiKey.urlEncode() +
+ BASE_CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
+ REFUND_WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
+ REDIRECT_URL_PATH + REDIRECT_URL_SELL_REQUEST.urlEncode()
+ }
+ }
+ val signature = createSignature(originalQuery, secretKey)
+ return baseUrl + originalQuery + SIGNATURE_PATH + signature.urlEncode()
+
+ }
+
+ fun getSellCryptoReceiptUrl(transactionId: String): String {
+ return BASE_URL_SELL + TRANSACTION_RECEIPT_PATH + transactionId
+ }
+
+ private fun String.urlEncode(): String {
+ return Uri.encode(this)
+ }
+
+ private fun createSignature(data: String, key: String): String {
+ val sha256Hmac = Mac.getInstance("HmacSHA256")
+ val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
+ sha256Hmac.init(secretKey)
+ val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
+ return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
+ }
+ }
+
+ enum class Action { Buy, Sell }
+}
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt
index 0251173136..400ee19ae3 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt
@@ -1,34 +1,34 @@
package com.tangem.tap.domain.twins
-import com.tangem.CardSession
-import com.tangem.CardSessionRunnable
-import com.tangem.commands.wallet.CreateWalletResponse
-import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.common.CompletionResult
-import com.tangem.common.TangemSdkConstants
-import com.tangem.tap.domain.extensions.getDefaultWalletIndex
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.operations.wallet.CreateWalletCommand
+import com.tangem.operations.wallet.CreateWalletResponse
+import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.domain.extensions.getSingleWallet
-import com.tangem.tasks.CreateWalletTask
class CreateFirstTwinWalletTask : CardSessionRunnable {
- override val requiresPin2 = false
-
override fun run(
session: CardSession,
callback: (result: CompletionResult) -> Unit,
) {
- if (session.environment.card?.getSingleWallet()?.publicKey != null) {
- PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response ->
+ val publicKey = session.environment.card?.getSingleWallet()?.publicKey
+ if (publicKey != null) {
+ PurgeWalletCommand(publicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
- session.environment.card = session.environment.card?.changeStatusToEmpty()
- CreateWalletTask().run(session) { callback(it) }
+ session.environment.card = session.environment.card?.setWallets(emptyList())
+ CreateWalletCommand(EllipticCurve.Secp256k1)
+ .run(session) { callback(it) }
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
}
}
} else {
- CreateWalletTask().run(session) { callback(it) }
+ CreateWalletCommand(EllipticCurve.Secp256k1)
+ .run(session) { callback(it) }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt
index 8224293d18..24b908cb0a 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt
@@ -1,14 +1,18 @@
package com.tangem.tap.domain.twins
-import com.tangem.*
-import com.tangem.commands.wallet.CreateWalletResponse
-import com.tangem.commands.wallet.PurgeWalletCommand
+import com.tangem.Message
import com.tangem.common.CompletionResult
-import com.tangem.common.TangemSdkConstants
+import com.tangem.common.KeyPair
+import com.tangem.common.card.EllipticCurve
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.TangemError
import com.tangem.common.extensions.hexToBytes
-import com.tangem.tap.domain.extensions.getDefaultWalletIndex
+import com.tangem.operations.wallet.CreateWalletCommand
+import com.tangem.operations.wallet.CreateWalletResponse
+import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.domain.extensions.getSingleWallet
-import com.tangem.tasks.CreateWalletTask
+import com.tangem.wallet.R
class CreateSecondTwinWalletTask(
private val firstPublicKey: String,
@@ -17,25 +21,20 @@ class CreateSecondTwinWalletTask(
private val preparingMessage: Message,
private val creatingWalletMessage: Message
) : CardSessionRunnable {
- override val requiresPin2 = true
-
-
-
override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
val card = session.environment.card
-
- if (card?.getSingleWallet()?.publicKey != null) {
-
+ val publicKey = card?.getSingleWallet()?.publicKey
+ if (publicKey != null) {
if (!card.cardId.startsWith(TwinsHelper.getPairCardSeries(firstCardId) ?: "")) {
- callback(CompletionResult.Failure(TangemSdkError.WrongCardType()))
+ callback(CompletionResult.Failure(WrongTwinCard()))
return
}
- session.setInitialMessage(preparingMessage)
- PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response ->
+ session.setMessage(preparingMessage)
+ PurgeWalletCommand(publicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
- session.environment.card = session.environment.card?.changeStatusToEmpty()
+ session.environment.card = session.environment.card?.setWallets(emptyList())
finishTask(session, callback)
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
@@ -47,11 +46,11 @@ class CreateSecondTwinWalletTask(
}
private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) {
- session.setInitialMessage(creatingWalletMessage)
- CreateWalletTask().run(session) { result ->
+ session.setMessage(creatingWalletMessage)
+ CreateWalletCommand(EllipticCurve.Secp256k1).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
- session.environment.card = session.environment.card?.changeStatusToLoaded()
+ session.environment.card = session.environment.card?.updateWallet(result.data.wallet)
WriteProtectedIssuerDataTask(
firstPublicKey.hexToBytes(), issuerKeys
@@ -67,4 +66,10 @@ class CreateSecondTwinWalletTask(
}
}
}
+
+ private class WrongTwinCard : TangemError {
+ override val code: Int = 50005
+ override var customMessage: String = code.toString()
+ override val messageResId = R.string.twins_wrong_card_error
+ }
}
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt
index 8eb0315241..c035973ac7 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt
@@ -1,30 +1,29 @@
package com.tangem.tap.domain.twins
-import com.tangem.CardSession
-import com.tangem.CardSessionRunnable
-import com.tangem.KeyPair
import com.tangem.common.CompletionResult
-import com.tangem.tap.domain.tasks.ScanNoteResponse
-import com.tangem.tap.domain.tasks.ScanNoteTask
-import com.tangem.tasks.PreflightReadSettings
-import com.tangem.tasks.PreflightReadTask
+import com.tangem.common.KeyPair
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.operations.PreflightReadMode
+import com.tangem.operations.PreflightReadTask
+import com.tangem.tap.domain.tasks.product.ScanProductTask
+import com.tangem.tap.domain.tasks.product.ScanResponse
class FinalizeTwinTask(
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,
-) : CardSessionRunnable {
- override val requiresPin2 = true
+) : CardSessionRunnable {
override fun run(
session: CardSession,
- callback: (result: CompletionResult) -> Unit,
+ callback: (result: CompletionResult) -> Unit,
) {
WriteProtectedIssuerDataTask(twinPublicKey, issuerKeys).run(session) { result ->
when (result) {
is CompletionResult.Success ->
- PreflightReadTask(PreflightReadSettings.FullCardRead).run(session) { readResult ->
+ PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult ->
when (readResult) {
is CompletionResult.Success ->
- ScanNoteTask(readResult.data).run(session, callback)
+ ScanProductTask(readResult.data).run(session, callback)
is CompletionResult.Failure ->
callback(CompletionResult.Failure(readResult.error))
}
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt
index ac8e5da05f..861f3db801 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt
@@ -1,52 +1,52 @@
package com.tangem.tap.domain.twins
-import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Types
-import com.tangem.KeyPair
import com.tangem.Message
import com.tangem.blockchain.extensions.Result
-import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
+import com.tangem.common.KeyPair
+import com.tangem.common.card.Card
+import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
-import com.tangem.crypto.CryptoUtils
-import com.tangem.tap.common.extensions.readAssetAsString
-import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.operations.wallet.CreateWalletResponse
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.network.createMoshi
import com.tangem.tap.tangemSdkManager
-class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: Context) {
+class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
- private val currentCardId: String = scanNoteResponse.card.cardId
+ private val currentCardId: String = card.cardId
private var currentCardPublicKey: String? = null
private var secondCardPublicKey: String? = null
- private val issuerKeyPair: KeyPair = getIssuerKeys(
- context, scanNoteResponse.card.issuerPublicKey!!.toHexString()
- )
+ private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString())
- suspend fun createFirstWallet(message: Message): SimpleResult {
- val response = tangemSdkManager.runTaskAsync(
- CreateFirstTwinWalletTask(), currentCardId, message
- )
+ suspend fun createFirstWallet(message: Message): CompletionResult {
+ val response = tangemSdkManager.runTaskAsync(CreateFirstTwinWalletTask(), currentCardId, message)
when (response) {
- is CompletionResult.Success -> {
- currentCardPublicKey = response.data.walletPublicKey.toHexString()
- return SimpleResult.Success
+ is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString()
+ is CompletionResult.Failure -> {
+ (response.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
+ card = card
+ )
+ }
}
- is CompletionResult.Failure -> return SimpleResult.failure(response.error)
}
-
+ return response
}
-
suspend fun createSecondWallet(
- initialMessage: Message,
- preparingMessage: Message,
- creatingWalletMessage: Message
- ): SimpleResult {
+ initialMessage: Message,
+ preparingMessage: Message,
+ creatingWalletMessage: Message,
+ ): CompletionResult {
val task = CreateSecondTwinWalletTask(
firstPublicKey = currentCardPublicKey!!,
firstCardId = currentCardId,
@@ -54,41 +54,47 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context:
preparingMessage = preparingMessage,
creatingWalletMessage = creatingWalletMessage
)
- val response = tangemSdkManager.runTaskAsync(
- task, null, initialMessage
- )
+ val response = tangemSdkManager.runTaskAsync(task, null, initialMessage)
when (response) {
is CompletionResult.Success -> {
- secondCardPublicKey = response.data.walletPublicKey.toHexString()
- return SimpleResult.Success
+ secondCardPublicKey = response.data.wallet.publicKey.toHexString()
+ }
+ is CompletionResult.Failure -> {
+ (response.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
+ card = card
+ )
+ }
}
- is CompletionResult.Failure -> return SimpleResult.failure(response.error)
}
-
+ return response
}
- suspend fun complete(message: Message): Result {
+ suspend fun complete(message: Message): Result {
val response = tangemSdkManager.runTaskAsync(
- FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair),
- currentCardId, message
+ FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair),
+ currentCardId, message
)
return when (response) {
is CompletionResult.Success -> Result.Success(response.data)
- is CompletionResult.Failure -> Result.failure(response.error)
+ is CompletionResult.Failure -> {
+ (response.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData,
+ card = card
+ )
+ }
+ Result.failure(response.error)
+ }
}
}
companion object {
- fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean {
- if (issuerData.size < 65) return false
- val publicKey = issuerData.sliceArray(0 until 65)
- val signedKey = issuerData.sliceArray(65 until issuerData.size)
- return (cardWalletPublicKey != null &&
- CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey))
- }
-
- private fun getIssuerKeys(context: Context, publicKey: String): KeyPair {
- val issuer = getIssuers(context).first { it.publicKey == publicKey }
+ private fun getIssuerKeys(reader: AssetReader, publicKey: String): KeyPair {
+ val issuer = getIssuers(reader).first { it.publicKey == publicKey }
return KeyPair(
publicKey = issuer.publicKey.hexToBytes(),
privateKey = issuer.privateKey.hexToBytes()
@@ -101,13 +107,17 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context:
)
}
- private fun getIssuers(context: Context): List {
- val file = context.readAssetAsString("tangem-app-config/issuers")
+ private fun getIssuers(reader: AssetReader): List {
+ val file = reader.readAssetAsString("tangem-app-config/issuers")
return getAdapter().fromJson(file)!!
}
}
}
+interface AssetReader {
+ fun readAssetAsString(name: String): String
+}
+
private class Issuer(
val id: String,
val privateKey: String,
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt
new file mode 100644
index 0000000000..c8f3c02952
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt
@@ -0,0 +1,170 @@
+package com.tangem.tap.domain.twins
+
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.PropertyValuesHolder
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.tangem.common.extensions.VoidCallback
+import com.tangem.tap.common.leapfrogWidget.LeapView
+import com.tangem.tap.common.leapfrogWidget.LeapViewState
+import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
+
+/**
+[REDACTED_AUTHOR]
+ */
+class TwinsCardWidget(
+ val leapfrogWidget: LeapfrogWidget,
+ val getTopOfAnchorViewForActivateState: () -> Float
+) {
+
+ var state: TwinCardsWidgetState? = null
+ private set
+
+ private val animationDuration = 300L
+
+ init {
+ if (leapfrogWidget.getViewsCount() != 2) throw UnsupportedOperationException()
+ }
+
+ fun toWelcome(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ if (state == TwinCardsWidgetState.Welcome) return
+
+ state = TwinCardsWidgetState.Welcome
+ val animator = createAnimator(animate)
+ animator.playTogether(
+ createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)),
+ createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second))
+ )
+ animator.doOnEnd { onEnd() }
+ leapfrogWidget.fold(animate) { animator.start() }
+ }
+
+ fun toLeapfrog(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ if (state == TwinCardsWidgetState.Leapfrog) return
+
+ state = TwinCardsWidgetState.Leapfrog
+ val animator = createAnimator(animate)
+ animator.playTogether(
+ createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)),
+ createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second))
+ )
+ animator.doOnEnd {
+ leapfrogWidget.initViews()
+ leapfrogWidget.unfold(animate, onEnd)
+ }
+ animator.start()
+ }
+
+ fun toActivate(animate: Boolean = true, onEnd: VoidCallback = {}) {
+ if (state == TwinCardsWidgetState.Activate) return
+
+ state = TwinCardsWidgetState.Activate
+ val animator = createAnimator(animate)
+ animator.playTogether(
+ createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)),
+ createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second))
+ )
+ animator.doOnEnd { onEnd() }
+ animator.start()
+ }
+
+ private fun createAnimator(animate: Boolean): AnimatorSet {
+ return AnimatorSet().apply {
+ duration = if (animate) animationDuration else 0
+ }
+ }
+
+ private fun createAnimator(cardNumber: TwinCardNumber, properties: TwinsCardProperties): ObjectAnimator {
+ val view = getLeapViewByCardNumber(cardNumber).view
+ val animator = ObjectAnimator.ofPropertyValuesHolder(view, *properties.createValuesHolders().toTypedArray())
+ view.elevation = properties.elevation
+ return animator
+ }
+
+ private fun createWelcomeProperties(cardNumber: TwinCardNumber): TwinsCardProperties {
+ return when (cardNumber) {
+ TwinCardNumber.First -> {
+ TwinsCardProperties(
+ xTranslation = -120f,
+ yTranslation = -220f,
+ rotation = -3f,
+ elevation = 1f,
+ scale = 1f,
+ )
+ }
+ TwinCardNumber.Second -> {
+ TwinsCardProperties(
+ xTranslation = 170f,
+ yTranslation = 170f,
+ rotation = -3f,
+ elevation = 0f,
+ scale = 1f,
+ )
+ }
+ }
+ }
+
+ private fun createLeapfrogProperties(cardNumber: TwinCardNumber): TwinsCardProperties {
+ val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state)
+ return twinProperties.copy(
+ xTranslation = 0f,
+ yTranslation = 0f,
+ rotation = 0f,
+ scale = 1f,
+ )
+ }
+
+ private fun createActivateProperties(cardNumber: TwinCardNumber): TwinsCardProperties {
+ val topOfAnchorView = getTopOfAnchorViewForActivateState()
+ val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state)
+ return twinProperties.copy(
+ xTranslation = twinProperties.xTranslation,
+ yTranslation = twinProperties.yTranslation - topOfAnchorView,
+ rotation = 0f,
+ scale = twinProperties.scale - 0.5f,
+ )
+ }
+
+ private fun getLeapViewByCardNumber(cardNumber: TwinCardNumber): LeapView {
+ return when (cardNumber) {
+ TwinCardNumber.First -> leapfrogWidget.getViewByPosition(0)
+ TwinCardNumber.Second -> leapfrogWidget.getViewByPosition(1)
+ }
+ }
+}
+
+enum class TwinCardsWidgetState {
+ Welcome, Leapfrog, Activate
+}
+
+private data class TwinsCardProperties(
+ val xTranslation: Float = 0f,
+ val yTranslation: Float = 0f,
+ val rotation: Float = 0f,
+ val elevation: Float = 0f,
+ val scale: Float = 1.1f,
+) {
+
+ fun createValuesHolders(): List {
+ return listOf(
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation),
+ PropertyValuesHolder.ofFloat(View.ROTATION, rotation),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, scale),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, scale),
+ )
+ }
+
+ companion object {
+ fun from(leapViewState: LeapViewState): TwinsCardProperties {
+ val leapViewProperties = leapViewState.properties
+
+ return TwinsCardProperties(
+ yTranslation = leapViewProperties.yTranslation,
+ elevation = leapViewProperties.elevationEnd,
+ scale = leapViewProperties.scale,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt
index 68a70fe179..8196f39b22 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt
@@ -1,15 +1,20 @@
package com.tangem.tap.domain.twins
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.CardStatus
-import com.tangem.commands.common.card.masks.Product
-import com.tangem.commands.wallet.WalletStatus
-import com.tangem.tap.common.extensions.isEven
+import com.tangem.common.card.Card
+import com.tangem.crypto.CryptoUtils
class TwinsHelper {
companion object {
const val TWIN_FILE_NAME = "TwinPublicKey"
+ fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean {
+ if (issuerData.size < 65 || cardWalletPublicKey == null) return false
+
+ val publicKey = issuerData.sliceArray(0 until 65)
+ val signedKey = issuerData.sliceArray(65 until issuerData.size)
+ return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey)
+ }
+
fun getTwinCardNumber(cardId: String): TwinCardNumber? {
return when {
firstCardSeries.map { cardId.startsWith(it) }.contains(true) -> {
@@ -37,22 +42,6 @@ class TwinsHelper {
}
}
- fun getTwinsCardId(cardId: String): String? {
- val cardIdWithNewSeries = when (getTwinCardNumber(cardId) ?: return null) {
- TwinCardNumber.First -> {
- val index = if (cardId.startsWith(firstCardSeries[0])) 0 else 1
- cardId.replace(firstCardSeries[index], secondCardSeries[index])
- }
- TwinCardNumber.Second -> {
- val index = if (cardId.startsWith(secondCardSeries[0])) 0 else 1
- cardId.replace(secondCardSeries[index], firstCardSeries[index])
- }
- }
- val cardIdWithoutChecksum = cardIdWithNewSeries.dropLast(1)
- val checkSum = cardIdWithoutChecksum.calculateLuhn()
- return cardIdWithoutChecksum + checkSum
- }
-
fun getTwinCardIdForUser(cardId: String): String {
if (cardId.length < 16) return cardId
@@ -66,21 +55,6 @@ class TwinsHelper {
}
}
-private fun String.calculateLuhn(): Int {
- val checksum = this.reversed()
- .mapIndexed { index, c ->
- val digit = if (c in '0'..'9') c - '0' else c - 'A'
- if (!index.isEven()) {
- digit
- } else {
- val newDigit = digit * 2
- if (newDigit >= 10) newDigit - 9 else newDigit
- }
- }.sum()
- .rem(10)
- return (10 - checksum) % 10
-}
-
enum class TwinCardNumber(val number: Int) {
First(1), Second(2);
@@ -90,20 +64,15 @@ enum class TwinCardNumber(val number: Int) {
}
}
-fun Card.isTwinCard(): Boolean {
- return this.cardData?.productMask?.contains(Product.TwinCard) == true
+@Deprecated("Use ScanResponse.isTangemTwin")
+fun Card.isTangemTwin(): Boolean {
+ return TwinsHelper.getTwinCardNumber(cardId) != null
+}
+
+fun Card.getTwinCardNumber(): TwinCardNumber? {
+ return TwinsHelper.getTwinCardNumber(this.cardId)
}
fun Card.getTwinCardIdForUser(): String {
return TwinsHelper.getTwinCardIdForUser(this.cardId)
-}
-
-fun Card.changeStatusToLoaded(): Card {
- val wallets = wallets.map { it.copy(status = WalletStatus.Loaded) }
- return copy(status = CardStatus.Loaded, wallets = wallets)
-}
-
-fun Card.changeStatusToEmpty(): Card {
- val wallets = wallets.map { it.copy(status = WalletStatus.Empty) }
- return copy(status = CardStatus.Empty, wallets = wallets)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt
index bf6c735be5..94ce765f12 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt
@@ -1,49 +1,61 @@
package com.tangem.tap.domain.twins
-import com.tangem.CardSession
-import com.tangem.CardSessionRunnable
-import com.tangem.KeyPair
-import com.tangem.TangemSdkError
-import com.tangem.commands.*
import com.tangem.common.CompletionResult
-import com.tangem.common.TangemSdkConstants
+import com.tangem.common.KeyPair
+import com.tangem.common.SuccessResponse
+import com.tangem.common.core.CardSession
+import com.tangem.common.core.CardSessionRunnable
+import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.guard
import com.tangem.common.files.FileHashHelper
-import com.tangem.tap.domain.extensions.getDefaultWalletIndex
+import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
+import com.tangem.operations.issuerAndUserData.ReadIssuerDataResponse
+import com.tangem.operations.issuerAndUserData.WriteIssuerDataCommand
+import com.tangem.operations.sign.SignHashCommand
+import com.tangem.tap.domain.extensions.getSingleWallet
class WriteProtectedIssuerDataTask(
- private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair
-) : CardSessionRunnable {
- override val requiresPin2 = true
+ private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,
+) : CardSessionRunnable {
- override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
- val hashes = arrayOf(twinPublicKey.calculateSha256())
- SignCommand(hashes, TangemSdkConstants.getDefaultWalletIndex()).run(session) { signResult ->
- when (signResult) {
- is CompletionResult.Success -> {
- ReadIssuerDataCommand().run(session) { readResult ->
- when (readResult) {
- is CompletionResult.Success -> {
- writeIssuerData(
- twinPublicKey, issuerKeys, signResult.data.signatures[0],
+ override fun run(
+ session: CardSession,
+ callback: (result: CompletionResult) -> Unit,
+ ) {
+ SignHashCommand(
+ twinPublicKey.calculateSha256(),
+ session.environment.card!!.getSingleWallet()!!.publicKey
+ )
+ .run(session) { signResult ->
+ when (signResult) {
+ is CompletionResult.Success -> {
+ ReadIssuerDataCommand().run(session) { readResult ->
+ when (readResult) {
+ is CompletionResult.Success -> {
+ writeIssuerData(
+ twinPublicKey, issuerKeys, signResult.data.signature,
readResult.data, session, callback
- )
+ )
+ }
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(
+ readResult.error))
}
- is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
}
}
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error))
}
- is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error))
}
- }
}
private fun writeIssuerData(
- twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
- readResponse: ReadIssuerDataResponse,
- session: CardSession, callback: (result: CompletionResult
- ) -> Unit) {
+ twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
+ readResponse: ReadIssuerDataResponse,
+ session: CardSession,
+ callback: (
+ result: CompletionResult,
+ ) -> Unit,
+ ) {
val cardId = session.environment.card?.cardId.guard {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
@@ -51,11 +63,11 @@ class WriteProtectedIssuerDataTask(
val counter = (readResponse.issuerDataCounter ?: 0) + 1
val data = twinPublicKey + cardSignature
val signedByIssuer = FileHashHelper.prepareHashes(
- cardId, data, counter, issuerKeys.privateKey
+ cardId, data, counter, issuerKeys.privateKey
)
WriteIssuerDataCommand(
- data, signedByIssuer.finalizingSignature!!,
- counter, issuerKeys.publicKey
+ data, signedByIssuer.finalizingSignature!!,
+ counter, issuerKeys.publicKey
).run(session, callback)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
index 44eece7399..f93217e447 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.hexToBytes
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.*
@@ -15,11 +16,9 @@ import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import com.trustwallet.walletconnect.models.session.WCSession
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import okhttp3.Interceptor
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
+import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.util.*
@@ -29,9 +28,9 @@ class WalletConnectManager {
private val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
- .connectTimeout(30, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .writeTimeout(30, TimeUnit.SECONDS)
+ .connectTimeout(20, TimeUnit.SECONDS)
+ .readTimeout(20, TimeUnit.SECONDS)
+ .writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor(RetryInterceptor())
.build()
@@ -59,6 +58,18 @@ class WalletConnectManager {
client = client,
wallet = wallet
)
+ setupConnectionTimeoutCheck(session)
+ }
+
+ private fun setupConnectionTimeoutCheck(session: WCSession) {
+ scope.launch {
+ delay(20_000)
+ val data = sessions[session]
+ if (data != null && data.peerMeta == null) {
+ disconnect(session)
+ store.dispatchOnMain(WalletConnectAction.OpeningSessionTimeout(session))
+ }
+ }
}
fun restoreSessions() {
@@ -136,6 +147,10 @@ class WalletConnectManager {
}
private fun onSessionClosed(session: WCSession) {
+ FirebaseAnalyticsHandler.logWcEvent(
+ FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
+ FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url)
+ )
sessions.remove(session)
walletConnectRepository.removeSession(session)
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
@@ -184,7 +199,6 @@ class WalletConnectManager {
acceptRequest(data.session.session, data.id, hash)
sessions[data.session.session] = activeData.copy(transactionData = null)
}
-
}
fun handlePersonalSignRequest(
@@ -235,6 +249,10 @@ class WalletConnectManager {
store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession(
sessionData))
}
+ FirebaseAnalyticsHandler.logWcEvent(
+ FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
+ FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url)
+ )
}
}
client.onSessionUpdate = { id: Long, update: WCSessionUpdate ->
@@ -244,6 +262,11 @@ class WalletConnectManager {
}
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSendTransaction: $transaction")
+ FirebaseAnalyticsHandler.logWcEvent(
+ FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
+ FirebaseAnalyticsHandler.WcAction.SendTransaction
+ )
+ )
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
@@ -252,10 +275,14 @@ class WalletConnectManager {
type = WcTransactionType.EthSendTransaction
))
}
-
}
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
Timber.d("onEthSignTransaction: $transaction")
+ FirebaseAnalyticsHandler.logWcEvent(
+ FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
+ FirebaseAnalyticsHandler.WcAction.SignTransaction
+ )
+ )
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest(
transaction = transaction,
@@ -267,6 +294,11 @@ class WalletConnectManager {
}
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
Timber.d("onEthSign: $message")
+ FirebaseAnalyticsHandler.logWcEvent(
+ FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
+ FirebaseAnalyticsHandler.WcAction.PersonalSign
+ )
+ )
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
store.dispatchOnMain(WalletConnectAction.HandlePersonalSignRequest(
message,
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
index a78b81d7ce..e2ac447999 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
@@ -10,10 +10,11 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.extensions.*
-import com.tangem.commands.SignCommand
-import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.CompletionResult
+import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
+import com.tangem.operations.sign.SignHashCommand
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
@@ -60,7 +61,7 @@ class WalletConnectSdkHelper {
val decimals = blockchain.decimals()
- val value = transaction.value?.hexToBigDecimal()
+ val value = (transaction.value ?: "0").hexToBigDecimal()
?.movePointLeft(decimals) ?: return null
val gasPrice = transaction.gasPrice?.hexToBigDecimal()
@@ -128,6 +129,12 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
+ (result.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction,
+ )
+ }
Timber.e(result.error)
null
}
@@ -142,9 +149,9 @@ class WalletConnectSdkHelper {
gasLimit = null
) ?: return null
- val command = SignCommand(
- hashes = arrayOf(dataToSign.hash),
- walletIndex = WalletIndex.PublicKey(data.walletManager.wallet.publicKey)
+ val command = SignHashCommand(
+ hash = dataToSign.hash,
+ walletPublicKey = data.walletManager.wallet.publicKey
)
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message())
return when (result) {
@@ -152,6 +159,12 @@ class WalletConnectSdkHelper {
HEX_PREFIX + result.data
}
is CompletionResult.Failure -> {
+ (result.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
+ )
+ }
Timber.e(result.error.customMessage)
null
}
@@ -196,18 +209,21 @@ class WalletConnectSdkHelper {
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? {
val publicKey = wallet.walletPublicKey.hexToBytes()
- val command = SignCommand(
- arrayOf(hashToSign),
- WalletIndex.PublicKey(publicKey))
+ val command = SignHashCommand(hashToSign, publicKey)
return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) {
is CompletionResult.Success -> {
- val hash = result.data.signatures.first()
-
+ val hash = result.data.signature
return EthereumUtils.prepareSignedMessageData(
hash, hashToSign, publicKey
)
}
is CompletionResult.Failure -> {
+ (result.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
+ )
+ }
Timber.e(result.error.customMessage)
null
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt
similarity index 55%
rename from app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt
rename to app/src/main/java/com/tangem/tap/features/BaseFragment.kt
index fb46ed08da..8b4d0d3f70 100644
--- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt
@@ -1,36 +1,33 @@
-package com.tangem.tap.features.send
+package com.tangem.tap.features
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
+import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_wallet.*
-import org.rekotlin.StoreSubscriber
/**
[REDACTED_AUTHOR]
*/
-abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
-
- abstract fun subscribeToStore()
+abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackPressedHandler {
protected lateinit var mainView: View
- protected val storeSubscribersList = mutableListOf>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(NavigationAction.PopBackTo())
- }
- })
+ configureTransitions()
+ }
+
+ protected open fun configureTransitions() {
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.slide_right)
exitTransition = inflater.inflateTransition(R.transition.fade)
@@ -41,27 +38,29 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
return mainView
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) }
+ override fun handleOnBackPressed() {
+ store.dispatch(NavigationAction.PopBackTo())
}
- override fun onStart() {
- super.onStart()
- subscribeToStore()
- }
-
-
- override fun onStop() {
- storeSubscribersList.forEach { store.unsubscribe(it) }
- super.onStop()
- }
-
- fun showRetrySnackbar(message: String, action: () -> Unit) {
+ fun showRetrySnackbar(message: String, action: VoidCallback) {
val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE)
snackbar.setAction(getString(R.string.common_retry)) {
snackbar.dismiss()
action()
}.show()
}
+}
+
+interface FragmentOnBackPressedHandler {
+ fun handleOnBackPressed()
+}
+
+@SuppressLint("FragmentBackPressedCallback")
+fun Fragment.addBackPressHandler(handler: FragmentOnBackPressedHandler) {
+ activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ handler.handleOnBackPressed()
+ }
+ })
+ view?.findViewById(R.id.toolbar)?.setNavigationOnClickListener { activity?.onBackPressed() }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt
new file mode 100644
index 0000000000..b4f486a1a2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt
@@ -0,0 +1,24 @@
+package com.tangem.tap.features
+
+import com.tangem.tap.store
+import org.rekotlin.StoreSubscriber
+
+/**
+[REDACTED_AUTHOR]
+ */
+abstract class BaseStoreFragment(layoutId: Int) : BaseFragment(layoutId) {
+
+ abstract fun subscribeToStore()
+ protected val storeSubscribersList = mutableListOf>()
+
+ override fun onStart() {
+ super.onStart()
+ subscribeToStore()
+ }
+
+ override fun onStop() {
+ storeSubscribersList.forEach { store.unsubscribe(it) }
+ storeSubscribersList.clear()
+ super.onStop()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
index 756d836287..9c7a39b172 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
@@ -1,15 +1,13 @@
package com.tangem.tap.features.details.redux
-import android.content.Context
-import com.tangem.Message
import com.tangem.blockchain.common.Wallet
-import com.tangem.commands.common.card.Card
+import com.tangem.common.card.Card
+import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.FiatCurrencyName
-import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.termsOfUse.CardTou
import com.tangem.tap.domain.twins.TwinCardNumber
-import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import com.tangem.wallet.R
import org.rekotlin.Action
@@ -17,17 +15,15 @@ import org.rekotlin.Action
sealed class DetailsAction : Action {
data class PrepareScreen(
- val card: Card,
- val scanNoteResponse: ScanNoteResponse,
- val wallets: List,
- val isCreatingTwinWalletAllowed: Boolean?,
- val cardTou: CardTou,
- val fiatCurrencyName: FiatCurrencyName,
- val fiatCurrencies: List? = null,
+ val scanResponse: ScanResponse,
+ val wallets: List,
+ val cardTou: CardTou,
+ val fiatCurrencyName: FiatCurrencyName,
+ val fiatCurrencies: List? = null,
) : DetailsAction()
object ShowDisclaimer : DetailsAction()
-
+ data class ReCreateTwinsWallet(val number: TwinCardNumber) : DetailsAction()
sealed class EraseWallet : DetailsAction() {
object Check : EraseWallet()
@@ -47,44 +43,6 @@ sealed class DetailsAction : Action {
object Success : EraseWallet()
}
- sealed class CreateTwinWalletAction : DetailsAction() {
- data class ShowWarning(
- val twinCardNumber: TwinCardNumber?,
- val createTwinWallet: CreateTwinWallet = CreateTwinWallet.RecreateWallet
- ) : CreateTwinWalletAction()
- object NotEmpty : CreateTwinWalletAction(), NotificationAction {
- override val messageResource = R.string.details_notification_erase_wallet_not_possible
- }
- object ShowAlert : CreateTwinWalletAction()
- object HideAlert : CreateTwinWalletAction()
- object Proceed: CreateTwinWalletAction()
-
- object Cancel : CreateTwinWalletAction() {
- object Confirm : CreateTwinWalletAction()
- }
-
- data class LaunchFirstStep(
- val message: Message, val context: Context
- ) : CreateTwinWalletAction() {
- object Success : CreateTwinWalletAction()
- object Failure : CreateTwinWalletAction()
- }
-
- data class LaunchSecondStep(
- val initialMessage: Message,
- val preparingMessage: Message,
- val creatingWalletMessage: Message,
- ) : CreateTwinWalletAction() {
- object Success : CreateTwinWalletAction()
- object Failure : CreateTwinWalletAction()
- }
-
- data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() {
- data class Success(val scanNoteResponse: ScanNoteResponse) : CreateTwinWalletAction()
- object Failure : CreateTwinWalletAction()
- }
- }
-
sealed class AppCurrencyAction : DetailsAction() {
data class SetCurrencies(val currencies: List) : AppCurrencyAction()
object ChooseAppCurrency : AppCurrencyAction()
@@ -93,6 +51,8 @@ sealed class DetailsAction : Action {
}
sealed class ManageSecurity : DetailsAction() {
+ data class CheckCurrentSecurityOption(val card: Card) : ManageSecurity()
+ data class SetCurrentOption(val userCodes: CheckUserCodesResponse) : ManageSecurity()
object OpenSecurity : ManageSecurity()
data class SelectOption(val option: SecurityOption) : ManageSecurity()
object SaveChanges : ManageSecurity() {
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index b66aa958e9..05591c0e48 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -1,13 +1,21 @@
package com.tangem.tap.features.details.redux
-import com.tangem.commands.common.network.Result
import com.tangem.common.CompletionResult
+import com.tangem.common.card.FirmwareVersion
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.services.Result
+import com.tangem.operations.pins.CheckUserCodesResponse
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.dispatchNotification
+import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.features.details.redux.twins.CreateTwinWalletMiddleware
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
+import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
+import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import com.tangem.tap.preferencesStorage
@@ -23,7 +31,6 @@ class DetailsMiddleware {
private val eraseWalletMiddleware = EraseWalletMiddleware()
private val appCurrencyMiddleware = AppCurrencyMiddleware()
private val manageSecurityMiddleware = ManageSecurityMiddleware()
- private val twinWalletMiddleware = CreateTwinWalletMiddleware()
val detailsMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
@@ -32,11 +39,25 @@ class DetailsMiddleware {
is DetailsAction.EraseWallet -> eraseWalletMiddleware.handle(action)
is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
- is DetailsAction.CreateTwinWalletAction -> twinWalletMiddleware.handle(action)
is DetailsAction.ShowDisclaimer -> {
store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
}
+ is DetailsAction.ReCreateTwinsWallet -> {
+ val wallet = store.state.walletState.walletManagers.map { it.wallet }.firstOrNull()
+ if (wallet == null) {
+ store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
+ } else {
+ if (wallet.hasSendableAmountsOrPendingTransactions()) {
+ val walletIsNotEmpty = store.state.globalState.resources.strings.walletIsNotEmpty
+ store.dispatchNotification(walletIsNotEmpty)
+ } else {
+ store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
+ }
+ }
+ }
}
next(action)
}
@@ -81,15 +102,23 @@ class DetailsMiddleware {
store.dispatch(NavigationAction.PopBackTo())
}
is DetailsAction.EraseWallet.Confirm -> {
+ val card = store.state.detailsState.scanResponse?.card ?: return
scope.launch {
- val result = tangemSdkManager.eraseWallet(
- store.state.detailsState.card?.cardId
- )
+ val result = tangemSdkManager.eraseWallet(card)
withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
+ is CompletionResult.Failure -> {
+ (result.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.PurgeWallet,
+ card = store.state.detailsState.scanResponse?.card
+ )
+ }
+ }
}
}
}
@@ -113,6 +142,27 @@ class DetailsMiddleware {
class ManageSecurityMiddleware {
fun handle(action: DetailsAction.ManageSecurity) {
when (action) {
+ is DetailsAction.ManageSecurity.CheckCurrentSecurityOption -> {
+ if (action.card.firmwareVersion >= FirmwareVersion.IsAccessCodeStatusAvailable) {
+ // for a card that meets this condition, we can get these statuses from it
+ val simulatedResponse = CheckUserCodesResponse(
+ action.card.isAccessCodeSet, action.card.isPasscodeSet ?: false
+ )
+ store.dispatch(DetailsAction.ManageSecurity.SetCurrentOption(simulatedResponse))
+ store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
+ } else {
+ scope.launch {
+ when (val response = tangemSdkManager.checkUserCodes(action.card.cardId)) {
+ is CompletionResult.Success -> {
+ store.dispatchOnMain(DetailsAction.ManageSecurity.SetCurrentOption(response.data))
+ store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity)
+ }
+ is CompletionResult.Failure -> {
+ }
+ }
+ }
+ }
+ }
is DetailsAction.ManageSecurity.OpenSecurity -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
}
@@ -128,7 +178,7 @@ class DetailsMiddleware {
}
}
is DetailsAction.ManageSecurity.SaveChanges -> {
- val cardId = store.state.detailsState.card?.cardId
+ val cardId = store.state.detailsState.scanResponse?.card?.cardId
val selectedOption = store.state.detailsState.securityScreenState?.selectedOption
scope.launch {
val result = when (selectedOption) {
@@ -148,8 +198,20 @@ class DetailsMiddleware {
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
- is CompletionResult.Failure, null ->
+ is CompletionResult.Failure -> {
+ (result.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error = error,
+ actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions,
+ parameters = mapOf(
+ FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to
+ (selectedOption?.name ?: "")
+ ),
+ card = store.state.detailsState.scanResponse?.card
+ )
+ }
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
+ }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index 33c71239fb..ca2b6fb4bb 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -1,18 +1,14 @@
package com.tangem.tap.features.details.redux
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.masks.Settings
+import com.tangem.common.card.Card
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.extensions.isWalletDataSupported
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.domain.extensions.toSendableAmounts
-import com.tangem.tap.domain.twins.TwinsHelper
-import com.tangem.tap.domain.twins.isTwinCard
-import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer
-import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState
-import com.tangem.tap.features.wallet.models.toPendingTransactions
+import com.tangem.tap.domain.twins.isTangemTwin
+import com.tangem.tap.features.wallet.models.hasPendingTransactions
import org.rekotlin.Action
import java.util.*
@@ -39,59 +35,29 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
is DetailsAction.ManageSecurity -> {
handleSecurityAction(action, detailsState)
}
- is DetailsAction.CreateTwinWalletAction -> {
- CreateTwinWalletReducer.handle(action, detailsState)
- }
else -> detailsState
}
}
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState {
- val securityOption = when {
- action.card.isPin1Default == false -> {
- SecurityOption.AccessCode
- }
- action.card.isPin2Default == false -> {
- SecurityOption.PassCode
- }
- else -> {
- SecurityOption.LongTap
- }
- }
- val twinsState = if (action.card.isTwinCard()) {
- CreateTwinWalletState(
- scanResponse = action.scanNoteResponse,
- twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId),
- createTwinWallet = null,
- showAlert = false,
- allowRecreatingWallet = action.isCreatingTwinWalletAllowed
- )
- } else {
- null
- }
-
return DetailsState(
- card = action.card, wallets = action.wallets,
- cardInfo = action.card.toCardInfo(),
- appCurrencyState = AppCurrencyState(
- action.fiatCurrencyName
- ),
- securityScreenState = SecurityScreenState(currentOption = securityOption),
- createTwinWalletState = twinsState,
- cardTermsOfUseUrl = action.cardTou.getUrl(action.card)
+ scanResponse = action.scanResponse,
+ wallets = action.wallets,
+ cardInfo = action.scanResponse.card.toCardInfo(),
+ appCurrencyState = AppCurrencyState(action.fiatCurrencyName),
+ cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card)
)
}
private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState {
return when (action) {
DetailsAction.EraseWallet.Check -> {
- val notAllowedByCard =
- state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
- || state.card?.settingsMask?.contains(Settings.IsReusable) == false
- || state.card?.isWalletDataSupported == true
+ val card = state.scanResponse?.card
+ val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false
+ val notAllowedByCard = notAllowedByAnyWallet ||
+ (card?.isWalletDataSupported == true && !state.scanResponse.isTangemNote())
val notEmpty = state.wallets.any {
- !it.recentTransactions.toPendingTransactions(it.address).isNullOrEmpty()
- || it.amounts.toSendableAmounts().isNotEmpty()
+ it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty()
}
val eraseWalletState = when {
notAllowedByCard -> EraseWalletState.NotAllowedByCard
@@ -115,7 +81,7 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
}
private fun handleAppCurrencyAction(
- action: DetailsAction.AppCurrencyAction, state: DetailsState
+ action: DetailsAction.AppCurrencyAction, state: DetailsState
): DetailsState {
return when (action) {
is DetailsAction.AppCurrencyAction.SetCurrencies -> {
@@ -129,9 +95,9 @@ private fun handleAppCurrencyAction(
}
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
state.copy(
- appCurrencyState = state.appCurrencyState.copy(
- fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false
- )
+ appCurrencyState = state.appCurrencyState.copy(
+ fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false
+ )
)
}
else -> state
@@ -139,31 +105,44 @@ private fun handleAppCurrencyAction(
}
private fun handleSecurityAction(
- action: DetailsAction.ManageSecurity, state: DetailsState
+ action: DetailsAction.ManageSecurity, state: DetailsState
): DetailsState {
return when (action) {
- is DetailsAction.ManageSecurity.OpenSecurity -> {
- if (state.card?.isStart2Coin == true) {
- return state.copy(securityScreenState = state.securityScreenState?.copy(
- allowedOptions = EnumSet.of(SecurityOption.LongTap),
- selectedOption = state.securityScreenState.currentOption
- ))
+ is DetailsAction.ManageSecurity.SetCurrentOption -> {
+ val securityOption = when {
+ action.userCodes.isAccessCodeSet -> {
+ SecurityOption.AccessCode
+ }
+ action.userCodes.isPasscodeSet -> {
+ SecurityOption.PassCode
+ }
+ else -> {
+ SecurityOption.LongTap
+ }
}
- if (state.card?.isPin2Default == null) {
+ state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption))
+ }
+ is DetailsAction.ManageSecurity.OpenSecurity -> {
+ if (state.scanResponse?.card?.isStart2Coin == true ||
+ state.scanResponse?.isTangemNote() == true
+ ) {
return state.copy(securityScreenState = state.securityScreenState?.copy(
- allowedOptions = EnumSet.noneOf(SecurityOption::class.java)
+ allowedOptions = EnumSet.of(SecurityOption.LongTap),
+ selectedOption = state.securityScreenState.currentOption
))
}
- val allowedSecurityOptions = prepareAllowedSecurityOptions(state.card)
+ val allowedSecurityOptions = prepareAllowedSecurityOptions(
+ state.scanResponse?.card, state.securityScreenState?.currentOption
+ )
state.copy(securityScreenState = state.securityScreenState?.copy(
- allowedOptions = allowedSecurityOptions,
- selectedOption = state.securityScreenState.currentOption
+ allowedOptions = allowedSecurityOptions,
+ selectedOption = state.securityScreenState.currentOption
))
}
is DetailsAction.ManageSecurity.SelectOption -> {
state.copy(securityScreenState = state.securityScreenState?.copy(
- selectedOption = action.option
+ selectedOption = action.option
))
}
is DetailsAction.ManageSecurity.ConfirmSelection -> {
@@ -176,48 +155,47 @@ private fun handleSecurityAction(
}
is DetailsAction.ManageSecurity.SaveChanges.Success -> {
// Setting options to show only LongTap from now on for non-twins
- val card = if (state.card?.isTwinCard() == true) {
- state.card
- } else {
- state.card?.copy(isPin1Default = true, isPin2Default = true)
- }
state.copy(
- card = card,
- securityScreenState = state.securityScreenState?.copy(
- currentOption = state.securityScreenState.selectedOption,
- allowedOptions = card?.let { prepareAllowedSecurityOptions(it) }
- ?: EnumSet.of(SecurityOption.LongTap)
- ))
+ securityScreenState = state.securityScreenState?.copy(
+ currentOption = state.securityScreenState.selectedOption,
+ allowedOptions = state.scanResponse?.card?.let {
+ prepareAllowedSecurityOptions(
+ it, state.securityScreenState.selectedOption
+ )
+ } ?: EnumSet.of(SecurityOption.LongTap)
+ ))
}
else -> state
}
}
-private fun prepareAllowedSecurityOptions(card: Card): EnumSet {
- val prohibitDefaultPin = card.settingsMask?.contains(Settings.ProhibitDefaultPIN1) == true
- val isDefaultPin1 = card.isPin1Default != false
- val isDefaultPin2 = card.isPin2Default != false
+private fun prepareAllowedSecurityOptions(
+ card: Card?, currentSecurityOption: SecurityOption?
+): EnumSet {
+ val prohibitDefaultPin = card?.settings?.isResettingUserCodesAllowed != true
val allowedSecurityOptions = EnumSet.noneOf(SecurityOption::class.java)
- if (card.isTwinCard()) {
+
+ if (card?.isTangemTwin() == true) {
allowedSecurityOptions.add(SecurityOption.PassCode)
}
- if ((isDefaultPin1 && isDefaultPin2) || !prohibitDefaultPin) {
+ if ((currentSecurityOption == SecurityOption.LongTap) || !prohibitDefaultPin) {
allowedSecurityOptions.add(SecurityOption.LongTap)
}
- if (!isDefaultPin1) {
+ if (currentSecurityOption == SecurityOption.AccessCode) {
allowedSecurityOptions.add(SecurityOption.AccessCode)
}
- if (!isDefaultPin2) {
+ if (currentSecurityOption == SecurityOption.PassCode) {
allowedSecurityOptions.add(SecurityOption.PassCode)
}
return allowedSecurityOptions
}
-private fun Card.toCardInfo(): CardInfo? {
+private fun Card.toCardInfo(): CardInfo {
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
- val issuer = this.cardData?.issuerName ?: return null
+ val issuer = this.issuer.name
val signedHashes = this.signedHashesCount()
- return CardInfo(cardId, issuer, signedHashes)}
\ No newline at end of file
+ return CardInfo(cardId, issuer, signedHashes)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
index 5ef1033e2f..cad9493b63 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
@@ -2,45 +2,56 @@ package com.tangem.tap.features.details.redux
import android.net.Uri
import com.tangem.blockchain.common.Wallet
-import com.tangem.commands.common.card.Card
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
-import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.network.coinmarketcap.FiatCurrency
+import com.tangem.tap.store
import org.rekotlin.StateType
import java.util.*
+import kotlin.properties.ReadOnlyProperty
data class DetailsState(
- val card: Card? = null,
- val wallets: List = emptyList(),
- val cardInfo: CardInfo? = null,
- val appCurrencyState: AppCurrencyState = AppCurrencyState(),
- val eraseWalletState: EraseWalletState? = null,
- val confirmScreenState: ConfirmScreenState? = null,
- val securityScreenState: SecurityScreenState? = null,
- val createTwinWalletState: CreateTwinWalletState? = null,
- val cardTermsOfUseUrl: Uri? = null,
-) : StateType
+ val scanResponse: ScanResponse? = null,
+ val wallets: List = emptyList(),
+ val cardInfo: CardInfo? = null,
+ val appCurrencyState: AppCurrencyState = AppCurrencyState(),
+ val eraseWalletState: EraseWalletState? = null,
+ val confirmScreenState: ConfirmScreenState? = null,
+ val securityScreenState: SecurityScreenState? = null,
+ val cardTermsOfUseUrl: Uri? = null,
+) : StateType {
+
+ // if you do not delegate - the application crashes on startup,
+ // because twinCardsState has not been created yet
+ val twinCardsState: TwinCardsState by ReadOnlyProperty { thisRef, property ->
+ store.state.twinCardsState
+ }
+
+ val isTangemTwins: Boolean
+ get() = store.state.globalState.scanResponse?.isTangemTwins() == true
+}
data class CardInfo(
- val cardId: String,
- val issuer: String,
- val signedHashes: Int,
+ val cardId: String,
+ val issuer: String,
+ val signedHashes: Int,
)
enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty }
enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode }
data class SecurityScreenState(
- val currentOption: SecurityOption = SecurityOption.LongTap,
- val selectedOption: SecurityOption = currentOption,
- val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java),
- val buttonProceed: Button = Button(true),
+ val currentOption: SecurityOption = SecurityOption.LongTap,
+ val selectedOption: SecurityOption = currentOption,
+ val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java),
+ val buttonProceed: Button = Button(true),
)
enum class SecurityOption { LongTap, PassCode, AccessCode }
data class AppCurrencyState(
- val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
- val showAppCurrencyDialog: Boolean = false,
- val fiatCurrencies: List? = null,
+ val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
+ val showAppCurrencyDialog: Boolean = false,
+ val fiatCurrencies: List? = null,
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt
deleted file mode 100644
index fa81ea9a18..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-package com.tangem.tap.features.details.redux.twins
-
-import com.tangem.blockchain.extensions.Result
-import com.tangem.blockchain.extensions.SimpleResult
-import com.tangem.tap.common.redux.navigation.AppScreen
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.domain.extensions.toSendableAmounts
-import com.tangem.tap.domain.twins.TwinCardsManager
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.scope
-import com.tangem.tap.store
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class CreateTwinWalletMiddleware {
- var twinsManager: TwinCardsManager? = null
- fun handle(action: DetailsAction.CreateTwinWalletAction) {
- when (action) {
- is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
- val wallet = store.state.detailsState.wallets.firstOrNull()
- if (wallet == null) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
- return
- }
- val notEmpty = wallet.recentTransactions.isNotEmpty() || wallet.amounts.toSendableAmounts().isNotEmpty()
- if (notEmpty) {
- store.dispatch(DetailsAction.CreateTwinWalletAction.NotEmpty)
- } else {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
- }
- }
- is DetailsAction.CreateTwinWalletAction.Proceed ->
- store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWallet))
- is DetailsAction.CreateTwinWalletAction.Cancel -> {
-
- val step = store.state.detailsState.createTwinWalletState?.step
- if (step != null && step != CreateTwinWalletStep.FirstStep
- ) {
- store.dispatch(DetailsAction.CreateTwinWalletAction.ShowAlert)
- } else {
- twinsManager = null
- store.dispatch(NavigationAction.PopBackTo())
- }
- }
- is DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> {
- twinsManager = null
- store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
- }
- is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> {
- store.state.globalState.scanNoteResponse?.let {
- twinsManager = TwinCardsManager(it, action.context)
- }
- scope.launch {
- val result = twinsManager?.createFirstWallet(action.message)
- withContext(Dispatchers.Main) {
- when (result) {
- SimpleResult.Success ->
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success)
- is SimpleResult.Failure ->
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure)
- }
- }
- }
- }
- DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success -> {
-
- }
- DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure -> {
-
- }
- is DetailsAction.CreateTwinWalletAction.LaunchSecondStep ->
- scope.launch {
- val result = twinsManager?.createSecondWallet(action.initialMessage,
- action.preparingMessage, action.creatingWalletMessage)
- withContext(Dispatchers.Main) {
- when (result) {
- SimpleResult.Success ->
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success)
- is SimpleResult.Failure ->
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure)
- }
- }
- }
- DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success -> {
-
- }
- DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure -> {
-
- }
- is DetailsAction.CreateTwinWalletAction.LaunchThirdStep -> {
- scope.launch {
- val result = twinsManager?.complete(action.message)
- withContext(Dispatchers.Main) {
- when (result) {
- is Result.Success ->
- store.dispatch(
- DetailsAction.CreateTwinWalletAction
- .LaunchThirdStep.Success(result.data)
- )
- is Result.Failure ->
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure)
- }
- }
- }
- }
- is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> {
- scope.launch {
- store.state.globalState.tapWalletManager.onCardScanned(action.scanNoteResponse)
- }
- store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
- }
- DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> {
-
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt
deleted file mode 100644
index 7de266d076..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletReducer.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.tangem.tap.features.details.redux.twins
-
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.features.details.redux.DetailsState
-
-class CreateTwinWalletReducer {
- companion object {
- fun handle(
- action: DetailsAction.CreateTwinWalletAction, state: DetailsState
- ): DetailsState {
- return when (action) {
- is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
- state.copy(createTwinWalletState = CreateTwinWalletState(
- scanResponse = null,
- twinCardNumber = action.twinCardNumber
- ?: state.createTwinWalletState?.twinCardNumber,
- createTwinWallet = action.createTwinWallet,
- showAlert = false,
- allowRecreatingWallet = state.createTwinWalletState?.allowRecreatingWallet
- ))
- }
- DetailsAction.CreateTwinWalletAction.NotEmpty -> state
- DetailsAction.CreateTwinWalletAction.ShowAlert -> {
- state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
- showAlert = true
- ))
- }
- DetailsAction.CreateTwinWalletAction.HideAlert -> {
- state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
- showAlert = false
- ))
- }
- is DetailsAction.CreateTwinWalletAction.Proceed -> {
- state
- }
- DetailsAction.CreateTwinWalletAction.Cancel -> state
- DetailsAction.CreateTwinWalletAction.Cancel.Confirm -> state
- is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> state
-
- DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Success -> {
- state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
- step = CreateTwinWalletStep.SecondStep
- ))
- }
- DetailsAction.CreateTwinWalletAction.LaunchFirstStep.Failure -> state
-
- is DetailsAction.CreateTwinWalletAction.LaunchSecondStep -> state
- DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Success ->
- state.copy(createTwinWalletState = state.createTwinWalletState?.copy(
- step = CreateTwinWalletStep.ThirdStep
- ))
- DetailsAction.CreateTwinWalletAction.LaunchSecondStep.Failure -> {
- state
- }
- is DetailsAction.CreateTwinWalletAction.LaunchThirdStep -> {
- state
- }
- is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> {
- state
- }
- DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Failure -> {
- state
- }
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt
deleted file mode 100644
index 488ac431bc..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletState.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.tangem.tap.features.details.redux.twins
-
-import com.tangem.tap.domain.tasks.ScanNoteResponse
-import com.tangem.tap.domain.twins.TwinCardNumber
-
-data class CreateTwinWalletState(
- val scanResponse: ScanNoteResponse?,
- val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep,
- val twinCardNumber: TwinCardNumber?,
- val createTwinWallet: CreateTwinWallet?,
- val showAlert: Boolean,
- val allowRecreatingWallet: Boolean? = null
-)
-
-enum class CreateTwinWalletStep { FirstStep, SecondStep, ThirdStep }
-
-enum class CreateTwinWallet { CreateWallet, RecreateWallet }
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
index 728a8d0446..16a3f638a4 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
@@ -31,6 +31,8 @@ sealed class WalletConnectAction : Action {
object RefuseOpeningSession : WalletConnectAction()
+ data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction()
+
data class AcceptOpeningSession(val session: WalletConnectSession) : WalletConnectAction()
data class ApproveSession(
@@ -41,7 +43,6 @@ sealed class WalletConnectAction : Action {
data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
-
data class SetSessionsRestored(val sessions: List) :
WalletConnectAction()
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index 89bec71f1f..dff6c35c72 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -1,25 +1,22 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.*
-import com.tangem.commands.common.card.Card
-import com.tangem.common.CompletionResult
+import com.tangem.common.card.Card
+import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.tap.*
-import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.wallet.R
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import org.rekotlin.Middleware
class WalletConnectMiddleware {
@@ -60,6 +57,10 @@ class WalletConnectMiddleware {
handleScanCard(action.wcUri)
}
+ is WalletConnectAction.OpeningSessionTimeout -> {
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
+ }
+
is WalletConnectAction.FailureEstablishingSession -> {
if (action.session != null) {
walletConnectManager.disconnect(action.session)
@@ -127,49 +128,35 @@ class WalletConnectMiddleware {
}
private fun handleScanCard(wcUri: String) {
- scope.launch {
- val result = tangemSdkManager.scanNote(
- FirebaseAnalyticsHandler,
- R.string.wallet_connect_scan_card_message
- )
- withContext(Dispatchers.Main) {
- when (result) {
- is CompletionResult.Success -> {
- val card = result.data.card
-
- if (!card.isMultiwalletAllowed) {
- store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
- return@withContext
- }
-
- val walletManager = getWalletManager(card)
- if (walletManager == null) {
- store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
- return@withContext
- }
-
- val key = walletManager.wallet.publicKey
- store.dispatchOnMain(WalletConnectAction.OpenSession(
- wcUri = wcUri,
- wallet = WalletForSession(
- card.cardId, key.toHexString(),
- isTestNet = false
- ),
- ))
- }
- is CompletionResult.Failure ->
- store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(
- null
- ))
- }
+ store.dispatch(GlobalAction.ScanCard({ scanResponse ->
+ val card = scanResponse.card
+ if (!card.isMultiwalletAllowed) {
+ store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
+ return@ScanCard
}
- }
+
+ val walletManager = getWalletManager(card).guard {
+ store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
+ return@ScanCard
+ }
+
+ val key = walletManager.wallet.publicKey
+ store.dispatchOnMain(WalletConnectAction.OpenSession(
+ wcUri = wcUri,
+ wallet = WalletForSession(
+ card.cardId, key.toHexString(),
+ isTestNet = card.isTestCard
+ ),
+ ))
+ }, {
+ store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
+ }, R.string.wallet_connect_scan_card_message))
}
private fun getWalletManager(card: Card): WalletManager? {
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
- return if (store.state.globalState.scanNoteResponse?.card?.cardId == card.cardId) {
+ return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
store.state.walletState.getWalletManager(Blockchain.Ethereum)
?: factory.makeWalletManagerForApp(card, Blockchain.Ethereum)
?.also { walletManager ->
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
index df3eec9834..c8b6f3b1b7 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
@@ -28,6 +28,7 @@ class WalletConnectReducer {
is WalletConnectAction.UnsupportedCard -> state.copy(loading = false)
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
+ is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
else -> state
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
index f5b7ce0c5f..38aeaabc84 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
@@ -1,10 +1,8 @@
package com.tangem.tap.features.details.redux.walletconnect
-import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager
-import com.tangem.common.extensions.hexToBytes
-import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.trustwallet.walletconnect.models.WCPeerMeta
@@ -30,11 +28,6 @@ data class WalletForSession(
) {
val chainId
get() = if (isTestNet) 4 else 1
-
- fun getAddress(): String {
- val blockchain = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum
- return blockchain.makeAddresses(walletPublicKey.hexToBytes()).first().value
- }
}
@@ -42,6 +35,7 @@ sealed class WalletConnectDialog : StateDialog {
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
object UnsupportedCard : WalletConnectDialog()
object OpeningSessionRejected : WalletConnectDialog()
+ object SessionTimeout : WalletConnectDialog()
data class ApproveWcSession(val session: WalletConnectSession) : WalletConnectDialog()
data class RequestTransaction(val dialogData: TransactionRequestDialogData) :
WalletConnectDialog()
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
index 3b0e9c83a1..5a1864ec3f 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
@@ -6,17 +6,15 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
-import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.twins.getTwinCardIdForUser
-import com.tangem.tap.domain.twins.isTwinCard
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -68,8 +66,8 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber {
-
- private var dialog: Dialog? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel)
- }
- })
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onStart() {
- super.onStart()
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.detailsState == newState.detailsState
- }.select { it.detailsState }
- }
- }
-
- override fun onStop() {
- super.onStop()
- store.unsubscribe(this)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- toolbar.setNavigationOnClickListener {
- store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel)
- }
-
- Picasso.get()
- .load(Artwork.TWIN_CARD_1)
- .placeholder(R.drawable.card_placeholder)
- ?.error(R.drawable.card_placeholder)
- ?.into(iv_card_1)
-
- Picasso.get()
- .load(Artwork.TWIN_CARD_2)
- .placeholder(R.drawable.card_placeholder)
- ?.error(R.drawable.card_placeholder)
- ?.into(iv_card_2)
- }
-
-
- override fun newState(state: DetailsState) {
- if (activity == null) return
-
- toolbar.title = when (state.createTwinWalletState?.createTwinWallet) {
- CreateTwinWallet.CreateWallet -> getText(R.string.wallet_button_create_wallet)
- CreateTwinWallet.RecreateWallet, null -> getText(R.string.details_twins_recreate_toolbar)
-
- }
-
- val selectedColor = getColor(requireContext(), R.color.colorSecondary)
- val defaultColor = getColor(requireContext(), R.color.blue_pale)
-
- val twinCardNumber = state.createTwinWalletState?.twinCardNumber ?: TwinCardNumber.First
-
- val cardNumber = when (state.createTwinWalletState?.step) {
- CreateTwinWalletStep.FirstStep -> {
- val twinCardNumberString = twinCardNumber.number.toString()
- tv_step_number.text =
- getString(R.string.details_twins_recreate_step_format, "1")
- v_step_1.setBackgroundColor(selectedColor)
- v_step_2.setBackgroundColor(defaultColor)
- v_step_3.setBackgroundColor(defaultColor)
- btn_tap.setOnClickListener {
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep(
- Message(getString(
- R.string.details_twins_recreate_title_format,
- twinCardNumberString
- )),
- requireContext()
- ))
- }
- btn_tap.text = getString(R.string.details_twins_recreate_button_format,
- twinCardNumber.number.toString())
- twinCardNumberString
- }
- CreateTwinWalletStep.SecondStep -> {
- val twinCardNumberString = twinCardNumber.pairNumber().number.toString()
-
- tv_step_number.text =
- getString(R.string.details_twins_recreate_step_format, "2")
- v_step_1.setBackgroundColor(selectedColor)
- v_step_2.setBackgroundColor(selectedColor)
- v_step_3.setBackgroundColor(defaultColor)
- btn_tap.setOnClickListener {
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchSecondStep(
- Message(getString(R.string.details_twins_recreate_title_format, twinCardNumberString)),
- Message(getString(R.string.details_twins_recreate_title_preparing)),
- Message(getString(R.string.details_twins_recreate_title_creating_wallet)),
- ))
- }
- btn_tap.text = getString(R.string.details_twins_recreate_button_format, "2")
- twinCardNumberString
- }
- CreateTwinWalletStep.ThirdStep -> {
- val twinCardNumberString = twinCardNumber.number.toString()
-
- tv_step_number.text =
- getString(R.string.details_twins_recreate_step_format, "3")
- v_step_1.setBackgroundColor(selectedColor)
- v_step_2.setBackgroundColor(selectedColor)
- v_step_3.setBackgroundColor(selectedColor)
- btn_tap.setOnClickListener {
- store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchThirdStep(
- Message(getString(
- R.string.details_twins_recreate_title_format, twinCardNumberString)
- )
- ))
- }
- btn_tap.text = getString(R.string.details_twins_recreate_button_format, twinCardNumberString)
- twinCardNumberString
- }
- else -> null
- }
- btn_tap.text = getString(R.string.details_twins_recreate_button_format, cardNumber)
- tv_twin_title.text = getString(R.string.details_twins_recreate_title_format, cardNumber)
-
- if (state.createTwinWalletState?.showAlert == true) {
- if (dialog == null) {
- dialog = MaterialAlertDialogBuilder(requireContext())
- .setMessage(R.string.details_twins_recreate_alert)
- .setPositiveButton(R.string.common_ok) { _, _ ->
- store.dispatch(DetailsAction.CreateTwinWalletAction.Cancel.Confirm)
- }
- .setNegativeButton(R.string.common_cancel) { _, _ ->
- dialog?.cancel()
- }
- .setOnCancelListener {
- store.dispatch(DetailsAction.CreateTwinWalletAction.HideAlert)
- }
- .create()
- dialog?.show()
- }
- } else {
- dialog?.cancel()
- dialog = null
- }
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt
deleted file mode 100644
index 678dddf2bb..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/ui/twins/TwinWalletWarningFragment.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.tangem.tap.features.details.ui.twins
-
-import android.os.Bundle
-import android.view.View
-import androidx.activity.OnBackPressedCallback
-import androidx.fragment.app.Fragment
-import androidx.transition.TransitionInflater
-import com.squareup.picasso.Picasso
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
-import com.tangem.tap.features.wallet.redux.Artwork
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_details_twin_cards_warning.*
-import kotlinx.android.synthetic.main.layout_twin_cards_orange.*
-
-class TwinWalletWarningFragment : Fragment(R.layout.fragment_details_twin_cards_warning) {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(NavigationAction.PopBackTo())
- }
- })
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- val createTwinWallet = store.state.detailsState.createTwinWalletState?.createTwinWallet
- if (createTwinWallet == CreateTwinWallet.CreateWallet) {
- tv_twin_cards_description.text = getText(R.string.details_twins_recreate_subtitle)
- } else if (createTwinWallet == CreateTwinWallet.RecreateWallet) {
- tv_twin_cards_description.text = getText(R.string.details_twins_recreate_warning)
- }
-
- btn_cancel.setOnClickListener { store.dispatch(NavigationAction.PopBackTo()) }
- btn_start.setOnClickListener {
- store.dispatch(
- DetailsAction.CreateTwinWalletAction.Proceed)
- }
- Picasso.get()
- .load(Artwork.TWIN_CARD_1)
- .placeholder(R.drawable.card_placeholder)
- ?.error(R.drawable.card_placeholder)
- ?.into(iv_twin_card_1)
-
- Picasso.get()
- .load(Artwork.TWIN_CARD_2)
- .placeholder(R.drawable.card_placeholder)
- ?.error(R.drawable.card_placeholder)
- ?.into(iv_twin_card_2)
- }
-
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt
new file mode 100644
index 0000000000..4bbb63e270
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/WcDialog.kt
@@ -0,0 +1,56 @@
+package com.tangem.tap.features.details.ui.walletconnect.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+
+class WcDialog {
+ companion object {
+ fun create(dialogData: DialogData, context: Context): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(dialogData.title)
+ setMessage(dialogData.message)
+ setPositiveButton(dialogData.positiveButton.title) { _, _ ->
+ dialogData.positiveButton.action()
+ }
+ setNegativeButton(dialogData.negativeButton.title) { _, _ ->
+ dialogData.negativeButton.action()
+ }
+ setOnDismissListener { dialogData.onDismissAction() }
+ }.create()
+ }
+ }
+}
+
+data class DialogData(
+ val title: String,
+ val message: String,
+ val positiveButton: ButtonData,
+ val negativeButton: ButtonData,
+ val onDismissAction: () -> Unit,
+)
+
+data class ButtonData(
+ val title: String,
+ val action: () -> Unit,
+)
+
+//class WcDialogMessageBuilder(val messageRes: Int, val data: DialogMessageData? = null) {
+// fun build(context: Context): String {
+// when (data) {
+// null -> context.getString(messageRes)
+// }
+// }
+//}
+
+interface DialogMessageData
+
+data class WcTransactionDialogMessageData(
+ val cardId: String,
+ val dAppName: String,
+ val dAppUrl: String,
+ val amount: String,
+ val gasAmount: String,
+ val totalAmount: String,
+ val balance: String,
+ val isEnoughFundsToSend: Boolean,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt
index 066b2b83db..12a1cfaccd 100644
--- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt
@@ -1,8 +1,9 @@
package com.tangem.tap.features.disclaimer.redux
+import com.tangem.tap.common.post
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
import org.rekotlin.Middleware
@@ -14,16 +15,10 @@ class DisclaimerMiddleware {
when (action) {
is DisclaimerAction.AcceptDisclaimer -> {
preferencesStorage.saveDisclaimerAccepted()
- if (store.state.walletState.twinCardsState != null) {
- val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
- if (showOnboarding) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
- } else {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
- }
- } else {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
- }
+ store.dispatch(NavigationAction.PopBackTo())
+
+ //delayed sending used for better animation
+ post(550) { store.dispatch(HomeAction.ReadCard) }
}
}
next(action)
diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt
index fb7ebe023d..82f131a3a2 100644
--- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt
@@ -25,8 +25,8 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
}
})
val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
+ enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom)
+ exitTransition = inflater.inflateTransition(android.R.transition.slide_top)
}
override fun onStart() {
@@ -58,6 +58,9 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
override fun newState(state: DisclaimerState) {
if (activity == null) return
- if (state.accepted) btn_accept.hide()
+ if (state.accepted) {
+ half_transparent_overlay.hide()
+ btn_accept.hide()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
index bab36b6f67..0e4fe0a875 100644
--- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
@@ -1,20 +1,14 @@
package com.tangem.tap.features.feedback
import android.app.Activity
-import android.content.ActivityNotFoundException
import android.content.Context
-import android.content.Intent
import android.content.pm.PackageManager
-import android.net.Uri
import android.os.Build
-import androidx.core.app.ShareCompat
-import androidx.core.content.ContextCompat
-import androidx.core.content.FileProvider
import com.tangem.Log
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.common.*
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.wallet.WalletStatus
+import com.tangem.common.card.Card
+import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.domain.TapWorkarounds
import timber.log.Timber
@@ -29,22 +23,26 @@ import java.util.*
[REDACTED_AUTHOR]
*/
class FeedbackManager(
- val infoHolder: AdditionalEmailInfo,
- private val logCollector: TangemLogCollector,
+ val infoHolder: AdditionalEmailInfo,
+ private val logCollector: TangemLogCollector,
) {
private lateinit var activity: Activity
- fun updateAcivity(activity: Activity) {
+ fun updateActivity(activity: Activity) {
this.activity = activity
}
- fun send(emailData: EmailData) {
+ fun send(emailData: EmailData, onFail: ((Exception) -> Unit)? = null) {
+ if (!this::activity.isInitialized) return
+
+ emailData.prepare(infoHolder)
val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
- sendTo(
+ activity.sendEmail(
email = getSupportEmail(),
subject = emailData.subject, message = emailData.joinTogether(infoHolder),
- fileLog = fileLog
+ file = fileLog,
+ onFail = onFail
)
}
@@ -56,42 +54,6 @@ class FeedbackManager(
}
}
- private fun sendTo(email: String, subject: String, message: String, fileLog: File? = null) {
- val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
- val originalIntentResults = activity.packageManager.queryIntentActivities(emailFilterIntent, 0)
- val emailFilterIntentResults = activity.packageManager.queryIntentActivities(emailFilterIntent, 0)
- val targetedIntents = originalIntentResults
- .filter { originalResult ->
- emailFilterIntentResults.any {
- originalResult.activityInfo.packageName == it.activityInfo.packageName
- }
- }
- .map {
- createEmailShareIntent(email, subject, message, fileLog).apply {
- setPackage(it.activityInfo.packageName)
- }
- }
- .toMutableList()
- try {
- val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
- chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
- chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- ContextCompat.startActivity(activity, chooserIntent, null)
- } catch (ex: ActivityNotFoundException) {
- Timber.e(ex)
- }
- }
-
- private fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
- val builder = ShareCompat.IntentBuilder.from(activity)
- .setType("message/rfc822")
- .setEmailTo(arrayOf(recipient))
- .setSubject(subject)
- .setText(text)
- file?.let { builder.setStream(FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it)) }
- return builder.intent
- }
-
private fun createLogFile(): File? {
return try {
val file = File(activity.filesDir, "logs.txt")
@@ -135,12 +97,12 @@ class TangemLogCollector : TangemSdkLogger {
class AdditionalEmailInfo {
class EmailWalletInfo(
- var blockchain: Blockchain = Blockchain.Unknown,
- var address: String = "",
- var explorerLink: String = "",
- var host: String = "",
- // var outputsCount: String = ""
- // var transactionHex: String = ""
+ var blockchain: Blockchain = Blockchain.Unknown,
+ var address: String = "",
+ var explorerLink: String = "",
+ var host: String = "",
+ // var outputsCount: String = ""
+ // var transactionHex: String = ""
)
var appVersion: String = ""
@@ -152,6 +114,7 @@ class AdditionalEmailInfo {
// wallets
internal val walletsInfo = mutableListOf()
+ internal val tokens = mutableMapOf>()
internal var onSendErrorWalletInfo: EmailWalletInfo? = null
var signedHashesCount: String = ""
@@ -176,15 +139,15 @@ class AdditionalEmailInfo {
fun setCardInfo(card: Card) {
cardId = card.cardId
- cardFirmwareVersion = card.firmwareVersion.version
- cardIssuer = card.cardData?.issuerName ?: ""
+ cardFirmwareVersion = card.firmwareVersion.stringValue
+ cardIssuer = card.issuer.name
signedHashesCount = card.wallets
- .filter { it.status == WalletStatus.Loaded }
- .joinToString(";") { "${it.curve?.curve} - ${it.signedHashes}" }
+ .joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" }
}
fun setWalletsInfo(walletManagers: List) {
walletsInfo.clear()
+ tokens.clear()
walletManagers.forEach { manager ->
walletsInfo.add(
EmailWalletInfo(
@@ -194,6 +157,9 @@ class AdditionalEmailInfo {
host = manager.currentHost
)
)
+ if (manager.cardTokens.isNotEmpty()) {
+ tokens[manager.wallet.blockchain] = manager.cardTokens
+ }
}
}
@@ -237,10 +203,21 @@ class AdditionalEmailInfo {
interface EmailData {
val subject: String
val mainMessage: String
+
+ fun prepare(infoHolder: AdditionalEmailInfo) {}
+
+ fun appendDelimiter(builder: StringBuilder) {
+ builder.append("----------\n")
+ }
+
+ fun appendBlankLine(builder: StringBuilder) {
+ builder.append("\n")
+ }
+
fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
fun joinTogether(infoHolder: AdditionalEmailInfo): String {
- return "$mainMessage\n\n\n\n\n" +
+ return "$mainMessage\n\n\n\n" +
"Following information is optional. You can erase it if you don’t want to share it.\n" +
createOptionalMessage(infoHolder)
}
@@ -255,6 +232,7 @@ class RateCanBeBetterEmail : EmailData {
return StringBuilder().apply {
appendKeyValue("Card ID", infoHolder.cardId)
appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
+ appendBlankLine(this)
appendKeyValue("Phone model", infoHolder.phoneModel)
appendKeyValue("OS version", infoHolder.osVersion)
appendKeyValue("App version", infoHolder.appVersion)
@@ -267,6 +245,7 @@ class ScanFailsEmail : EmailData {
override val mainMessage: String = "Please tell us what card do you have?"
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
return StringBuilder().apply {
+ appendBlankLine(this)
appendKeyValue("Phone model", infoHolder.phoneModel)
appendKeyValue("OS version", infoHolder.osVersion)
appendKeyValue("App version", infoHolder.appVersion)
@@ -277,31 +256,50 @@ class ScanFailsEmail : EmailData {
class SendTransactionFailedEmail(private val error: String) : EmailData {
override val subject: String = "Can’t send a transaction"
override val mainMessage: String = "Please tell us more about your issue. Every small detail can help."
+
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
return StringBuilder().apply {
- appendKeyValue("Error", error)
appendKeyValue("Card ID", infoHolder.cardId)
+ appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+ appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
+ appendDelimiter(this)
appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
appendKeyValue("Host", walletInfo.host)
appendKeyValue("Token", infoHolder.token)
+ appendKeyValue("Error", error)
+ appendDelimiter(this)
appendKeyValue("Source address", walletInfo.address)
appendKeyValue("Destination address", infoHolder.destinationAddress)
appendKeyValue("Amount", infoHolder.amount)
appendKeyValue("Fee", infoHolder.fee)
+ appendBlankLine(this)
appendKeyValue("Phone model", infoHolder.phoneModel)
appendKeyValue("OS version", infoHolder.osVersion)
appendKeyValue("App version", infoHolder.appVersion)
- appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
- appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
// appendKeyValue("Transaction HEX", infoHolder.transactionHex)
}.toString()
}
}
class FeedbackEmail : EmailData {
- override val subject: String = "Tangem Tap feedback"
- override val mainMessage: String = "Hi Tangem,"
+ override val subject: String
+ get() = if (isS2CCard) s2cSubject else tangemSubject
+ override val mainMessage: String
+ get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
+
+ private val tangemSubject = "Tangem feedback"
+ private val tangemMainMessage = "Hi support team,"
+
+ private val s2cSubject = "Feedback"
+ private val s2cMainMessage = "Hi support team,"
+
+ private var isS2CCard = false
+
+ override fun prepare(infoHolder: AdditionalEmailInfo) {
+ isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
+ }
+
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
val builder = StringBuilder()
builder.appendKeyValue("Card ID", infoHolder.cardId)
@@ -309,11 +307,22 @@ class FeedbackEmail : EmailData {
builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
infoHolder.walletsInfo.forEach {
+ appendDelimiter(builder)
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
builder.appendKeyValue("Host", it.host)
builder.appendKeyValue("Wallet address", it.address)
builder.appendKeyValue("Explorer link", it.explorerLink)
}
+ appendBlankLine(builder)
+
+ infoHolder.tokens.forEach { tokens ->
+ appendDelimiter(builder)
+ builder.appendKeyValue("Blockchain", tokens.key.fullName)
+ builder.appendKeyValue("Tokens", tokens.value.map { "${it.name} - ${it.symbol}" }.toString())
+ }
+ appendDelimiter(builder)
+ appendBlankLine(builder)
+
// appendKeyValue("Outputs count", infoHolder.outputsCount)
builder.appendKeyValue("Phone model", infoHolder.phoneModel)
builder.appendKeyValue("OS version", infoHolder.osVersion)
diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
index c93543613e..f28523564f 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
@@ -1,76 +1,85 @@
package com.tangem.tap.features.home
-import android.app.Dialog
import android.os.Bundle
import android.view.View
-import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
-import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.redux.navigation.FragmentShareTransition
+import com.tangem.tap.common.redux.navigation.ShareElement
+import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
+import com.tangem.tap.common.toggleWidget.ViewStateWidget
+import com.tangem.tap.common.transitions.FrontCardEnterTransition
+import com.tangem.tap.common.transitions.FrontCardExitTransition
import com.tangem.tap.features.home.redux.HomeAction
-import com.tangem.tap.features.home.redux.HomeDialog
import com.tangem.tap.features.home.redux.HomeState
-import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
+import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_home.*
-import org.rekotlin.StoreSubscriber
+import kotlinx.android.synthetic.main.fragment_onboarding_main.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
+import kotlinx.android.synthetic.main.layout_onboarding_home_top.*
+import kotlinx.android.synthetic.main.view_bg_home.*
-class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber {
+class HomeFragment : BaseOnboardingFragment() {
- private var dialog: Dialog? = null
+ private lateinit var btnScanCard: ViewStateWidget
- override fun onStart() {
- super.onStart()
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.homeState == newState.homeState
- }.select { it.homeState }
- }
- }
-
- override fun onStop() {
- super.onStop()
- store.unsubscribe(this)
- }
-
-
- override fun newState(state: HomeState) {
- if (activity == null) return
- if (state.firstLaunch) {
- tv_home_description.text = getText(R.string.home_welcome)
- btn_shop.text = getText(R.string.common_no)
- btn_yes.text = getText(R.string.home_button_yes)
- } else {
- tv_home_description.text = getText(R.string.home_welcome_back)
- btn_shop.text = getText(R.string.home_button_shop)
- btn_yes.text = getText(R.string.home_button_scan)
- }
- handleDialog(state.dialog)
- }
-
- private fun handleDialog(stateDialog: StateDialog?) {
- when (stateDialog) {
- is HomeDialog.ScanFailsDialog -> {
- if (dialog == null) dialog = ScanFailsDialog.create(requireContext()).apply { show() }
- }
- else -> {
- dialog?.dismiss()
- dialog = null
- }
- }
- }
-
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- btn_yes?.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
- btn_shop?.setOnClickListener { store.dispatch(HomeAction.GoToShop(requireContext())) }
- }
+ override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home_top
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = TransitionInflater.from(requireContext())
exitTransition = inflater.inflateTransition(R.transition.fade)
+ store.dispatch(HomeAction.Init)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ toolbar.hide()
+ val shareTransition = FragmentShareTransition(
+ listOf(
+ ShareElement(imv_front_card, ShareElement.imvFrontCard),
+ ShareElement(imv_back_card, ShareElement.imvBackCard),
+ ShareElement(bg_circle_large),
+ ShareElement(bg_circle_medium),
+ ShareElement(bg_circle_min),
+ ),
+ FrontCardEnterTransition(),
+ FrontCardExitTransition()
+ )
+
+ store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition))
+
+ tv_header.setText(R.string.home_welcome_header)
+ tv_body.setText(R.string.home_welcome_body)
+
+ btn_main_action.setText(R.string.home_button_scan)
+ btn_main_action.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
+ btn_alternative_action.setText(R.string.home_button_get_new_card)
+ btn_alternative_action.setOnClickListener { store.dispatch(HomeAction.GoToShop) }
+
+ btnScanCard = IndeterminateProgressButtonWidget(btn_main_action, progress)
+ }
+
+ override fun subscribeToStore() {
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.homeState == newState.homeState
+ }.select { it.homeState }
+ }
+ storeSubscribersList.add(this)
+ }
+
+ override fun newState(state: HomeState) {
+ if (activity == null) return
+
+ btnScanCard.changeState(state.btnScanState.progressState)
+ btnScanCard.mainView.isEnabled = state.btnScanState.enabled
+ }
+
+ override fun onDestroyView() {
+ store.dispatch(HomeAction.SetFragmentShareTransition(null))
+ super.onDestroyView()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
index 4a575343c7..14f762f6c4 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
@@ -1,18 +1,20 @@
package com.tangem.tap.features.home.redux
-import android.content.Context
+import com.tangem.tap.common.entities.IndeterminateProgressButton
+import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import org.rekotlin.Action
sealed class HomeAction : Action {
+ // from ui
object ReadCard : HomeAction()
- data class GoToShop(val context: Context) : HomeAction()
- object CheckIfFirstLaunch : HomeAction() {
- data class Result(val firstLaunch: Boolean) : HomeAction()
- }
+ object GoToShop : HomeAction()
- object ShowDialog : HomeAction() {
- object ScanFails : HomeAction()
- }
+ // internal
+ data class ShouldScanCardOnResume(val shouldScanCard: Boolean):HomeAction()
+ object Init : HomeAction()
+
+ data class SetFragmentShareTransition(val shareTransition: FragmentShareTransition?) : HomeAction()
+ data class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction()
+ data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()
- object HideDialog : HomeAction()
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
index b084b208cb..43346530de 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
@@ -1,80 +1,99 @@
package com.tangem.tap.features.home.redux
-import android.content.Intent
-import android.net.Uri
-import androidx.core.content.ContextCompat.startActivity
-import com.tangem.common.CompletionResult
-import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.entities.IndeterminateProgressButton
+import com.tangem.tap.common.extensions.dispatchOpenUrl
+import com.tangem.tap.common.extensions.onCardScanned
+import com.tangem.tap.common.extensions.withMainContext
+import com.tangem.tap.common.post
+import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
+import com.tangem.tap.features.onboarding.OnboardingHelper
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
+import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
-import com.tangem.tap.tangemSdkManager
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import org.rekotlin.Middleware
class HomeMiddleware {
- val homeMiddleware: Middleware = { dispatch, state ->
- { next ->
- { action ->
- when (action) {
- is HomeAction.CheckIfFirstLaunch -> {
- store.dispatch(
- HomeAction.CheckIfFirstLaunch.Result(preferencesStorage.getCountOfLaunches() == 1)
- )
- }
- is HomeAction.ReadCard -> {
- scope.launch {
- val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
- withContext(Dispatchers.Main) {
- store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
- when (result) {
- is CompletionResult.Success -> {
- tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
- store.dispatch(GlobalAction.RestoreAppCurrency)
- store.state.globalState.tapWalletManager.onCardScanned(result.data)
- showDisclaimerOrNavigateToWallet()
- }
- }
- }
- }
- }
- is HomeAction.GoToShop -> {
- val uri = Uri.parse(CARD_SHOP_URI)
- val intent = Intent(Intent.ACTION_VIEW, uri)
- startActivity(action.context, intent, null)
- }
- }
- next(action)
- }
- }
- }
-
- private fun showDisclaimerOrNavigateToWallet() {
- if (!preferencesStorage.wasDisclaimerAccepted()) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
- return
- }
- if (store.state.walletState.twinCardsState != null) {
- val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
- if (showOnboarding) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
- return
- }
- }
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
- }
-
companion object {
- private const val CARD_SHOP_URI =
+ val handler = homeMiddleware
+
+ const val CARD_SHOP_URI =
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
}
}
+private val homeMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ when (action) {
+ is HomeAction.Init -> {
+ store.dispatch(GlobalAction.RestoreAppCurrency)
+ store.dispatch(GlobalAction.GetMoonPayStatus)
+ store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
+ }
+ is HomeAction.ShouldScanCardOnResume -> {
+ if (action.shouldScanCard) {
+ store.dispatch(HomeAction.ShouldScanCardOnResume(false))
+ postUi(700) { store.dispatch(HomeAction.ReadCard) }
+ }
+ }
+ is HomeAction.ReadCard -> handleReadCard()
+ is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
+ }
+ next(action)
+ }
+ }
+}
+private fun handleReadCard() {
+ if (!preferencesStorage.wasDisclaimerAccepted()) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
+ } else {
+ changeButtonState(ButtonState.PROGRESS)
+ store.dispatch(GlobalAction.ScanCard({ scanResponse ->
+ store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
+ store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
+ if (OnboardingHelper.isOnboardingCase(scanResponse)) {
+ val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
+ store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
+ navigateTo(navigateTo, store.state.homeState.shareTransition)
+ } else {
+ scope.launch {
+ store.onCardScanned(scanResponse)
+ withMainContext {
+ if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
+ navigateTo(AppScreen.OnboardingTwins, store.state.homeState.shareTransition)
+ } else {
+ navigateTo(AppScreen.Wallet, null)
+ }
+ }
+ }
+ }
+ }, {
+ changeButtonState(ButtonState.ENABLED)
+ }))
+ }
+}
+
+private fun changeButtonState(state: ButtonState) {
+ val btnState = IndeterminateProgressButton(state)
+ store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
+}
+
+private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition?) {
+ post(DELAY_SDK_DIALOG_CLOSE) {
+ changeButtonState(ButtonState.ENABLED)
+ store.dispatch(NavigationAction.NavigateTo(screen, transition))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
index 3c031ed7b2..d4f11f31f9 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
@@ -13,18 +13,18 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
if (action !is HomeAction) return state.homeState
- var homeState = state.homeState
+ var state = state.homeState
when (action) {
- is HomeAction.CheckIfFirstLaunch.Result -> {
- homeState = homeState.copy(firstLaunch = action.firstLaunch)
+ is HomeAction.SetFragmentShareTransition -> {
+ state = state.copy(shareTransition = action.shareTransition)
}
- is HomeAction.ShowDialog.ScanFails -> {
- homeState = homeState.copy(dialog = HomeDialog.ScanFailsDialog)
+ is HomeAction.ShouldScanCardOnResume -> {
+ state = state.copy(shouldScanCardOnResume = action.shouldScanCard)
}
- is HomeAction.HideDialog -> {
- homeState = homeState.copy(dialog = null)
+ is HomeAction.ChangeScanCardButtonState -> {
+ state = state.copy(btnScanState = action.state)
}
}
- return homeState
+ return state
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
index c9fce9b492..8c9949cf4c 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
@@ -1,13 +1,12 @@
package com.tangem.tap.features.home.redux
-import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.entities.IndeterminateProgressButton
+import com.tangem.tap.common.redux.navigation.FragmentShareTransition
+import com.tangem.tap.features.send.redux.states.ButtonState
import org.rekotlin.StateType
data class HomeState(
- val firstLaunch: Boolean = true,
- val dialog: StateDialog? = null
-) : StateType
-
-sealed class HomeDialog: StateDialog {
- object ScanFailsDialog: HomeDialog()
-}
\ No newline at end of file
+ val shouldScanCardOnResume: Boolean = false,
+ val shareTransition: FragmentShareTransition? = null,
+ val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
+) : StateType
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
new file mode 100644
index 0000000000..a7bafedba2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
@@ -0,0 +1,68 @@
+package com.tangem.tap.features.onboarding
+
+import android.content.Context
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.tangem.tap.common.extensions.copyToClipboard
+import com.tangem.tap.common.extensions.dispatchDialogHide
+import com.tangem.tap.common.extensions.dispatchShare
+import com.tangem.tap.common.extensions.dispatchToastNotification
+import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.dialog_onboarding_address_info.*
+import kotlinx.android.synthetic.main.layout_pseudo_toolbar.*
+
+/**
+[REDACTED_AUTHOR]
+ */
+class AddressInfoBottomSheetDialog(
+ private val stateDialog: AppDialog.AddressInfoDialog,
+ context: Context
+) : BottomSheetDialog(context) {
+
+ init {
+ this.setContentView(R.layout.dialog_onboarding_address_info)
+ setOnCancelListener { store.dispatchDialogHide() }
+ }
+
+ override fun show() {
+ super.show()
+ val data = stateDialog.addressData
+
+ imv_close.setOnClickListener {
+ dismissWithAnimation = true
+ cancel()
+ }
+ imv_qr_code.setImageBitmap(data.qrCode)
+ tv_address.text = data.address
+ btn_fl_copy_address.setOnClickListener {
+ context.copyToClipboard(data.address)
+ store.dispatchToastNotification(R.string.copy_toast_msg)
+ }
+ btn_fl_share.setOnClickListener {
+ store.dispatchShare(data.shareUrl)
+ }
+ tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, stateDialog.currency)
+ }
+}
+
+fun getQRReceiveMessage(context: Context, currency: Currency): String {
+ return when (currency) {
+ is Currency.Blockchain -> {
+ context.getString(
+ R.string.address_qr_code_message_format,
+ currency.blockchain.fullName,
+ currency.currencySymbol
+ )
+ }
+ is Currency.Token -> {
+ context.getString(
+ R.string.address_qr_code_message_token_format,
+ currency.token.name,
+ currency.currencySymbol,
+ currency.blockchain.fullName
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
new file mode 100644
index 0000000000..6a097f6511
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
@@ -0,0 +1,41 @@
+package com.tangem.tap.features.onboarding
+
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.domain.ProductType
+import com.tangem.tap.domain.extensions.hasWallets
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.preferencesStorage
+
+/**
+[REDACTED_AUTHOR]
+ */
+class OnboardingHelper {
+ companion object {
+
+ fun isOnboardingCase(response: ScanResponse): Boolean {
+ val cardInfoStorage = preferencesStorage.usedCardsPrefStorage
+ return when {
+ response.productType == ProductType.Twins -> {
+ if (!response.twinsIsTwinned()) {
+ true
+ } else {
+ cardInfoStorage.activationIsStarted(response.card.cardId)
+ }
+ }
+ response.card.hasWallets() -> {
+ cardInfoStorage.activationIsStarted(response.card.cardId)
+ }
+ else -> true
+ }
+ }
+
+ fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
+ return when (scanResponse.productType) {
+ ProductType.Note -> AppScreen.OnboardingNote
+ ProductType.Wallet -> AppScreen.OnboardingWallet
+ ProductType.Twins -> AppScreen.OnboardingTwins
+ ProductType.Other -> AppScreen.OnboardingOther
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
new file mode 100644
index 0000000000..3aa671c5c3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
@@ -0,0 +1,137 @@
+package com.tangem.tap.features.onboarding
+
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.extensions.isZero
+import com.tangem.common.services.Result
+import com.tangem.operations.attestation.CardVerifyAndGetInfo
+import com.tangem.operations.attestation.OnlineCardVerifier
+import com.tangem.tap.common.extensions.isPositive
+import com.tangem.tap.common.extensions.safeUpdate
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.features.wallet.models.hasPendingTransactions
+import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.persistence.UsedCardsPrefStorage
+import timber.log.Timber
+import java.math.BigDecimal
+
+/**
+[REDACTED_AUTHOR]
+ */
+class OnboardingManager(
+ var scanResponse: ScanResponse,
+ val usedCardsPrefStorage: UsedCardsPrefStorage,
+) {
+
+ var cardInfo: Result? = null
+ private set
+
+ suspend fun loadArtworkUrl(): String {
+ val cardInfo = cardInfo
+ ?: OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
+ this.cardInfo = cardInfo
+ return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)
+ }
+
+// suspend fun loadArtwork(): Artwork {
+// cardInfo = OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
+// return loadArtwork(scanResponse.card.getOrLoadCardArtworkUrl(cardInfo))
+// }
+//
+// suspend fun loadArtwork(url: String): Artwork {
+// return withContext(Dispatchers.Main) {
+// suspendCoroutine { continuation ->
+// UrlBitmapLoader().loadBitmap(url) {
+// val result = when (it) {
+// is Result.Success -> Artwork(url, it.data)
+// is Result.Failure -> Artwork(url, null)
+// }
+// continuation.resume(result)
+// }
+// }
+// }
+// }
+
+ suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {
+ val balance = when (val result = walletManager.safeUpdate()) {
+ is Result.Success -> {
+ val wallet = walletManager.wallet
+ val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
+ if (valueOfAmount == null) {
+ val customError = TapError.CustomError("Amount is NULL")
+ OnboardingWalletBalance.criticalError(customError)
+ } else {
+ val balance = if (valueOfAmount.isZero()) {
+ OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions())
+ } else {
+ OnboardingWalletBalance.done(valueOfAmount, false)
+ }
+ balance
+ }
+ }
+ is Result.Failure -> {
+ val error = (result.error as? TapError) ?: TapError.UnknownError
+ when (error) {
+ is TapError.WalletManagerUpdate.NoAccountError -> OnboardingWalletBalance.error(error)
+ // NoInternetConnection, WalletManagerUpdate.InternalError
+ else -> {
+ Timber.e(error.localizedMessage)
+ OnboardingWalletBalance.criticalError(TapError.WalletManagerUpdate.BlockchainIsUnreachableTryLater)
+ }
+ }
+ }
+ }
+
+ return balance.copy(currency = Currency.Blockchain(walletManager.wallet.blockchain))
+ }
+
+ fun activationStarted(cardId: String) {
+ usedCardsPrefStorage.activationStarted(cardId)
+ }
+
+ fun activationFinished(cardId: String) {
+ usedCardsPrefStorage.activationFinished(cardId)
+ }
+}
+
+data class OnboardingWalletBalance(
+ val value: BigDecimal = BigDecimal.ZERO,
+ val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown),
+ val hasIncomingTransaction: Boolean = false,
+ val state: ProgressState,
+ val error: TapError? = null,
+ val criticalError: TapError? = null,
+) {
+
+ fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction
+
+ val amountToCreateAccount: String?
+ get() = if (error is TapError.WalletManagerUpdate.NoAccountError) error.customMessage else null
+
+ companion object {
+ fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
+ state = ProgressState.Error,
+ error = error
+ )
+
+ fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
+ state = ProgressState.Error,
+ criticalError = error
+ )
+
+ fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance(
+ value,
+ state = ProgressState.Loading
+ )
+
+ fun done(value: BigDecimal, hasTransactions: Boolean): OnboardingWalletBalance = OnboardingWalletBalance(
+ value,
+ hasIncomingTransaction = hasTransactions,
+ state = ProgressState.Done
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt
new file mode 100644
index 0000000000..b33b3118f5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt
@@ -0,0 +1,41 @@
+package com.tangem.tap.features.onboarding.products
+
+import android.os.Bundle
+import android.view.View
+import com.tangem.tap.common.extensions.inflate
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.transitions.HomeToOnboardingTransition
+import com.tangem.tap.features.BaseStoreFragment
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_onboarding_main.*
+import kotlinx.android.synthetic.main.view_confetti.*
+import org.rekotlin.StoreSubscriber
+
+/**
+[REDACTED_AUTHOR]
+ */
+abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber {
+
+ override fun configureTransitions() {
+ enterTransition = HomeToOnboardingTransition()
+ exitTransition = HomeToOnboardingTransition()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ onboarding_top_container.inflate(getOnboardingTopContainerId(), true)
+ }
+
+ abstract fun getOnboardingTopContainerId(): Int
+
+ protected fun showConfetti(show: Boolean) {
+ lav_confetti.show(show)
+
+ if (show) {
+ lav_confetti.playAnimation()
+ } else {
+ lav_confetti.cancelAnimation()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt
new file mode 100644
index 0000000000..d9d832d5a6
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt
@@ -0,0 +1,222 @@
+package com.tangem.tap.features.onboarding.products.note
+
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.Bundle
+import android.view.View
+import android.view.animation.OvershootInterpolator
+import android.widget.ImageView
+import androidx.annotation.LayoutRes
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.view.isVisible
+import androidx.transition.TransitionManager
+import com.squareup.picasso.Picasso
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tangem_sdk_new.extensions.fadeIn
+import com.tangem.tangem_sdk_new.extensions.fadeOut
+import com.tangem.tap.common.extensions.getDrawableCompat
+import com.tangem.tap.common.extensions.stripZeroPlainString
+import com.tangem.tap.common.redux.navigation.ShareElement
+import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
+import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
+import com.tangem.tap.features.addBackPressHandler
+import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_onboarding_main.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
+import kotlinx.android.synthetic.main.view_onboarding_progress.*
+import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
+
+/**
+[REDACTED_AUTHOR]
+ */
+class OnboardingNoteFragment : BaseOnboardingFragment() {
+
+ private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None
+
+ private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
+
+ override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ postponeEnterTransition()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ addBackPressHandler(this)
+
+ imv_front_card.transitionName = ShareElement.imvFrontCard
+ startPostponedEnterTransition()
+
+ toolbar.setTitle(R.string.onboarding_title)
+ btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
+
+ store.dispatch(OnboardingNoteAction.LoadCardArtwork)
+ store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
+ }
+
+ override fun subscribeToStore() {
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.onboardingNoteState == newState.onboardingNoteState
+ }.select { it.onboardingNoteState }
+ }
+ storeSubscribersList.add(this)
+ }
+
+ override fun newState(state: OnboardingNoteState) {
+ if (activity == null) return
+
+ Picasso.get()
+ .load(state.cardArtworkUrl)
+ .error(R.drawable.card_placeholder_black)
+ .placeholder(R.drawable.card_placeholder_black)
+ ?.into(imv_front_card)
+
+ pb_state.max = state.steps.size - 1
+ pb_state.progress = state.progress
+
+ when (state.currentStep) {
+ OnboardingNoteStep.None -> setupNoneState(state)
+ OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
+ OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
+ OnboardingNoteStep.Done -> setupDoneState(state)
+ }
+ setBalance(state)
+ showConfetti(state.showConfetti)
+ previousStep = state.currentStep
+ }
+
+ private fun setBalance(state: OnboardingNoteState) {
+ if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
+
+ if (state.balanceCriticalError == null) {
+ val balanceValue = state.walletBalance.value.stripZeroPlainString()
+ tv_balance_value.text = balanceValue
+ tv_balance_currency.text = state.walletBalance.currency.currencySymbol
+ } else {
+ tv_balance_value.text = "–"
+ tv_balance_currency.text = ""
+ }
+ }
+
+ private fun setupNoneState(state: OnboardingNoteState) {
+ btn_main_action.isVisible = false
+ btn_alternative_action.isVisible = false
+ btnRefreshBalanceWidget.show(false)
+ tv_header.isVisible = false
+ tv_body.isVisible = false
+
+ btn_main_action.text = ""
+ btn_alternative_action.text = ""
+ tv_header.text = ""
+ tv_body.text = ""
+ }
+
+ private fun setupCreateWalletState(state: OnboardingNoteState) {
+ btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
+ btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
+ btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
+ btn_alternative_action.setOnClickListener { }
+
+ btnRefreshBalanceWidget.mainView.setOnClickListener(null)
+
+ tv_header.setText(R.string.onboarding_create_wallet_header)
+ tv_body.setText(R.string.onboarding_create_wallet_body)
+
+ imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
+ updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
+
+ btn_alternative_action.isVisible = false // temporary
+ }
+
+ private fun setupTopUpWalletState(state: OnboardingNoteState) {
+ if (state.isBuyAllowed) {
+ btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
+ btn_main_action.setOnClickListener {
+ store.dispatch(OnboardingNoteAction.TopUp)
+ }
+
+ btn_alternative_action.isVisible = true
+ btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
+ btn_alternative_action.setOnClickListener {
+ store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
+ }
+ } else {
+ btn_main_action.setText(R.string.onboarding_button_receive_crypto)
+ btn_main_action.setOnClickListener {
+ store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
+ }
+
+ btn_alternative_action.isVisible = false
+ }
+
+ tv_header.setText(R.string.onboarding_top_up_header)
+ if (state.balanceNonCriticalError == null) {
+ tv_body.setText(R.string.onboarding_top_up_body)
+ } else {
+ state.walletBalance.amountToCreateAccount?.let { amount ->
+ val tvBodyMessage = getString(
+ R.string.onboarding_top_up_body_no_account_error,
+ amount, state.walletBalance.currency.currencySymbol
+ )
+ tv_body.text = tvBodyMessage
+ }
+ }
+
+ btnRefreshBalanceWidget.changeState(state.walletBalance.state)
+ if (btnRefreshBalanceWidget.isShowing != true) {
+ btnRefreshBalanceWidget.mainView.setOnClickListener {
+ store.dispatch(OnboardingNoteAction.Balance.Update)
+ }
+ }
+ imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet)
+ }
+
+ private fun setupDoneState(state: OnboardingNoteState) {
+ btn_main_action.setText(R.string.onboarding_done_button_continue)
+ btn_main_action.setOnClickListener {
+ showConfetti(false)
+ store.dispatch(OnboardingNoteAction.Done)
+ }
+
+ btn_alternative_action.isVisible = false
+ btn_alternative_action.setText("")
+ btn_alternative_action.setOnClickListener { }
+ btnRefreshBalanceWidget.mainView.setOnClickListener(null)
+
+ tv_header.setText(R.string.onboarding_done_header)
+ tv_body.setText(R.string.onboarding_done_body)
+
+ imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation)
+ }
+
+ private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) {
+ if (this.previousStep == currentStep) return
+
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(requireContext(), layoutId)
+ constraintSet.applyTo(onboarding_main_container)
+ val transition = InternalNoteLayoutTransition()
+ transition.interpolator = OvershootInterpolator()
+ TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
+ }
+}
+
+fun ImageView.swapToBitmapDrawable(bitmap: Bitmap?, duration: Long = 200) {
+ if (drawable is BitmapDrawable && (drawable as BitmapDrawable).bitmap == bitmap) return
+
+ fadeOut(duration) {
+ setImageBitmap(bitmap)
+ fadeIn(duration)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt
new file mode 100644
index 0000000000..5b377fd072
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt
@@ -0,0 +1,37 @@
+package com.tangem.tap.features.onboarding.products.note.redux
+
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.features.onboarding.OnboardingWalletBalance
+import org.rekotlin.Action
+
+/**
+[REDACTED_AUTHOR]
+ */
+sealed class OnboardingNoteAction : Action {
+ // from user, ui
+ object LoadCardArtwork : OnboardingNoteAction()
+ object CreateWallet : OnboardingNoteAction()
+ object TopUp : OnboardingNoteAction()
+ object ShowAddressInfoDialog : OnboardingNoteAction()
+
+ // from redux
+ class SetArtworkUrl(val artworkUrl: String) : OnboardingNoteAction()
+ data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction()
+ object DetermineStepOfScreen : OnboardingNoteAction()
+ object Done : OnboardingNoteAction()
+
+ data class SetStepOfScreen(val step: OnboardingNoteStep) : OnboardingNoteAction()
+
+ sealed class Balance {
+ object Update : OnboardingNoteAction()
+ data class Set(val balance: OnboardingWalletBalance) : OnboardingNoteAction()
+ data class SetCriticalError(val error: TapError?) : OnboardingNoteAction()
+ data class SetNonCriticalError(val error: TapError?) : OnboardingNoteAction()
+ }
+
+ sealed class Confetti {
+ object Show : OnboardingNoteAction()
+ object Hide : OnboardingNoteAction()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
new file mode 100644
index 0000000000..558f5b0e5f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
@@ -0,0 +1,153 @@
+package com.tangem.tap.features.onboarding.products.note.redux
+
+import com.tangem.common.CompletionResult
+import com.tangem.common.extensions.guard
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.postUi
+import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.extensions.hasWallets
+import com.tangem.tap.domain.extensions.makePrimaryWalletManager
+import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.DispatchFunction
+import org.rekotlin.Middleware
+
+class OnboardingNoteMiddleware {
+ companion object {
+ val handler = onboardingNoteMiddleware
+ }
+}
+
+private val onboardingNoteMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ handleNoteAction(action, dispatch)
+ next(action)
+ }
+ }
+}
+
+private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
+ if (action !is OnboardingNoteAction) return
+ val globalState = store.state.globalState
+ val onboardingManager = globalState.onboardingManager ?: return
+
+ val scanResponse = onboardingManager.scanResponse
+ val card = onboardingManager.scanResponse.card
+ val noteState = store.state.onboardingNoteState
+
+ when (action) {
+ is OnboardingNoteAction.LoadCardArtwork -> {
+ scope.launch {
+ val artworkUrl = onboardingManager.loadArtworkUrl()
+ withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artworkUrl)) }
+ }
+ }
+ is OnboardingNoteAction.DetermineStepOfScreen -> {
+ val step = when {
+ !card.hasWallets() -> OnboardingNoteStep.CreateWallet
+ noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done
+ else -> OnboardingNoteStep.TopUpWallet
+ }
+ store.dispatch((OnboardingNoteAction.SetStepOfScreen(step)))
+ }
+ is OnboardingNoteAction.SetStepOfScreen -> {
+ when (action.step) {
+ OnboardingNoteStep.TopUpWallet -> {
+ store.dispatch(OnboardingNoteAction.Balance.Update)
+ }
+ OnboardingNoteStep.Done -> {
+ onboardingManager.activationFinished(card.cardId)
+ postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
+ }
+ }
+ }
+ is OnboardingNoteAction.CreateWallet -> {
+ scope.launch {
+ val result = tangemSdkManager.createProductWallet(scanResponse)
+ withMainContext {
+ when (result) {
+ is CompletionResult.Success -> {
+ val updatedResponse = scanResponse.copy(card = result.data)
+ onboardingManager.scanResponse = updatedResponse
+ onboardingManager.activationStarted(updatedResponse.card.cardId)
+ store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
+ }
+ is CompletionResult.Failure -> {
+// do nothing
+ }
+ }
+ }
+ }
+ }
+ is OnboardingNoteAction.Balance.Update -> {
+ val walletManager = if (noteState.walletManager != null) {
+ noteState.walletManager
+ } else {
+ val wmFactory = globalState.tapWalletManager.walletManagerFactory
+ val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard {
+ val message = "Loading cancelled. Cause: wallet manager didn't created"
+ val customError = TapError.CustomError(message)
+ store.dispatchErrorNotification(customError)
+ return
+ }
+ dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
+ walletManager
+ }
+
+ val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading
+ val balanceIsLoading = noteState.walletBalance.copy(
+ currency = Currency.Blockchain(walletManager.wallet.blockchain),
+ state = ProgressState.Loading,
+ error = null,
+ criticalError = null
+ )
+ store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading))
+
+ scope.launch {
+ val loadedBalance = onboardingManager.updateBalance(walletManager)
+ delay(if (isLoadedBefore) 0 else 300)
+ loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) }
+ withMainContext {
+ store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
+ store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
+ store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
+ }
+ }
+ }
+ is OnboardingNoteAction.Balance.Set -> {
+ if (action.balance.balanceIsToppedUp()) {
+ store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done))
+ }
+ }
+ is OnboardingNoteAction.ShowAddressInfoDialog -> {
+ val addressData = noteState.walletManager?.getAddressData() ?: return
+
+ val appDialog = AppDialog.AddressInfoDialog(noteState.walletBalance.currency, addressData)
+ store.dispatchDialogShow(appDialog)
+ }
+ is OnboardingNoteAction.TopUp -> {
+ val topUpUrl = noteState.walletManager?.getToUpUrl() ?: return
+ store.dispatchOpenUrl(topUpUrl)
+ }
+ OnboardingNoteAction.Done -> {
+ store.dispatch(GlobalAction.Onboarding.Stop)
+ scope.launch {
+ store.onCardScanned(scanResponse)
+ withMainContext { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt
new file mode 100644
index 0000000000..7626922560
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt
@@ -0,0 +1,49 @@
+package com.tangem.tap.features.onboarding.products.note.redux
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
+import org.rekotlin.Action
+
+class OnboardingNoteReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, appState: AppState): OnboardingNoteState {
+ var state = appState.onboardingNoteState
+
+ when (action) {
+ is GlobalAction.Onboarding.Start -> {
+ state = OnboardingNoteState()
+ }
+ is OnboardingNoteAction.SetArtworkUrl -> {
+ state = state.copy(cardArtworkUrl = action.artworkUrl)
+ }
+ is OnboardingNoteAction.SetWalletManager -> {
+ state = state.copy(walletManager = action.walletManager)
+ }
+ is OnboardingNoteAction.Balance.Set -> {
+ state = state.copy(walletBalance = action.balance)
+ }
+ is OnboardingNoteAction.Balance.SetCriticalError -> {
+ state = state.copy(balanceCriticalError = action.error)
+ }
+ is OnboardingNoteAction.Balance.SetNonCriticalError -> {
+ state = state.copy(balanceNonCriticalError = action.error)
+ }
+ is OnboardingNoteAction.SetStepOfScreen -> {
+ if (action.step != state.currentStep && state.steps.contains(action.step)) {
+ state = state.copy(currentStep = action.step)
+ }
+ }
+ is OnboardingNoteAction.Confetti.Show -> {
+ state = state.copy(showConfetti = true)
+ }
+ is OnboardingNoteAction.Confetti.Hide -> {
+ state = state.copy(showConfetti = false)
+ }
+ }
+
+ return state
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
new file mode 100644
index 0000000000..150c551f0b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.features.onboarding.products.note.redux
+
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.extensions.buyIsAllowed
+import com.tangem.tap.features.onboarding.OnboardingWalletBalance
+import com.tangem.tap.store
+import org.rekotlin.StateType
+import kotlin.properties.ReadOnlyProperty
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class OnboardingNoteState(
+ val walletManager: WalletManager? = null,
+ // UI
+ val cardArtworkUrl: String? = null,
+ val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(),
+ val balanceNonCriticalError: TapError? = null,
+ val balanceCriticalError: TapError? = null,
+ val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
+ val steps: List = OnboardingNoteStep.values().toList(),
+ val showConfetti: Boolean = false,
+) : StateType {
+
+ val progress: Int
+ get() = steps.indexOf(currentStep)
+
+ val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property ->
+ store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false
+ }
+}
+
+enum class OnboardingNoteStep {
+ None, CreateWallet, TopUpWallet, Done
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt
new file mode 100644
index 0000000000..22e479721b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt
@@ -0,0 +1,120 @@
+package com.tangem.tap.features.onboarding.products.otherCards
+
+import android.os.Bundle
+import android.view.View
+import android.view.animation.OvershootInterpolator
+import androidx.annotation.LayoutRes
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.view.isVisible
+import androidx.transition.TransitionManager
+import com.squareup.picasso.Picasso
+import com.tangem.tap.common.extensions.getDrawableCompat
+import com.tangem.tap.common.redux.navigation.ShareElement
+import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
+import com.tangem.tap.features.addBackPressHandler
+import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
+import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsAction
+import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
+import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_onboarding_main.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
+import kotlinx.android.synthetic.main.view_onboarding_progress.*
+
+/**
+[REDACTED_AUTHOR]
+ */
+class OnboardingOtherCardsFragment : BaseOnboardingFragment() {
+
+ override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ postponeEnterTransition()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ addBackPressHandler(this)
+
+ imv_front_card.transitionName = ShareElement.imvFrontCard
+ startPostponedEnterTransition()
+
+ toolbar.setTitle(R.string.onboarding_title)
+ store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
+ store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen)
+ }
+
+ override fun subscribeToStore() {
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.onboardingOtherCardsState == newState.onboardingOtherCardsState
+ }.select { it.onboardingOtherCardsState }
+ }
+ storeSubscribersList.add(this)
+ }
+
+ override fun newState(state: OnboardingOtherCardsState) {
+ if (activity == null) return
+ if (state.currentStep == OnboardingOtherCardsStep.None) return
+
+ Picasso.get()
+ .load(state.cardArtworkUrl)
+ .error(R.drawable.card_placeholder_black)
+ .placeholder(R.drawable.card_placeholder_black)
+ ?.into(imv_front_card)
+
+ pb_state.max = state.steps.size - 1
+ pb_state.progress = state.progress
+
+ when (state.currentStep) {
+ OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState(state)
+ OnboardingOtherCardsStep.Done -> setupDoneState(state)
+ }
+ showConfetti(state.showConfetti)
+ }
+
+ private fun setupCreateWalletState(state: OnboardingOtherCardsState) {
+ btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
+ btn_main_action.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
+ btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
+ btn_alternative_action.setOnClickListener { }
+
+ tv_header.setText(R.string.onboarding_create_wallet_header)
+ tv_body.setText(R.string.onboarding_create_wallet_body)
+
+ imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
+ updateConstraints(R.layout.lp_onboarding_create_wallet)
+
+ btn_alternative_action.isVisible = false // temporary
+ }
+
+ private fun setupDoneState(state: OnboardingOtherCardsState) {
+ btn_main_action.setText(R.string.onboarding_done_button_continue)
+ btn_main_action.setOnClickListener {
+ showConfetti(false)
+ store.dispatch(OnboardingOtherCardsAction.Done)
+ }
+
+ btn_alternative_action.isVisible = false
+ btn_alternative_action.setText("")
+ btn_alternative_action.setOnClickListener { }
+
+ tv_header.setText(R.string.onboarding_done_header)
+ tv_body.setText(R.string.onboarding_done_body)
+
+ imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ updateConstraints(R.layout.lp_onboarding_done)
+ }
+
+ private fun updateConstraints(@LayoutRes layoutId: Int) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(requireContext(), layoutId)
+ constraintSet.applyTo(onboarding_main_container)
+ val transition = InternalNoteLayoutTransition()
+ transition.interpolator = OvershootInterpolator()
+ TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt
new file mode 100644
index 0000000000..2cd82314b3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt
@@ -0,0 +1,24 @@
+package com.tangem.tap.features.onboarding.products.otherCards.redux
+
+import org.rekotlin.Action
+
+/**
+[REDACTED_AUTHOR]
+ */
+sealed class OnboardingOtherCardsAction : Action {
+ // from user, ui
+ object LoadCardArtwork : OnboardingOtherCardsAction()
+ object CreateWallet : OnboardingOtherCardsAction()
+
+ // from redux
+ class SetArtworkUrl(val artworkUrl: String) : OnboardingOtherCardsAction()
+ object DetermineStepOfScreen : OnboardingOtherCardsAction()
+ object Done : OnboardingOtherCardsAction()
+
+ data class SetStepOfScreen(val step: OnboardingOtherCardsStep) : OnboardingOtherCardsAction()
+
+ sealed class Confetti {
+ object Show : OnboardingOtherCardsAction()
+ object Hide : OnboardingOtherCardsAction()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
new file mode 100644
index 0000000000..4606c539ae
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
@@ -0,0 +1,92 @@
+package com.tangem.tap.features.onboarding.products.otherCards.redux
+
+import com.tangem.common.CompletionResult
+import com.tangem.tap.common.extensions.onCardScanned
+import com.tangem.tap.common.extensions.withMainContext
+import com.tangem.tap.common.postUi
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
+import com.tangem.tap.domain.extensions.hasWallets
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.DispatchFunction
+import org.rekotlin.Middleware
+
+class OnboardingOtherCardsMiddleware {
+ companion object {
+ val handler = onboardingOtherCardsMiddleware
+ }
+}
+
+private val onboardingOtherCardsMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ handleOtherCardsAction(action, dispatch)
+ next(action)
+ }
+ }
+}
+
+private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
+ if (action !is OnboardingOtherCardsAction) return
+ val globalState = store.state.globalState
+ val onboardingManager = globalState.onboardingManager ?: return
+
+ val card = onboardingManager.scanResponse.card
+
+ when (action) {
+ is OnboardingOtherCardsAction.LoadCardArtwork -> {
+ scope.launch {
+ val artworkUrl = onboardingManager.loadArtworkUrl()
+ withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artworkUrl)) }
+ }
+ }
+ is OnboardingOtherCardsAction.DetermineStepOfScreen -> {
+ val step = when {
+ !card.hasWallets() -> OnboardingOtherCardsStep.CreateWallet
+ else -> OnboardingOtherCardsStep.Done
+ }
+ store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step)))
+ }
+ is OnboardingOtherCardsAction.SetStepOfScreen -> {
+ if (action.step == OnboardingOtherCardsStep.Done) {
+ onboardingManager.activationFinished(card.cardId)
+ postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
+ }
+ }
+ is OnboardingOtherCardsAction.CreateWallet -> {
+ scope.launch {
+ val result = tangemSdkManager.createProductWallet(onboardingManager.scanResponse)
+ withMainContext {
+ when (result) {
+ is CompletionResult.Success -> {
+ val updatedResponse = onboardingManager.scanResponse.copy(card = result.data)
+ onboardingManager.scanResponse = updatedResponse
+ onboardingManager.activationStarted(updatedResponse.card.cardId)
+
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
+ }
+ is CompletionResult.Failure -> {
+// do nothing
+ }
+ }
+ }
+ }
+ }
+ OnboardingOtherCardsAction.Done -> {
+ store.dispatch(GlobalAction.Onboarding.Stop)
+ scope.launch {
+ store.onCardScanned(onboardingManager.scanResponse)
+ withMainContext { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt
new file mode 100644
index 0000000000..e8dc2ae111
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt
@@ -0,0 +1,37 @@
+package com.tangem.tap.features.onboarding.products.otherCards.redux
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
+import org.rekotlin.Action
+
+class OnboardingOtherCardsReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, appState: AppState): OnboardingOtherCardsState {
+ var state = appState.onboardingOtherCardsState
+
+ when (action) {
+ is GlobalAction.Onboarding.Start -> {
+ state = OnboardingOtherCardsState()
+ }
+ is OnboardingOtherCardsAction.SetArtworkUrl -> {
+ state = state.copy(cardArtworkUrl = action.artworkUrl)
+ }
+ is OnboardingOtherCardsAction.SetStepOfScreen -> {
+ if (action.step != state.currentStep && state.steps.contains(action.step)) {
+ state = state.copy(currentStep = action.step)
+ }
+ }
+ is OnboardingOtherCardsAction.Confetti.Show -> {
+ state = state.copy(showConfetti = true)
+ }
+ is OnboardingOtherCardsAction.Confetti.Hide -> {
+ state = state.copy(showConfetti = false)
+ }
+ }
+
+ return state
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt
new file mode 100644
index 0000000000..f502350050
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt
@@ -0,0 +1,22 @@
+package com.tangem.tap.features.onboarding.products.otherCards.redux
+
+import org.rekotlin.StateType
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class OnboardingOtherCardsState(
+ // UI
+ val cardArtworkUrl: String? = null,
+ val showConfetti: Boolean = false,
+ val currentStep: OnboardingOtherCardsStep = OnboardingOtherCardsStep.None,
+ val steps: List = OnboardingOtherCardsStep.values().toList(),
+) : StateType {
+
+ val progress: Int
+ get() = steps.indexOf(currentStep)
+}
+
+enum class OnboardingOtherCardsStep {
+ None, CreateWallet, Done
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt
new file mode 100644
index 0000000000..766930141a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt
@@ -0,0 +1,67 @@
+package com.tangem.tap.features.onboarding.products.twins.redux
+
+import com.tangem.Message
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.extensions.VoidCallback
+import com.tangem.tap.common.redux.StateDialog
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.domain.twins.AssetReader
+import com.tangem.tap.domain.twins.TwinCardsManager
+import com.tangem.tap.features.onboarding.OnboardingWalletBalance
+import org.rekotlin.Action
+
+/**
+[REDACTED_AUTHOR]
+ */
+sealed class TwinCardsAction : Action {
+ data class IfTwinsPrepareState(val scanResponse: ScanResponse) : TwinCardsAction()
+
+ object Init : TwinCardsAction()
+ data class SetMode(val mode: CreateTwinWalletMode) : TwinCardsAction()
+ data class SetStepOfScreen(val step: TwinCardsStep) : TwinCardsAction()
+ data class SetUserUnderstand(val isUnderstand: Boolean) : TwinCardsAction()
+
+ sealed class CardsManager {
+ data class Set(val manager: TwinCardsManager) : TwinCardsAction()
+ object Release : TwinCardsAction()
+ }
+
+ sealed class Wallet : TwinCardsAction() {
+ data class HandleOnBackPressed(
+ // this is necessary for the correct animation of the return of cards to the Home screen
+ val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit
+ ) : TwinCardsAction()
+
+ data class ShowInterruptDialog(val onOk: VoidCallback) : TwinCardsAction(), StateDialog
+
+ data class LaunchFirstStep(val initialMessage: Message, val reader: AssetReader) : TwinCardsAction()
+ data class LaunchSecondStep(
+ val initialMessage: Message,
+ val preparingMessage: Message,
+ val creatingWalletMessage: Message,
+ ) : TwinCardsAction()
+
+ data class LaunchThirdStep(val message: Message) : TwinCardsAction()
+ }
+
+
+ // for the onboarding
+ data class SetPairCardId(val cardId: String) : TwinCardsAction()
+ object TopUp : TwinCardsAction()
+ object ShowAddressInfoDialog : TwinCardsAction()
+ data class SetWalletManager(val walletManager: WalletManager) : TwinCardsAction()
+ object Done : TwinCardsAction()
+
+ sealed class Balance {
+ object Update : TwinCardsAction()
+ data class Set(val balance: OnboardingWalletBalance) : TwinCardsAction()
+ data class SetCriticalError(val error: TapError?) : TwinCardsAction()
+ data class SetNonCriticalError(val error: TapError?) : TwinCardsAction()
+ }
+
+ sealed class Confetti {
+ object Show : TwinCardsAction()
+ object Hide : TwinCardsAction()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
new file mode 100644
index 0000000000..818b1974aa
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
@@ -0,0 +1,267 @@
+package com.tangem.tap.features.onboarding.products.twins.redux
+
+import com.tangem.blockchain.extensions.Result
+import com.tangem.common.CompletionResult
+import com.tangem.common.extensions.guard
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.postUi
+import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.extensions.makePrimaryWalletManager
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.domain.twins.TwinCardsManager
+import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.DispatchFunction
+import org.rekotlin.Middleware
+
+class TwinCardsMiddleware {
+ companion object {
+ val handler = twinsWalletMiddleware
+ }
+}
+
+private val twinsWalletMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ handle(action, dispatch)
+ next(action)
+ }
+ }
+}
+
+private fun handle(action: Action, dispatch: DispatchFunction) {
+ val action = action as? TwinCardsAction ?: return
+
+ val globalState = store.state.globalState
+ val onboardingManager = globalState.onboardingManager
+ val twinCardsState = store.state.twinCardsState
+
+ fun getScanResponse(): ScanResponse {
+ return when (twinCardsState.mode) {
+ CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
+ CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
+ } ?: throw NullPointerException("ScanResponse can't be NULL")
+ }
+
+ fun updateScanResponse(response: ScanResponse) {
+ when (twinCardsState.mode) {
+ CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
+ CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanNoteResponse(response))
+ }
+ }
+
+ fun startCardActivation(cardId: String) {
+ if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
+ onboardingManager?.activationStarted(cardId)
+ }
+ }
+
+ fun finishCardActivation() {
+ if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
+ onboardingManager?.activationFinished(getScanResponse().card.cardId)
+ twinCardsState.pairCardId?.let { onboardingManager?.activationFinished(it) }
+ }
+ }
+
+ when (action) {
+ is TwinCardsAction.Init -> {
+ if (twinCardsState.currentStep == TwinCardsStep.WelcomeOnly) return
+
+ when (twinCardsState.mode) {
+ CreateTwinWalletMode.CreateWallet -> {
+ if (preferencesStorage.wasTwinsOnboardingShown()) {
+ val step = when {
+ !getScanResponse().twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet
+ twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done
+ else -> TwinCardsStep.TopUpWallet
+ }
+ store.dispatch((TwinCardsAction.SetStepOfScreen(step)))
+ } else {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Welcome))
+ }
+ }
+ CreateTwinWalletMode.RecreateWallet -> {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
+ }
+ }
+ }
+ is TwinCardsAction.Wallet.HandleOnBackPressed -> {
+ val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet
+ && twinCardsState.currentStep != TwinCardsStep.TopUpWallet
+ && twinCardsState.currentStep != TwinCardsStep.Done
+
+ if (twinCardsState.showAlert) {
+ val onInterruptPrompt = {
+ store.dispatch(TwinCardsAction.CardsManager.Release)
+ action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
+ store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
+ }
+ }
+ val stateDialog = TwinCardsAction.Wallet.ShowInterruptDialog(onInterruptPrompt)
+ store.dispatchDialogShow(stateDialog)
+ } else {
+ store.dispatch(TwinCardsAction.CardsManager.Release)
+ action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ }
+ }
+ is TwinCardsAction.SetStepOfScreen -> {
+ when (action.step) {
+ TwinCardsStep.WelcomeOnly -> preferencesStorage.saveTwinsOnboardingShown()
+ TwinCardsStep.Welcome -> preferencesStorage.saveTwinsOnboardingShown()
+ TwinCardsStep.TopUpWallet -> store.dispatch(TwinCardsAction.Balance.Update)
+ TwinCardsStep.Done -> {
+ finishCardActivation()
+ postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) }
+ }
+ }
+ }
+ is TwinCardsAction.Wallet.LaunchFirstStep -> {
+ val manager = TwinCardsManager(getScanResponse().card, action.reader)
+ store.dispatch(TwinCardsAction.CardsManager.Set(manager))
+
+ scope.launch {
+ when (val result = manager.createFirstWallet(action.initialMessage)) {
+ is CompletionResult.Success -> {
+ startCardActivation(result.data.cardId)
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ withMainContext {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateSecondWallet))
+ }
+ }
+ is CompletionResult.Failure -> {
+ }
+ }
+ }
+ }
+ is TwinCardsAction.Wallet.LaunchSecondStep -> {
+ val manager = twinCardsState.twinCardsManager ?: return
+
+ scope.launch {
+ val result = manager.createSecondWallet(
+ action.initialMessage,
+ action.preparingMessage,
+ action.creatingWalletMessage
+ )
+ when (result) {
+ is CompletionResult.Success -> {
+ startCardActivation(result.data.cardId)
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ withMainContext {
+ store.dispatch(TwinCardsAction.SetPairCardId(result.data.cardId))
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateThirdWallet))
+ }
+ }
+ is CompletionResult.Failure -> {
+ }
+ }
+ }
+ }
+ is TwinCardsAction.Wallet.LaunchThirdStep -> {
+ val manager = twinCardsState.twinCardsManager ?: return
+
+ scope.launch {
+ when (val result = manager.complete(action.message)) {
+ is Result.Success -> {
+ updateScanResponse(result.data)
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ withMainContext {
+ when (twinCardsState.mode) {
+ CreateTwinWalletMode.CreateWallet -> {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet))
+ }
+ CreateTwinWalletMode.RecreateWallet -> {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
+ }
+ }
+ }
+ }
+ is Result.Failure -> {
+ }
+ }
+ }
+ }
+ is TwinCardsAction.Balance.Update -> {
+ val walletManager = if (twinCardsState.walletManager != null) {
+ twinCardsState.walletManager
+ } else {
+ val wmFactory = globalState.tapWalletManager.walletManagerFactory
+ val walletManager = wmFactory.makePrimaryWalletManager(getScanResponse()).guard {
+ val message = "Loading cancelled. Cause: wallet manager didn't created"
+ val customError = TapError.CustomError(message)
+ store.dispatchErrorNotification(customError)
+ return
+ }
+ dispatch(TwinCardsAction.SetWalletManager(walletManager))
+ walletManager
+ }
+ val isLoadedBefore = twinCardsState.walletBalance.state != ProgressState.Loading
+ val balanceIsLoading = twinCardsState.walletBalance.copy(
+ currency = Currency.Blockchain(walletManager.wallet.blockchain),
+ state = ProgressState.Loading,
+ error = null,
+ criticalError = null
+ )
+ store.dispatch(TwinCardsAction.Balance.Set(balanceIsLoading))
+
+ scope.launch {
+ val loadedBalance = onboardingManager?.updateBalance(walletManager) ?: return@launch
+ loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) }
+ delay(if (isLoadedBefore) 0 else 300)
+ withMainContext {
+ store.dispatch(TwinCardsAction.Balance.Set(loadedBalance))
+ store.dispatch(TwinCardsAction.Balance.SetCriticalError(loadedBalance.criticalError))
+ store.dispatch(TwinCardsAction.Balance.SetNonCriticalError(loadedBalance.error))
+ }
+ }
+
+ }
+ is TwinCardsAction.Balance.Set -> {
+ if (action.balance.balanceIsToppedUp()) {
+ scope.launch {
+ withMainContext { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done)) }
+ }
+ }
+ }
+ is TwinCardsAction.ShowAddressInfoDialog -> {
+ val addressData = twinCardsState.walletManager?.getAddressData() ?: return
+
+ val appDialog = AppDialog.AddressInfoDialog(twinCardsState.walletBalance.currency, addressData)
+ store.dispatchDialogShow(appDialog)
+ }
+ is TwinCardsAction.TopUp -> {
+ val topUpUrl = twinCardsState.walletManager?.getToUpUrl() ?: return
+ store.dispatchOpenUrl(topUpUrl)
+ }
+ TwinCardsAction.Done -> {
+ scope.launch {
+ val scanResponse = getScanResponse()
+ store.onCardScanned(scanResponse)
+ withMainContext {
+ when (twinCardsState.mode) {
+ CreateTwinWalletMode.CreateWallet -> {
+ store.dispatch(GlobalAction.Onboarding.Stop)
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
+ }
+ CreateTwinWalletMode.RecreateWallet -> {
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt
new file mode 100644
index 0000000000..437213c588
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt
@@ -0,0 +1,73 @@
+package com.tangem.tap.features.onboarding.products.twins.redux
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.domain.twins.getTwinCardNumber
+import org.rekotlin.Action
+
+class TwinCardsReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, state: AppState): TwinCardsState {
+ if (action !is TwinCardsAction) return state.twinCardsState
+
+ var state = state.twinCardsState
+
+ when (action) {
+ is TwinCardsAction.IfTwinsPrepareState -> {
+ state = if (action.scanResponse.isTangemTwins()) {
+ val cardNumber = action.scanResponse.card.getTwinCardNumber() ?: return state
+
+ TwinCardsState(
+ cardNumber = cardNumber,
+ mode = CreateTwinWalletMode.CreateWallet,
+ )
+ } else {
+ TwinCardsState()
+ }
+ }
+ is TwinCardsAction.SetMode -> {
+ state = state.copy(
+ mode = action.mode,
+ userWasUnderstandIfWalletRecreate = false,
+ )
+ }
+ is TwinCardsAction.SetStepOfScreen -> {
+ state = state.copy(currentStep = action.step)
+ }
+ is TwinCardsAction.SetUserUnderstand -> {
+ state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand)
+ }
+ is TwinCardsAction.CardsManager.Set -> {
+ state = state.copy(twinCardsManager = action.manager)
+ }
+ TwinCardsAction.CardsManager.Release -> {
+ state = state.copy(twinCardsManager = null)
+ }
+ is TwinCardsAction.SetPairCardId -> {
+ state = state.copy(pairCardId = action.cardId)
+ }
+ is TwinCardsAction.SetWalletManager -> {
+ state = state.copy(walletManager = action.walletManager)
+ }
+ is TwinCardsAction.Balance.Set -> {
+ state = state.copy(walletBalance = action.balance)
+ }
+ is TwinCardsAction.Balance.SetCriticalError -> {
+ state = state.copy(balanceCriticalError = action.error)
+ }
+ is TwinCardsAction.Balance.SetNonCriticalError -> {
+ state = state.copy(balanceNonCriticalError = action.error)
+ }
+ is TwinCardsAction.Confetti.Show -> {
+ state = state.copy(showConfetti = true)
+ }
+ is TwinCardsAction.Confetti.Hide -> {
+ state = state.copy(showConfetti = false)
+ }
+ }
+
+ return state
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
new file mode 100644
index 0000000000..9d68635c08
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
@@ -0,0 +1,71 @@
+package com.tangem.tap.features.onboarding.products.twins.redux
+
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.extensions.buyIsAllowed
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.domain.twins.TwinCardsManager
+import com.tangem.tap.features.onboarding.OnboardingWalletBalance
+import com.tangem.tap.store
+import org.rekotlin.StateType
+import kotlin.properties.ReadOnlyProperty
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class TwinCardsState(
+ // if cardNumber = null -> the scanned card is not Twin
+ val cardNumber: TwinCardNumber? = null,
+ val mode: CreateTwinWalletMode = CreateTwinWalletMode.CreateWallet,
+ val currentStep: TwinCardsStep = TwinCardsStep.Welcome,
+ val userWasUnderstandIfWalletRecreate: Boolean = false,
+ // available only when creating or recreating wallets
+ val twinCardsManager: TwinCardsManager? = null,
+
+ // for the onboarding
+ val pairCardId: String? = null, // available after create\recreate wallets
+ val walletManager: WalletManager? = null,
+ val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(),
+ val balanceNonCriticalError: TapError? = null,
+ val balanceCriticalError: TapError? = null,
+ val showConfetti: Boolean = false,
+) : StateType {
+
+ val steps: List
+ get() = when (mode) {
+ CreateTwinWalletMode.CreateWallet -> listOf(
+ TwinCardsStep.None,
+ TwinCardsStep.CreateFirstWallet,
+ TwinCardsStep.CreateSecondWallet,
+ TwinCardsStep.CreateThirdWallet,
+ TwinCardsStep.TopUpWallet,
+ TwinCardsStep.Done
+ )
+ CreateTwinWalletMode.RecreateWallet -> listOf(
+ TwinCardsStep.None,
+ TwinCardsStep.CreateFirstWallet,
+ TwinCardsStep.CreateSecondWallet,
+ TwinCardsStep.CreateThirdWallet,
+ TwinCardsStep.Done
+ )
+ }
+
+ val progress: Int
+ get() = steps.indexOf(currentStep)
+
+ val showAlert: Boolean
+ get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
+
+ val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property ->
+ store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false
+ }
+}
+
+enum class CreateTwinWalletMode { CreateWallet, RecreateWallet }
+
+enum class TwinCardsStep {
+ None, WelcomeOnly, Welcome, Warning, CreateFirstWallet, CreateSecondWallet, CreateThirdWallet,
+
+ // for the onboarding
+ TopUpWallet, Done
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt
new file mode 100644
index 0000000000..33eaf7a983
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt
@@ -0,0 +1,385 @@
+package com.tangem.tap.features.onboarding.products.twins.ui
+
+import android.os.Bundle
+import android.view.View
+import android.view.animation.OvershootInterpolator
+import androidx.annotation.LayoutRes
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.view.isVisible
+import androidx.transition.TransitionInflater
+import androidx.transition.TransitionManager
+import com.squareup.picasso.Picasso
+import com.tangem.Message
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.extensions.VoidCallback
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.common.redux.navigation.ShareElement
+import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
+import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
+import com.tangem.tap.domain.twins.AssetReader
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.domain.twins.TwinsCardWidget
+import com.tangem.tap.features.addBackPressHandler
+import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
+import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
+import com.tangem.tap.features.wallet.redux.Artwork
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_onboarding_main.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
+import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
+import kotlinx.android.synthetic.main.view_bg_twins_welcome.*
+import kotlinx.android.synthetic.main.view_onboarding_progress.*
+import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
+
+class TwinsCardsFragment : BaseOnboardingFragment() {
+
+ private var previousStep = TwinCardsStep.None
+
+ private lateinit var twinsWidget: TwinsCardWidget
+ private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
+
+ private val assetReader: AssetReader by lazy {
+ object : AssetReader {
+ override fun readAssetAsString(name: String): String = requireContext().readAssetAsString(name)
+ }
+ }
+
+ override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ postponeEnterTransition()
+ store.dispatch(TwinCardsAction.Init)
+ }
+
+ override fun configureTransitions() {
+ when (store.state.twinCardsState.mode) {
+ CreateTwinWalletMode.CreateWallet -> super.configureTransitions()
+ CreateTwinWalletMode.RecreateWallet -> {
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.slide_right)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ addBackPressHandler(this)
+
+ reconfigureLayoutForTwins()
+ twinsWidget = TwinsCardWidget(LeapfrogWidget(cards_container)) { 285f }
+ btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
+
+ toolbar.title = getText(R.string.twins_recreate_toolbar)
+
+ Picasso.get()
+ .load(Artwork.TWIN_CARD_1)
+ .error(R.drawable.card_placeholder_black)
+ .placeholder(R.drawable.card_placeholder_black)
+ ?.into(imv_twin_front_card)
+
+ Picasso.get()
+ .load(Artwork.TWIN_CARD_2)
+ .error(R.drawable.card_placeholder_white)
+ .placeholder(R.drawable.card_placeholder_white)
+ ?.into(imv_twin_back_card)
+ }
+
+ private fun reconfigureLayoutForTwins() {
+ imv_front_card.hide()
+ imv_card_background.hide()
+ cards_container.show()
+
+ imv_twin_front_card.transitionName = ShareElement.imvFrontCard
+ imv_twin_back_card.transitionName = ShareElement.imvBackCard
+
+ // if don't this, the bg_circle_... is overflow the app_bar
+ app_bar.bringToFront()
+ }
+
+ override fun subscribeToStore() {
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.twinCardsState == newState.twinCardsState
+ }.select { it.twinCardsState }
+ }
+ storeSubscribersList.add(this)
+ }
+
+ override fun newState(state: TwinCardsState) {
+ if (activity == null) return
+
+ pb_state.max = state.steps.size - 1
+ pb_state.progress = state.progress
+
+ when (state.currentStep) {
+ TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state)
+ TwinCardsStep.Welcome -> setupWelcomeState(state)
+ TwinCardsStep.Warning -> setupWarningState(state)
+ TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state)
+ TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state)
+ TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state)
+ TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state)
+ TwinCardsStep.Done -> setupDoneState(state)
+ }
+
+ setBalance(state)
+ showConfetti(state.showConfetti)
+ previousStep = state.currentStep
+ }
+
+ private fun setBalance(state: TwinCardsState) {
+ if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
+
+ if (state.balanceCriticalError == null) {
+ val balanceValue = state.walletBalance.value.stripZeroPlainString()
+ tv_balance_value.text = balanceValue
+ tv_balance_currency.text = state.walletBalance.currency.currencySymbol
+ } else {
+ tv_balance_value.text = "–"
+ tv_balance_currency.text = ""
+ }
+ }
+
+ private fun setupWelcomeOnlyState(state: TwinCardsState) {
+ setupWelcomeState(state) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.None))
+ }
+ }
+
+ private fun setupWelcomeState(state: TwinCardsState) {
+ setupWelcomeState(state) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) }
+ }
+
+ private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) {
+ twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
+
+ onboarding_twins_welcome_bg.show()
+ pb_state.hide()
+
+ tv_header.setText(R.string.twins_onboarding_subtitle)
+ tv_body.text = getString(R.string.twins_onboarding_description_format, state.cardNumber?.pairIndexNumber())
+
+ btn_main_action.setText(R.string.common_continue)
+ btn_main_action.setOnClickListener { mainAction() }
+ }
+
+ private fun setupWarningState(state: TwinCardsState) {
+ twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
+
+ onboarding_twins_welcome_bg.hide()
+ pb_state.hide()
+ chb_understand.show()
+
+ tv_header.setText(R.string.common_warning)
+ tv_body.setText(R.string.twins_recreate_warning)
+
+ chb_understand.setOnCheckedChangeListener { buttonView, isChecked ->
+ store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
+ }
+ btn_main_action.isEnabled = state.userWasUnderstandIfWalletRecreate
+ btn_main_action.setText(R.string.common_continue)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
+ }
+ }
+
+ private fun setupCreateFirstWalletState(state: TwinCardsState) {
+ onboarding_twins_welcome_bg.hide()
+ bg_circle_large.hide()
+ bg_circle_medium.hide()
+ bg_circle_min.hide()
+ chb_understand.hide()
+ pb_state.show()
+
+ onboarding_main_container.beginDelayedTransition()
+
+ when (previousStep) {
+ TwinCardsStep.None -> {
+ twinsWidget.toLeapfrog(false) {
+ when (state.cardNumber) {
+ TwinCardNumber.First -> startPostponedEnterTransition()
+ TwinCardNumber.Second -> {
+ switchToCard(state.cardNumber, false) { startPostponedEnterTransition() }
+ }
+ }
+ }
+ }
+ TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
+ twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
+ }
+ }
+
+ val twinIndexNumber = state.cardNumber?.indexNumber()
+ tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
+ tv_body.setText(R.string.onboarding_twins_interrupt_warning)
+
+ btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.Wallet.LaunchFirstStep(
+ Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
+ assetReader
+ ))
+ }
+
+ btn_alternative_action.isVisible = false
+ btn_alternative_action.isClickable = false
+ }
+
+ private fun setupCreateSecondWalletState(state: TwinCardsState) {
+ switchToCard(state.cardNumber?.pairNumber())
+
+ val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
+ tv_header.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
+ tv_body.setText(R.string.onboarding_twins_interrupt_warning)
+
+ btn_main_action.text = getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.Wallet.LaunchSecondStep(
+ Message(getString(R.string.twins_recreate_title_format, twinPairIndexNumber)),
+ Message(getString(R.string.twins_recreate_title_preparing)),
+ Message(getString(R.string.twins_recreate_title_creating_wallet)),
+ ))
+ }
+ }
+
+ private fun setupCreateThirdWalletState(state: TwinCardsState) {
+ switchToCard(state.cardNumber)
+
+ val twinIndexNumber = state.cardNumber?.indexNumber()
+ tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
+ tv_body.setText(R.string.onboarding_twins_interrupt_warning)
+
+ btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.Wallet.LaunchThirdStep(
+ Message(getString(R.string.twins_recreate_title_format, twinIndexNumber))
+ ))
+ }
+ }
+
+
+ private fun setupTopUpWalletState(state: TwinCardsState) {
+ when (previousStep) {
+ TwinCardsStep.None -> {
+ when (state.cardNumber) {
+ TwinCardNumber.First -> {
+ twinsWidget.leapfrogWidget.unfold(false) {
+ twinsWidget.toActivate(false) {
+ startPostponedEnterTransition()
+ }
+ }
+ }
+ TwinCardNumber.Second -> {
+ switchToCard(state.cardNumber, false) {
+ twinsWidget.toActivate(false)
+ startPostponedEnterTransition()
+ }
+ }
+ }
+ }
+ TwinCardsStep.CreateThirdWallet -> {
+ twinsWidget.toActivate()
+ }
+ }
+
+ if (state.isBuyAllowed) {
+ btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.TopUp)
+ }
+
+ btn_alternative_action.isVisible = true
+ btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
+ btn_alternative_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
+ }
+ } else {
+ btn_main_action.setText(R.string.onboarding_button_receive_crypto)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
+ }
+
+ btn_alternative_action.isVisible = false
+ }
+
+ tv_header.setText(R.string.onboarding_top_up_header)
+ tv_body.setText(R.string.onboarding_top_up_body)
+
+ btnRefreshBalanceWidget.changeState(state.walletBalance.state)
+ if (btnRefreshBalanceWidget.isShowing != true) {
+ btnRefreshBalanceWidget.mainView.setOnClickListener {
+ store.dispatch(TwinCardsAction.Balance.Update)
+ }
+ }
+ imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
+ }
+
+ private fun setupDoneState(state: TwinCardsState) {
+ btn_main_action.setText(R.string.onboarding_done_button_continue)
+ btn_main_action.setOnClickListener {
+ store.dispatch(TwinCardsAction.Confetti.Hide)
+ store.dispatch(TwinCardsAction.Done)
+ }
+
+ btn_alternative_action.isVisible = false
+ btn_alternative_action.isClickable = false
+
+ tv_header.setText(R.string.onboarding_done_header)
+ tv_body.setText(R.string.onboarding_done_body)
+
+ val layout = when (state.mode) {
+ CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
+ CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
+ }
+ updateConstraints(state.currentStep, layout)
+ }
+
+ private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) {
+ if (this.previousStep == currentStep) return
+
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(requireContext(), layoutId)
+ constraintSet.applyTo(onboarding_main_container)
+ val transition = InternalNoteLayoutTransition()
+ transition.interpolator = OvershootInterpolator()
+ TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
+ }
+
+ private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) {
+ val position = cardNumber?.number ?: return
+
+ if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) {
+ twinsWidget.leapfrogWidget.leap(animate, onEnd)
+ } else {
+ onEnd()
+ }
+ }
+
+ override fun handleOnBackPressed() {
+ store.dispatch(TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction ->
+ store.dispatch(TwinCardsAction.Confetti.Hide)
+ showConfetti(false)
+ if (should) switchToCard(TwinCardNumber.First, true, popAction)
+ else popAction()
+ })
+ }
+}
+
+private fun TwinCardNumber?.indexNumber(): String {
+ return this?.number?.toString() ?: ""
+}
+
+private fun TwinCardNumber?.pairIndexNumber(): String {
+ return this?.pairNumber()?.number?.toString() ?: ""
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt
new file mode 100644
index 0000000000..62d98c1f69
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt
@@ -0,0 +1,24 @@
+package com.tangem.tap.features.onboarding.products.twins.ui.dialog
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.tangem.tap.common.extensions.dispatchDialogHide
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class CreateWalletInterruptDialog {
+ companion object {
+ fun create(state: TwinCardsAction.Wallet.ShowInterruptDialog, context: Context): AlertDialog {
+ return MaterialAlertDialogBuilder(context)
+ .setMessage(R.string.onboarding_twin_exit_warning)
+ .setPositiveButton(R.string.warning_button_ok) { _, _ -> }
+ .setOnDismissListener { store.dispatchDialogHide() }
+ .create()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt
new file mode 100644
index 0000000000..69c66581a6
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt
@@ -0,0 +1,42 @@
+package com.tangem.tap.features.onboarding.products.wallet
+
+import android.os.Bundle
+import android.view.View
+import androidx.transition.TransitionInflater
+import com.tangem.tap.features.BaseStoreFragment
+import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import org.rekotlin.StoreSubscriber
+
+/**
+[REDACTED_AUTHOR]
+ */
+class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.fade)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ }
+
+ override fun subscribeToStore() {
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.onboardingWalletState == newState.onboardingWalletState
+ }.select { it.onboardingWalletState }
+ }
+ storeSubscribersList.add(this)
+ }
+
+ override fun newState(state: OnboardingWalletState) {
+ if (activity == null) return
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt
new file mode 100644
index 0000000000..5cfd213d57
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.features.onboarding.products.wallet.redux
+
+import org.rekotlin.Action
+
+/**
+[REDACTED_AUTHOR]
+ */
+sealed class OnboardingWalletAction : Action {
+ object Init : OnboardingWalletAction()
+ object CreateWallet : OnboardingWalletAction()
+ object TopUp : OnboardingWalletAction()
+ object Done : OnboardingWalletAction()
+
+ object ProceedBackup : OnboardingWalletAction()
+
+ sealed class Backup : Action {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
new file mode 100644
index 0000000000..c442090de4
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.features.onboarding.products.wallet.redux
+
+import com.tangem.tap.common.redux.AppState
+import org.rekotlin.Middleware
+
+class OnboardingWalletMiddleware {
+ companion object {
+ val handler = onboardingWalletMiddleware
+ }
+}
+
+private val onboardingWalletMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ next(action)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt
new file mode 100644
index 0000000000..427f44048e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt
@@ -0,0 +1,20 @@
+package com.tangem.tap.features.onboarding.products.wallet.redux
+
+import com.tangem.tap.common.redux.AppState
+import org.rekotlin.Action
+
+class OnboardingWalletReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState {
+ var state = appState.onboardingWalletState
+
+ when (action) {
+
+ }
+
+ return state
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt
new file mode 100644
index 0000000000..5207a85b3d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt
@@ -0,0 +1,14 @@
+package com.tangem.tap.features.onboarding.products.wallet.redux
+
+import org.rekotlin.StateType
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class OnboardingWalletState(
+ val any: String? = null
+) : StateType
+
+enum class OnboardingWalletStep {
+ None, CreateWallet, TopUpWallet, Backup, Done
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index 08c3b35a09..2ed9fe33e0 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
@@ -5,13 +5,13 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.common.redux.ErrorAction
+import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
-import com.tangem.tap.common.redux.global.StateDialog
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
-import com.tangem.tap.features.send.redux.states.SendButtonState
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
@@ -58,6 +58,10 @@ sealed class TransactionExtrasAction : SendScreenActionUi {
data class HandleUserInput(val data: String) : XlmMemo()
}
+ sealed class BinanceMemo : TransactionExtrasAction() {
+ data class HandleUserInput(val data: String) : BinanceMemo()
+ }
+
sealed class XrpDestinationTag : TransactionExtrasAction() {
data class HandleUserInput(val data: String) : XrpDestinationTag()
}
@@ -132,11 +136,12 @@ sealed class ReceiptAction : SendScreenAction {
sealed class SendActionUi : SendScreenActionUi {
data class SendAmountToRecipient(val messageForSigner: Message) : SendScreenActionUi
+ object CheckIfTransactionDataWasProvided : SendScreenActionUi
}
sealed class SendAction : SendScreenAction {
- data class ChangeSendButtonState(val state: SendButtonState) : SendAction()
+ data class ChangeSendButtonState(val state: ButtonState) : SendAction()
object SendSuccess : SendAction(), ToastNotificationAction {
override val messageResource: Int = R.string.send_transaction_success
}
@@ -158,4 +163,11 @@ sealed class SendAction : SendScreenAction {
object Update : SendAction()
data class Set(val warningList: List) : SendAction()
}
+
+ data class SendSpecificTransaction(
+ val sendAmount: String,
+ val destinationAddress: String,
+ val transactionId: String
+ ) : SendAction()
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
index 3da9747dd3..33a7313b5d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
@@ -2,7 +2,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
-import com.tangem.commands.common.network.Result
+import com.tangem.common.services.Result
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.isPayIdSupported
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt
index dcf3bf5a43..3d59d0a270 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt
@@ -68,9 +68,9 @@ class AmountMiddleware {
} else {
val amountErrors = extractErrorsForAmountField(transactionErrors)
if (amountErrors.isNotEmpty()) {
+ transactionErrors.removeAll(amountErrors)
dispatch(AmountAction.SetAmountError(createValidateTransactionError(amountErrors, walletManager)))
}
- transactionErrors.removeAll(amountErrors)
if (transactionErrors.isNotEmpty()) {
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
}
@@ -84,7 +84,7 @@ class AmountMiddleware {
dispatch(AmountAction.SetAmount(sendState.amountState.balanceCrypto, false))
if (sendState.amountState.isCoinAmount()) {
- dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true, chipGroup = true))
+ dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true))
dispatch(FeeActionUi.ChangeIncludeFee(true))
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index b2523e9e7a..04d5b420d5 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
@@ -29,7 +29,7 @@ class RequestFeeMiddleware {
if (!SendState.isReadyToRequestFee()) {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY))
- dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true))
+// dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true))
dispatch(ReceiptAction.RefreshReceipt)
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
return
@@ -60,7 +60,7 @@ class RequestFeeMiddleware {
}
is Result.Failure -> {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.REQUEST_FAILED))
- dispatch(FeeAction.ChangeLayoutVisibility(main = false, controls = false, chipGroup = false))
+ dispatch(FeeAction.ChangeLayoutVisibility(main = false))
}
}
dispatch(AmountActionUi.CheckAmountToSend)
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 7dcececcda..6701ac636d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -1,14 +1,17 @@
package com.tangem.tap.features.send.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.SimpleResult
-import com.tangem.commands.common.card.Card
+import com.tangem.common.card.Card
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
-import com.tangem.tap.common.extensions.stripZeroPlainString
+import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
@@ -19,7 +22,9 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
-import com.tangem.tap.features.send.redux.states.SendButtonState
+import com.tangem.tap.features.send.redux.states.ButtonState
+import com.tangem.tap.features.send.redux.states.ExternalTransactionData
+import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
@@ -37,29 +42,44 @@ import java.util.*
/**
[REDACTED_AUTHOR]
*/
-val sendMiddleware: Middleware = { dispatch, appState ->
- { nextDispatch ->
- { action ->
- when (action) {
- is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch)
- is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch)
- is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
- is SendActionUi.SendAmountToRecipient ->
- verifyAndSendTransaction(action, appState(), dispatch)
- is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
- is SendAction.Warnings.Update -> updateWarnings(dispatch)
+class SendMiddleware {
+ val sendMiddleware: Middleware = { dispatch, appState ->
+ { nextDispatch ->
+ { action ->
+ when (action) {
+ is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch)
+ is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch)
+ is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
+ is SendActionUi.SendAmountToRecipient ->
+ verifyAndSendTransaction(action, appState(), dispatch)
+ is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
+ is SendAction.Warnings.Update -> updateWarnings(dispatch)
+ is SendActionUi.CheckIfTransactionDataWasProvided -> {
+ val transactionData = appState()?.sendState?.externalTransactionData
+ if (transactionData != null) {
+ store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
+ transactionData.destinationAddress, false
+ ))
+ store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO))
+ store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount))
+ store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(),
+ false))
+ }
+ }
+ }
+ nextDispatch(action)
}
- nextDispatch(action)
}
}
+
}
private fun verifyAndSendTransaction(
- action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit,
+ action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit,
) {
val sendState = appState?.sendState ?: return
val walletManager = sendState.walletManager ?: return
- val card = appState.globalState.scanNoteResponse?.card ?: return
+ val card = appState.globalState.scanResponse?.card ?: return
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return
val typedAmount = sendState.amountState.amountToExtract ?: return
val feeAmount = sendState.feeState.currentFee ?: return
@@ -75,8 +95,11 @@ private fun verifyAndSendTransaction(
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
dispatch(AmountActionUi.CheckAmountToSend)
}, sendAllCallback = {
- sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
- sendState.transactionExtrasState, card, dispatch)
+ sendTransaction(
+ action, walletManager, amountToSend, feeAmount, destinationAddress,
+ sendState.transactionExtrasState, card, sendState.externalTransactionData,
+ dispatch
+ )
}, reduceAmount))
}
transactionErrors.isNotEmpty() -> {
@@ -84,29 +107,47 @@ private fun verifyAndSendTransaction(
}
else -> {
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
- sendState.transactionExtrasState, card, dispatch)
+ sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch)
}
}
}
private fun sendTransaction(
- action: SendActionUi.SendAmountToRecipient,
- walletManager: WalletManager,
- amountToSend: Amount,
- feeAmount: Amount,
- destinationAddress: String,
- transactionExtras: TransactionExtrasState,
- card: Card,
- dispatch: (Action) -> Unit,
+ action: SendActionUi.SendAmountToRecipient,
+ walletManager: WalletManager,
+ amountToSend: Amount,
+ feeAmount: Amount,
+ destinationAddress: String,
+ transactionExtras: TransactionExtrasState,
+ card: Card,
+ externalTransactionData: ExternalTransactionData?,
+ dispatch: (Action) -> Unit,
) {
- dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS))
+ dispatch(SendAction.ChangeSendButtonState(ButtonState.PROGRESS))
var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress)
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
+ transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) }
transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) }
scope.launch {
- walletManager.update()
+ val updateWalletResult = walletManager.safeUpdate()
+ if (updateWalletResult is Result.Failure) {
+ when (val error = updateWalletResult.error) {
+ is TapError -> store.dispatchErrorNotification(error)
+ else -> {
+ val tapError = if (error.message == null) {
+ TapError.UnknownError
+ } else {
+ TapError.CustomError(error.message!!)
+ }
+ store.dispatchErrorNotification(tapError)
+ }
+ }
+ withMainContext { dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) }
+ return@launch
+ }
+
val isLinkedTerminal = tangemSdk.config.linkedTerminal
if (card.isStart2Coin) {
tangemSdk.config.linkedTerminal = false
@@ -116,20 +157,29 @@ private fun sendTransaction(
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
- walletSignedHashes = signResponse.walletSignedHashes,
- remainingSignatures = signResponse.walletRemainingSignatures,
- walletPublicKey = walletManager.wallet.publicKey
+ walletSignedHashes = signResponse.totalSignedHashes,
+ walletPublicKey = walletManager.wallet.publicKey,
+ remainingSignatures = signResponse.remainingSignatures
)
)
}
val result = (walletManager as TransactionSender).send(txData, signer)
withContext(Dispatchers.Main) {
+ tangemSdk.config.linkedTerminal = isLinkedTerminal
when (result) {
is SimpleResult.Success -> {
- tangemSdk.config.linkedTerminal = isLinkedTerminal
- FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.TRANSACTION_IS_SENT, card)
+ FirebaseAnalyticsHandler.triggerEvent(
+ event = AnalyticsEvent.TRANSACTION_IS_SENT,
+ card = card,
+ blockchain = walletManager.wallet.blockchain.currency
+ )
dispatch(SendAction.SendSuccess)
- dispatch(NavigationAction.PopBackTo())
+
+ if (externalTransactionData != null) {
+ dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
+ } else {
+ dispatch(NavigationAction.PopBackTo())
+ }
scope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain))
@@ -178,6 +228,16 @@ private fun sendTransaction(
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
+ (result.error as? TangemSdkError)?.let { error ->
+ FirebaseAnalyticsHandler.logCardSdkError(
+ error,
+ FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
+ mapOf(
+ FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
+ to walletManager.wallet.blockchain.currency),
+ card = card,
+ )
+ }
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))
@@ -195,7 +255,7 @@ private fun sendTransaction(
}
}
}
- dispatch(SendAction.ChangeSendButtonState(SendButtonState.ENABLED))
+ dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
}
}
}
@@ -213,7 +273,7 @@ fun extractErrorsForAmountField(errors: EnumSet): EnumSet {
- val notAcceptable = listOf(TransactionError.FeeExceedsBalance, TransactionError.FeeExceedsBalance)
+ val notAcceptable = listOf(TransactionError.AmountExceedsBalance, TransactionError.FeeExceedsBalance)
if (!showIntoAmountField.containsAll(notAcceptable)) showIntoAmountField.add(it)
}
TransactionError.InvalidAmountValue -> showIntoAmountField.add(it)
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
index f5da58ab54..d0a1a13072 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
@@ -45,7 +45,7 @@ class FeeReducer : SendInternalReducer {
state.copy(
mainLayoutIsVisible = getVisibility(state.mainLayoutIsVisible, action.main),
controlsLayoutIsVisible = getVisibility(state.controlsLayoutIsVisible, action.controls),
- feeChipGroupIsVisible = getVisibility(state.mainLayoutIsVisible, action.chipGroup)
+ feeChipGroupIsVisible = getVisibility(state.feeChipGroupIsVisible, action.chipGroup)
)
}
is FeeAction.FeeCalculation.SetFeeResult -> {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt
index 0a4a4df672..bc030f2c8d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt
@@ -4,7 +4,6 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.extensions.scaleToFiat
import com.tangem.tap.common.extensions.stripZeroPlainString
-import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.*
@@ -40,15 +39,15 @@ class ReceiptReducer : SendInternalReducer {
val wallet = sendState.walletManager?.wallet ?: return sendState
val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount)
- val symbols = determineSymbols(wallet)
+ val symbols = determineSymbols(wallet, amountState.typeOfAmount)
val showBlank = !SendState.isReadyToSend()
val result = state.copy(
- visibleTypeOfReceipt = layoutType,
- mainCurrency = amountState.mainCurrency,
- fiat = createFiatType(symbols, showBlank),
- crypto = createCryptoType(symbols, showBlank),
- tokenFiat = createTokenFiatType(symbols, showBlank),
- tokenCrypto = createTokenCryptoType(symbols, showBlank)
+ visibleTypeOfReceipt = layoutType,
+ mainCurrency = amountState.mainCurrency,
+ fiat = createFiatType(symbols, showBlank),
+ crypto = createCryptoType(symbols, showBlank),
+ tokenFiat = createTokenFiatType(symbols, showBlank),
+ tokenCrypto = createTokenCryptoType(symbols, showBlank)
)
return updateLastState(sendState.copy(receiptState = result), result)
}
@@ -65,22 +64,22 @@ class ReceiptReducer : SendInternalReducer {
val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto.minus(feeCrypto))
val totalFiat = convertToFiatPrecision(amountState.amountToSendCrypto)
ReceiptFiat(
- amountFiat = amountFiat,
- feeFiat = feeFiat,
- totalFiat = totalFiat,
- willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
- symbols = symbols
+ amountFiat = amountFiat,
+ feeFiat = feeFiat,
+ totalFiat = totalFiat,
+ willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
+ symbols = symbols
)
} else {
val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto)
val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto)
val totalFiat = convertToFiatPrecision(totalAmountCrypto)
ReceiptFiat(
- amountFiat = amountFiat,
- feeFiat = feeFiat,
- totalFiat = totalFiat,
- willSentCrypto = totalAmountCrypto.stripZeroPlainString(),
- symbols = symbols
+ amountFiat = amountFiat,
+ feeFiat = feeFiat,
+ totalFiat = totalFiat,
+ willSentCrypto = totalAmountCrypto.stripZeroPlainString(),
+ symbols = symbols
)
}
}
@@ -94,22 +93,22 @@ class ReceiptReducer : SendInternalReducer {
if (feeState.feeIsIncluded) {
return ReceiptCrypto(
- amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(),
- feeCrypto = feeCrypto.stripZeroPlainString(),
- totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
- feeFiat = feeFiat,
- willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto),
- symbols = symbols
+ amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(),
+ feeCrypto = feeCrypto.stripZeroPlainString(),
+ totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
+ feeFiat = feeFiat,
+ willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto),
+ symbols = symbols
)
} else {
val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto)
return ReceiptCrypto(
- amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
- feeCrypto = feeCrypto.stripZeroPlainString(),
- totalCrypto = totalCrypto.stripZeroPlainString(),
- feeFiat = feeFiat,
- willSentFiat = convertToFiatPrecision(totalCrypto),
- symbols = symbols
+ amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
+ feeCrypto = feeCrypto.stripZeroPlainString(),
+ totalCrypto = totalCrypto.stripZeroPlainString(),
+ feeFiat = feeFiat,
+ willSentFiat = convertToFiatPrecision(totalCrypto),
+ symbols = symbols
)
}
}
@@ -128,21 +127,21 @@ class ReceiptReducer : SendInternalReducer {
val amountFiat = sendState.tokenConverter!!.toFiatUnscaled(tokensToSend)
val totalFiat = amountFiat.plus(feeFiat)
ReceiptTokenFiat(
- amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(),
- feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString(),
- totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(),
- willSentToken = tokensToSend.stripZeroPlainString(),
- willSentFeeCoin = feeCoin.stripZeroPlainString(),
- symbols = symbols
+ amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(),
+ feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString(),
+ totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(),
+ willSentToken = tokensToSend.stripZeroPlainString(),
+ willSentFeeCoin = feeCoin.stripZeroPlainString(),
+ symbols = symbols
)
} else {
ReceiptTokenFiat(
- amountFiat = EMPTY,
- feeFiat = EMPTY,
- totalFiat = EMPTY,
- willSentToken = tokensToSend.stripZeroPlainString(),
- willSentFeeCoin = feeCoin.stripZeroPlainString(),
- symbols = symbols
+ amountFiat = EMPTY,
+ feeFiat = EMPTY,
+ totalFiat = EMPTY,
+ willSentToken = tokensToSend.stripZeroPlainString(),
+ willSentFeeCoin = feeCoin.stripZeroPlainString(),
+ symbols = symbols
)
}
}
@@ -160,27 +159,30 @@ class ReceiptReducer : SendInternalReducer {
val totalFiat = tokenFiat.plus(feeFiat)
ReceiptTokenCrypto(
- amountToken = tokensToSend.stripZeroPlainString(),
- feeCoin = feeCoin.stripZeroPlainString(),
- totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(),
- symbols = symbols
+ amountToken = tokensToSend.stripZeroPlainString(),
+ feeCoin = feeCoin.stripZeroPlainString(),
+ totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(),
+ symbols = symbols
)
} else {
ReceiptTokenCrypto(
- amountToken = tokensToSend.stripZeroPlainString(),
- feeCoin = feeCoin.stripZeroPlainString(),
- totalFiat = EMPTY,
- symbols = symbols
+ amountToken = tokensToSend.stripZeroPlainString(),
+ feeCoin = feeCoin.stripZeroPlainString(),
+ totalFiat = EMPTY,
+ symbols = symbols
)
}
}
- private fun determineSymbols(wallet: Wallet): ReceiptSymbols {
+ private fun determineSymbols(wallet: Wallet, amountType: AmountType): ReceiptSymbols {
return ReceiptSymbols(
- fiat = store.state.globalState.appCurrency,
- crypto = wallet.blockchain.currency,
- token = wallet.getFirstToken()?.symbol
+ fiat = store.state.globalState.appCurrency,
+ crypto = wallet.blockchain.currency,
+ token = when (amountType) {
+ is AmountType.Token -> amountType.token.symbol
+ else -> null
+ }
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
index 8cad24d39c..673f19cb66 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
@@ -2,7 +2,9 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.tap.common.CurrencyConverter
+import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.features.send.redux.*
+import com.tangem.tap.features.send.redux.states.ExternalTransactionData
import com.tangem.tap.features.send.redux.states.IdStateHolder
import com.tangem.tap.features.send.redux.states.SendState
import org.rekotlin.Action
@@ -40,16 +42,38 @@ class SendScreenReducer {
private class SendReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
val result = when (action) {
- is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state)
+ is SendAction.ChangeSendButtonState -> {
+ sendState.copy(sendButtonState = IndeterminateProgressButton(action.state))
+ }
is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
is SendAction.Dialog.SendTransactionFails -> sendState.copy(dialog = action)
is SendAction.Dialog.Hide -> sendState.copy(dialog = null)
is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList)
+ is SendAction.SendSpecificTransaction ->
+ handleSendSpecificTransactionAction(action, sendState)
else -> return sendState
}
return updateLastState(result, result)
}
+
+ private fun handleSendSpecificTransactionAction(
+ action: SendAction.SendSpecificTransaction, state: SendState
+ ): SendState {
+ return state.copy(
+ externalTransactionData =
+ ExternalTransactionData(action.sendAmount, action.destinationAddress, action.transactionId),
+ feeState = state.feeState.copy(
+ includeFeeSwitcherIsEnabled = false
+ ),
+ amountState = state.amountState.copy(
+ inputIsEnabled = false
+ ),
+ addressPayIdState = state.addressPayIdState.copy(
+ inputIsEnabled = false
+ ),
+ )
+ }
}
private class EmptyReducer : SendInternalReducer {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt
index 6ecd4514d4..4215143301 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt
@@ -14,7 +14,8 @@ class TransactionExtrasReducer : SendInternalReducer {
return when (action) {
is Prepare -> handleInitialization(action, sendState)
Release -> handleRelease(action, sendState)
- is XlmMemo -> handleMemo(action, sendState, sendState.transactionExtrasState)
+ is XlmMemo -> handleXlmMemo(action, sendState, sendState.transactionExtrasState)
+ is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState)
is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState)
else -> sendState
}
@@ -40,6 +41,7 @@ class TransactionExtrasReducer : SendInternalReducer {
}
}
Blockchain.Stellar -> TransactionExtrasState(xlmMemo = XlmMemoState())
+ Blockchain.Binance -> TransactionExtrasState(binanceMemo = BinanceMemoState())
else -> emptyResult
}
return updateLastState(sendState.copy(transactionExtrasState = result), result)
@@ -50,7 +52,7 @@ class TransactionExtrasReducer : SendInternalReducer {
return updateLastState(sendState.copy(transactionExtrasState = result), result)
}
- private fun handleMemo(
+ private fun handleXlmMemo(
action: XlmMemo,
sendState: SendState,
infoState: TransactionExtrasState,
@@ -94,6 +96,26 @@ class TransactionExtrasReducer : SendInternalReducer {
return updateLastState(sendState.copy(transactionExtrasState = result), result)
}
+ private fun handleBinanceMemo(
+ action: BinanceMemo,
+ sendState: SendState,
+ infoState: TransactionExtrasState,
+ ): SendState {
+ val result = when (action) {
+ is BinanceMemo.HandleUserInput -> {
+ val tag = action.data.toBigIntegerOrNull()
+ if (tag != null) {
+ val input = InputViewValue(action.data, true)
+ val tagState = BinanceMemoState(input, tag)
+ infoState.copy(binanceMemo = tagState)
+ } else {
+ infoState
+ }
+ }
+ }
+ return updateLastState(sendState.copy(transactionExtrasState = result), result)
+ }
+
private fun handleXrpTag(
action: XrpDestinationTag,
sendState: SendState,
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
index d17531a84a..e4f816c106 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
@@ -12,7 +12,8 @@ data class AddressPayIdState(
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
val sendingToPayIdEnabled: Boolean = false,
- val pasteIsEnabled: Boolean = false
+ val pasteIsEnabled: Boolean = false,
+ val inputIsEnabled: Boolean = true
) : SendScreenState {
override val stateId: StateId = StateId.ADDRESS_PAY_ID
@@ -26,6 +27,7 @@ data class AddressPayIdState(
data class TransactionExtrasState(
val xlmMemo: XlmMemoState? = null,
+ val binanceMemo: BinanceMemoState? = null,
val xrpDestinationTag: XrpDestinationTagState? = null
) : IdStateHolder {
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
@@ -53,6 +55,16 @@ data class XlmMemoState(
}
}
+data class BinanceMemoState(
+ val viewFieldValue: InputViewValue = InputViewValue(""),
+ val memo: BigInteger? = null,
+ val error: TransactionExtraError? = null
+ ) {
+ companion object {
+ val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
+ }
+}
+
// tag must contains only digits
data class XrpDestinationTagState(
val viewFieldValue: InputViewValue = InputViewValue(""),
@@ -66,5 +78,6 @@ data class XrpDestinationTagState(
enum class TransactionExtraError {
INVALID_DESTINATION_TAG,
- INVALID_XLM_MEMO
+ INVALID_XLM_MEMO,
+ INVALID_BINANCE_MEMO
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
index 41d2fd79c5..ed98986ae3 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
@@ -5,8 +5,9 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.CurrencyConverter
+import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.entities.TapCurrency
-import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
@@ -39,8 +40,9 @@ data class SendState(
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
val sendWarningsList: List = listOf(),
- val sendButtonState: SendButtonState = SendButtonState.DISABLED,
- val dialog: StateDialog? = null
+ val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED),
+ val dialog: StateDialog? = null,
+ val externalTransactionData: ExternalTransactionData? = null
) : SendScreenState {
override val stateId: StateId = StateId.SEND_SCREEN
@@ -86,7 +88,7 @@ data class SendState(
}
}
- fun getButtonState(): SendButtonState = if (isReadyToSend()) SendButtonState.ENABLED else SendButtonState.DISABLED
+ fun getButtonState(): ButtonState = if (isReadyToSend()) ButtonState.ENABLED else ButtonState.DISABLED
fun getTotalAmountToSend(value: BigDecimal = amountState.amountToSendCrypto): BigDecimal {
val needToExtractFee = amountState.isCoinAmount() && feeState.feeIsIncluded
@@ -116,7 +118,7 @@ data class SendState(
}
}
-enum class SendButtonState {
+enum class ButtonState {
ENABLED, DISABLED, PROGRESS
}
@@ -131,7 +133,8 @@ data class AmountState(
val cursorAtTheSamePosition: Boolean = true,
val maxLengthOfAmount: Int = 2,
val decimalSeparator: String = ".",
- val error: TapError? = null
+ val error: TapError? = null,
+ val inputIsEnabled: Boolean = true,
) : SendScreenState {
override val stateId: StateId = StateId.AMOUNT
@@ -166,4 +169,12 @@ data class MainCurrency(
val type: MainCurrencyType,
val currencySymbol: String,
val isEnabled: Boolean = true
+)
+
+data class ExternalTransactionData(
+ val amount: String,
+ val destinationAddress: String,
+ val transactionId: String,
+ val canAmountBeModified: Boolean = false,
+ val canDestinationAddressBeModified: Boolean = false
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
index 2b2b682294..1f91fb98cb 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
@@ -20,10 +20,12 @@ import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.extensions.setOnImeActionListener
import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
+import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
import com.tangem.tap.common.text.truncateMiddleWith
import com.tangem.tap.common.toggleWidget.*
-import com.tangem.tap.features.send.BaseStoreFragment
+import com.tangem.tap.features.BaseStoreFragment
+import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
import com.tangem.tap.features.send.redux.AmountActionUi.*
@@ -31,6 +33,7 @@ import com.tangem.tap.features.send.redux.FeeActionUi.*
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
+import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.mainScope
@@ -62,6 +65,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ addBackPressHandler(this)
etAmountToSend = view.findViewById(R.id.etAmountToSend)
@@ -71,12 +75,13 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
setupAmountLayout()
setupFeeLayout()
setupWarningMessages()
+ store.dispatch(SendActionUi.CheckIfTransactionDataWasProvided)
}
private fun initSendButtonStates() {
btnSend.setOnClickListener {
store.dispatch(SendActionUi.SendAmountToRecipient(
- Message(getString(R.string.initial_message_sign_header))
+ Message(getString(R.string.initial_message_sign_header))
))
}
sendBtn = IndeterminateProgressButtonWidget(btnSend, progress)
@@ -103,14 +108,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
imvQrCode.setOnClickListener {
startActivityForResult(
- Intent(requireContext(), ScanQrCodeActivity::class.java),
- ScanQrCodeActivity.SCAN_QR_REQUEST_CODE
+ Intent(requireContext(), ScanQrCodeActivity::class.java),
+ ScanQrCodeActivity.SCAN_QR_REQUEST_CODE
)
}
}
private fun setupTransactionExtrasLayout() {
- etMemo.inputtedTextAsFlow()
+ etXlmMemo.inputtedTextAsFlow()
.debounce(400)
.filter {
val info = store.state.sendState.transactionExtrasState
@@ -127,6 +132,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
.onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) }
.launchIn(mainScope)
+
+ etBinanceMemo.inputtedTextAsFlow()
+ .debounce(400)
+ .filter {
+ val info = store.state.sendState.transactionExtrasState
+ info.binanceMemo?.viewFieldValue?.value != it
+ }
+ .onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) }
+ .launchIn(mainScope)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -250,7 +264,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
val mainCurrency = sp.getString("mainCurrency", TapCurrency.DEFAULT_FIAT_CURRENCY)
val foundType = MainCurrencyType.values()
- .firstOrNull { it.name.toLowerCase() == mainCurrency!!.toLowerCase() }
+ .firstOrNull { it.name.equals(mainCurrency!!, ignoreCase = true) }
?: MainCurrencyType.CRYPTO
return foundType
}
@@ -260,6 +274,17 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
sp.edit().putString("mainCurrency", type.name).apply()
}
+ override fun handleOnBackPressed() {
+ val externalTransactionData = store.state.sendState.externalTransactionData
+ if (externalTransactionData == null) {
+ store.dispatch(NavigationAction.PopBackTo())
+ } else {
+ store.dispatch(
+ WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)
+ )
+ }
+ }
+
override fun onDestroy() {
keyboardObserver.unregisterListener()
store.dispatch(ReleaseSendState)
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
index cb65943200..8f4b106e97 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
@@ -5,7 +5,6 @@ import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.feedback.SendTransactionFailedEmail
import com.tangem.tap.features.send.redux.SendAction
-import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -23,7 +22,7 @@ class SendTransactionFailsDialog {
store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage)))
}
setPositiveButton(R.string.common_no) { _, _ -> }
- setOnDismissListener { store.dispatch(WalletAction.HideDialog) }
+ setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
}.create()
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt
index aa129ebbb5..17dfb7a5fc 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/FragmentStateSubscriber.kt
@@ -1,6 +1,6 @@
package com.tangem.tap.features.send.ui.stateSubscribers
-import com.tangem.tap.features.send.BaseStoreFragment
+import com.tangem.tap.features.BaseStoreFragment
import org.rekotlin.StateType
import org.rekotlin.StoreSubscriber
import java.lang.ref.WeakReference
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
index 933b1580d7..b90c53e080 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
@@ -7,13 +7,13 @@ import android.text.SpannableStringBuilder
import android.view.View
import android.view.ViewGroup
import androidx.core.text.bold
+import com.tangem.common.extensions.remove
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.getMessageString
import com.tangem.tap.common.text.DecimalDigitsInputFilter
-import com.tangem.tap.common.toggleWidget.ProgressState
import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.assembleErrors
-import com.tangem.tap.features.send.BaseStoreFragment
+import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.SendAction
@@ -65,19 +65,20 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
showView(fg.xlmMemoContainer, infoState.xlmMemo)
showView(fg.xrpDestinationTagContainer, infoState.xrpDestinationTag)
+ showView(fg.binanceMemoContainer, infoState.binanceMemo)
infoState.xlmMemo?.let {
- fg.etMemo.inputType = when (it.selectedMemoType) {
+ fg.etXlmMemo.inputType = when (it.selectedMemoType) {
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
}
- if (!it.viewFieldValue.isFromUserInput) fg.etMemo.setText(it.viewFieldValue.value)
+ if (!it.viewFieldValue.isFromUserInput) fg.etXlmMemo.setText(it.viewFieldValue.value)
if (it.error != null) {
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
- fg.tilMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
+ fg.tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
}
} else {
- fg.tilMemo.error = null
+ fg.tilXlmMemo.error = null
}
}
infoState.xrpDestinationTag?.let {
@@ -92,6 +93,18 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
fg.etDestinationTag.setText(it.viewFieldValue.value)
}
}
+ infoState.binanceMemo?.let {
+ if (infoState.binanceMemo.error != null) {
+ if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
+ fg.tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
+ }
+ } else {
+ fg.tilBinanceMemo.error = null
+ }
+ if (!it.viewFieldValue.isFromUserInput) {
+ fg.etBinanceMemo.setText(it.viewFieldValue.value)
+ }
+ }
}
private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) {
@@ -116,20 +129,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
- when (state.sendButtonState) {
- SendButtonState.ENABLED -> {
- fg.btnSend.isEnabled = true
- sendFragment.sendBtn.changeState(ProgressState.None)
- }
- SendButtonState.DISABLED -> {
- fg.btnSend.isEnabled = false
- sendFragment.sendBtn.changeState(ProgressState.None)
- }
- SendButtonState.PROGRESS -> {
- fg.btnSend.isEnabled = true
- sendFragment.sendBtn.changeState(ProgressState.Progress())
- }
- }
+ sendFragment.sendBtn.changeState(state.sendButtonState.progressState)
+ sendFragment.sendBtn.mainView.isEnabled = state.sendButtonState.enabled
val rv = fg.rv_warning_messages
val adapter = rv.adapter as? WarningMessagesAdapter ?: return
@@ -156,6 +157,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val til = fg.tilAddressOrPayId
val parsedError = parseError(til.context, state.error)
+ til.isEnabled = state.inputIsEnabled
+ fg.imvPaste.show(state.inputIsEnabled)
+ fg.imvQrCode.show(state.inputIsEnabled)
+ fg.flPaste.show(state.inputIsEnabled)
+ fg.flQrCode.show(state.inputIsEnabled)
+
val hintResId = if (state.sendingToPayIdEnabled) {
R.string.send_destination_hint_address_payid
} else {
@@ -179,7 +186,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val messageList = multiError.assembleErrors().map { getMessageString(context, it.first, it.second) }
multiError.builder(messageList)
}
- else -> context.getString(state.error.localizedMessage)
+ else -> context.getString(state.error.messageResource)
}
fg.tilAmountToSend.enableError(true, message)
} else {
@@ -194,10 +201,22 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
- val balanceText = fg.getString(R.string.send_balance_subtitle_format,
- state.mainCurrency.currencySymbol,
- state.viewBalanceValue)
+ val balanceText = when (state.mainCurrency.type) {
+ MainCurrencyType.FIAT -> fg.getString(R.string.send_balance_subtitle_format,
+ state.viewBalanceValue, state.mainCurrency.currencySymbol).remove(":")
+ MainCurrencyType.CRYPTO -> fg.getString(R.string.send_balance_subtitle_format,
+ state.mainCurrency.currencySymbol, state.viewBalanceValue)
+ }
+
fg.tvBalance.update(balanceText)
+
+ fg.tilAmountToSend.isEnabled = state.inputIsEnabled
+
+ val imageRes = if (state.inputIsEnabled) R.drawable.ic_arrows_up_down else 0
+ fg.tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0)
+ val textColor = if (state.inputIsEnabled) R.color.blue else R.color.textGray
+ fg.tvAmountCurrency.setTextColor(fg.getColor(textColor))
+
}
private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
@@ -244,7 +263,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
val willSent = getString(R.string.send_total_subtitle_format,
- receipt.willSentCrypto, receipt.symbols.crypto)
+ receipt.willSentCrypto, receipt.symbols.crypto)
totalLayout.tvWillBeSentValue.update(willSent)
}
@@ -276,9 +295,9 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
val willSent = getString(
- R.string.send_total_subtitle_asset_format,
- receipt.symbols.token ?: "", receipt.willSentToken,
- receipt.symbols.crypto, receipt.willSentFeeCoin
+ R.string.send_total_subtitle_asset_format,
+ receipt.symbols.token ?: "", receipt.willSentToken,
+ receipt.symbols.crypto, receipt.willSentFeeCoin
)
totalLayout.tvWillBeSentValue.update(willSent)
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt
new file mode 100644
index 0000000000..98efd84540
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt
@@ -0,0 +1,66 @@
+package com.tangem.tap.features.tokens.redux
+
+import androidx.annotation.StringRes
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.wallet.R
+
+sealed class CurrencyListItem {
+ data class TokenListItem(val token: Token) : CurrencyListItem()
+ data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
+ data class TitleListItem(
+ @StringRes val titleResId: Int,
+ var isContentShown: Boolean = true,
+ val blockchain: Blockchain? = null,
+ ) : CurrencyListItem()
+
+ companion object {
+ fun createListOfCurrencies(
+ blockchains: List,
+ tokens: List,
+ ): List {
+ val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
+ val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens
+ val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens
+ val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens
+
+ val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum }
+ val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC }
+ val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance }
+ return listOf(TitleListItem(blockchainsTitle)) +
+ blockchains.map { BlockchainListItem(it) } +
+ listOf(TitleListItem(ethereumTokensTitle, blockchain = Blockchain.Ethereum)) +
+ ethereumTokens.map { TokenListItem(it) } +
+ listOf(TitleListItem(bscTokensTitle, blockchain = Blockchain.BSC)) +
+ bscTokens.map { TokenListItem(it) } +
+ listOf(TitleListItem(binanceTokensTitle, blockchain = Blockchain.Binance)) +
+ binanceTokens.map { TokenListItem(it) }
+ }
+ }
+}
+
+fun List.removeTokensForBlockchain(blockchain: Blockchain): List {
+ return filterNot {
+ it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain
+ }
+}
+
+fun List.addTokensForBlockchain(
+ blockchain: Blockchain, fullCurrenciesList: List,
+): List {
+ val tokensToAdd = fullCurrenciesList.filter {
+ it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain
+ }
+ val indexToInsert = indexOfFirst {
+ it is CurrencyListItem.TitleListItem && it.blockchain == blockchain
+ } + 1
+ return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) }
+}
+
+fun List.toggleHeaderContentShownValue(blockchain: Blockchain) {
+ map {
+ if (it is CurrencyListItem.TitleListItem && it.blockchain == blockchain) {
+ it.isContentShown = !it.isContentShown
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
index e0c9abe119..1be9e7c701 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.features.tokens.redux
+import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
-import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
import com.tangem.tap.features.wallet.redux.WalletData
import org.rekotlin.Action
@@ -17,5 +17,6 @@ sealed class TokensAction : Action {
data class SetAddedCurrencies(val wallets: List) : TokensAction()
+ data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
index cb13d64dd0..6ecff3f595 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
@@ -2,7 +2,7 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.currenciesRepository
-import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.store
import org.rekotlin.Middleware
@@ -13,9 +13,13 @@ class TokensMiddleware {
{ action ->
when (action) {
is TokensAction.LoadCurrencies -> {
- val cardFirmware = state()?.globalState?.scanNoteResponse?.card?.firmwareVersion
- val tokens = currenciesRepository.getPopularTokens()
- val blockchains = currenciesRepository.getBlockchains(cardFirmware)
+ val card = state()?.globalState?.scanResponse?.card
+ val isTestcard = card?.isTestCard ?: false
+ val tokens = currenciesRepository.getPopularTokens(isTestcard)
+ val blockchains = currenciesRepository.getBlockchains(
+ cardFirmware = card?.firmwareVersion,
+ isTestNet = isTestcard
+ )
val currencies = CurrencyListItem.createListOfCurrencies(
blockchains, tokens
)
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
index 66a7dd928b..f221424d67 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
@@ -18,7 +18,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
val tokensState = state.tokensState
return when (action) {
is TokensAction.LoadCurrencies.Success -> {
- tokensState.copy(currencies = action.currencies)
+ tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies)
}
is TokensAction.SetAddedCurrencies -> {
tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies())
@@ -28,12 +28,26 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
action.tokens.map { TokenWithAmount(it, null) }
))
}
+
+ is TokensAction.ToggleShowTokensForBlockchain -> {
+ if (action.isShown) {
+ val shownCurrencies = tokensState.shownCurrencies
+ .removeTokensForBlockchain(action.blockchain)
+ shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
+ tokensState.copy(shownCurrencies = shownCurrencies)
+ } else {
+ val shownCurrencies = tokensState.shownCurrencies
+ .addTokensForBlockchain(action.blockchain, tokensState.currencies)
+ shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
+ tokensState.copy(shownCurrencies = shownCurrencies)
+ }
+ }
else -> tokensState
}
}
private fun List.toCardCurrencies(): CardCurrencies {
- val tokens = mapNotNull { (it.currency as? Currency.Token)?.token }.toSet()
- val blockchains = mapNotNull { (it.currency as? Currency.Blockchain)?.blockchain }.toSet()
+ val tokens = mapNotNull { (it.currency as? Currency.Token)?.token }.distinct()
+ val blockchains = mapNotNull { (it.currency as? Currency.Blockchain)?.blockchain }.distinct()
return CardCurrencies(tokens = tokens, blockchains = blockchains)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
index b3d528fe5f..ef30a92229 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
@@ -2,15 +2,14 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Token
-import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.tokens.CardCurrencies
-import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
import org.rekotlin.StateType
data class TokensState(
val addedTokens: LinkedHashSet = LinkedHashSet(),
val addedCurrencies: CardCurrencies? = null,
val currencies: List = emptyList(),
+ val shownCurrencies: List = emptyList(),
) : StateType
data class TokenWithAmount(
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
index e3d8eb80fb..b5d09b7074 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
@@ -18,7 +18,6 @@ import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_add_tokens.*
-import kotlinx.android.synthetic.main.fragment_details_twin_cards.toolbar
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import org.rekotlin.StoreSubscriber
@@ -74,7 +73,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
override fun newState(state: TokensState) {
if (activity == null) return
viewAdapter.addedCurrencies = state.addedCurrencies
- viewAdapter.submitUnfilteredList(state.currencies)
+ viewAdapter.submitUnfilteredList(state.shownCurrencies)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
index 7767d74b32..85a8462b81 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
@@ -3,16 +3,17 @@ package com.tangem.tap.features.tokens.ui.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.annotation.StringRes
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Token
-import com.tangem.tap.common.extensions.*
-import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.common.extensions.getString
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.extensions.loadCurrenciesIcon
+import com.tangem.tap.common.extensions.show
import com.tangem.tap.domain.tokens.CardCurrencies
+import com.tangem.tap.features.tokens.redux.CurrencyListItem
+import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -66,11 +67,11 @@ class CurrenciesAdapter : ListAdapter
object DiffUtilCallback : DiffUtil.ItemCallback() {
override fun areContentsTheSame(
- oldItem: CurrencyListItem, newItem: CurrencyListItem
+ oldItem: CurrencyListItem, newItem: CurrencyListItem,
) = oldItem == newItem
override fun areItemsTheSame(
- oldItem: CurrencyListItem, newItem: CurrencyListItem
+ oldItem: CurrencyListItem, newItem: CurrencyListItem,
) = oldItem == newItem
}
@@ -84,17 +85,18 @@ class CurrenciesAdapter : ListAdapter
when (element) {
is CurrencyListItem.BlockchainListItem -> {
element.blockchain.currency.toLowerCase(Locale.US)
- .contains(queryNormalized)
- || element.blockchain.fullName.toLowerCase(Locale.US)
- .contains(queryNormalized)
+ .contains(queryNormalized) ||
+ element.blockchain.fullName.toLowerCase(Locale.US)
+ .contains(queryNormalized)
}
is CurrencyListItem.TokenListItem -> {
- element.token.name.toLowerCase(Locale.US).contains(queryNormalized)
- || element.token.symbol.toLowerCase(Locale.US)
- .contains(queryNormalized)
+ element.token.name.toLowerCase(Locale.US)
+ .contains(queryNormalized) ||
+ element.token.symbol.toLowerCase(Locale.US)
+ .contains(queryNormalized)
}
- else -> false
+ is CurrencyListItem.TitleListItem -> true
}
})
} else {
@@ -143,7 +145,7 @@ class CurrenciesAdapter : ListAdapter
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
textView = view.tv_token_letter,
- token = token, blockchain = Blockchain.Ethereum
+ token = token, blockchain = token.blockchain
)
view.btn_add_token.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.AddToken(token))
@@ -158,28 +160,21 @@ class CurrenciesAdapter : ListAdapter
class TitleViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
fun bind(title: CurrencyListItem.TitleListItem) {
- view.tv_subtitle.text = view.getString(title.titleResId).toUpperCase(Locale.US)
- }
- }
-}
-
-
-sealed class CurrencyListItem {
- data class TokenListItem(val token: Token) : CurrencyListItem()
- data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
- data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem()
-
- companion object {
- fun createListOfCurrencies(
- blockchains: List,
- tokens: List
- ): List