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 { - val blockchainsTitle = R.string.add_tokens_subtitle_blockchains - val tokensTitle = R.string.add_tokens_subtitle_tokens - return listOf(TitleListItem(blockchainsTitle)) + - blockchains.map { BlockchainListItem(it) } + - listOf(TitleListItem(tokensTitle)) + - tokens.map { TokenListItem(it) } + view.tv_subtitle.text = view.getString(title.titleResId).uppercase() + + if (title.blockchain != null) { + view.cl_subtitle_container.setOnClickListener { + val rotation = if (title.isContentShown) -90f else 0f + store.dispatch(TokensAction.ToggleShowTokensForBlockchain( + title.isContentShown, title.blockchain + )) + view.iv_toggle_sublist_visibility.animate().rotation(rotation) + } + view.iv_toggle_sublist_visibility.show() + view.iv_toggle_sublist_visibility.setImageResource(R.drawable.ic_arrow_angle_down) + } else { + view.iv_toggle_sublist_visibility.hide() + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt index 0ade915979..a449bb17ea 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt @@ -3,13 +3,15 @@ package com.tangem.tap.features.wallet.models import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionStatus +import com.tangem.blockchain.common.Wallet import com.tangem.tap.common.extensions.toFormattedString +import com.tangem.tap.domain.extensions.toSendableAmounts data class PendingTransaction( - val address: String?, - val amount: String?, - val currency: String, - val type: PendingTransactionType + val address: String?, + val amount: String?, + val currency: String, + val type: PendingTransactionType ) enum class PendingTransactionType { Incoming, Outgoing, Unknown } @@ -35,10 +37,10 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact } return PendingTransaction( - if (address == "unknown") null else address, - this.amount.value?.toFormattedString(amount.decimals), - this.amount.currencySymbol, - type + if (address == "unknown") null else address, + this.amount.value?.toFormattedString(amount.decimals), + this.amount.currencySymbol, + type ) } @@ -57,4 +59,16 @@ fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: St fun List.toPendingTransactionsForToken(token: Token, walletAddress: String): List { return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) } +} + +fun Wallet.getPendingTransactions(): List { + return recentTransactions.toPendingTransactions(address) +} + +fun Wallet.hasPendingTransactions(): Boolean { + return getPendingTransactions().isNotEmpty() +} + +fun Wallet.hasSendableAmountsOrPendingTransactions(): Boolean { + return hasPendingTransactions() || amounts.toSendableAmounts().isNotEmpty() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 4bd89c024b..149550727b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -3,13 +3,13 @@ package com.tangem.tap.features.wallet.redux import android.content.Context import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.AddressType -import com.tangem.commands.common.card.Card +import com.tangem.common.card.Card import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.tokens.CardCurrencies -import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -18,12 +18,18 @@ sealed class WalletAction : Action { object ResetState : WalletAction() + data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction() + object LoadData : WalletAction() { data class Failure(val error: TapError) : WalletAction() } - data class LoadWallet(val allowTopUp: Boolean? = null, val blockchain: Blockchain? = null) : WalletAction() { + data class LoadWallet( + val moonpayStatus: MoonpayStatus? = null, + val blockchain: Blockchain? = null, + ) : + WalletAction() { data class Success(val wallet: Wallet) : WalletAction() data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction() data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction() @@ -37,13 +43,16 @@ sealed class WalletAction : Action { data class AddWalletManagers(val walletManagers: List) : MultiWallet() { constructor(walletManager: WalletManager) : this(listOf(walletManager)) } + data class AddBlockchain(val blockchain: Blockchain) : MultiWallet() data class AddBlockchains(val blockchains: List) : MultiWallet() data class AddTokens(val tokens: List) : MultiWallet() data class AddToken(val token: Token) : MultiWallet() data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet() object FindTokensInUse : MultiWallet() - data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) : MultiWallet() + data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) : + MultiWallet() + data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet() data class SelectWallet(val walletData: WalletData?) : MultiWallet() data class RemoveWallet(val walletData: WalletData) : MultiWallet() @@ -67,6 +76,7 @@ sealed class WalletAction : Action { object SetNeverToShow : Warnings() object RemindLater : Warnings() } + class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings() } @@ -77,12 +87,14 @@ sealed class WalletAction : Action { } data class LoadFiatRate( - val wallet: Wallet? = null, val currency: Currency? = null + val wallet: Wallet? = null, val currency: Currency? = null, ) : WalletAction() { data class Success(val fiatRate: Pair) : WalletAction() object Failure : WalletAction() } + class LoadCardInfo(val card: Card) : WalletAction() + data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() { data class Success(val artwork: Artwork) : WalletAction() object Failure : WalletAction() @@ -110,8 +122,8 @@ sealed class WalletAction : Action { object ShowDialog : WalletAction() { object QrCode : WalletAction() - object ScanFails : WalletAction() object SignedHashesMultiWalletDialog : WalletAction() + object ChooseTradeActionDialog : WalletAction() } object HideDialog : WalletAction() @@ -121,18 +133,18 @@ sealed class WalletAction : Action { object CreateWallet : WalletAction() object EmptyWallet : WalletAction() - sealed class TopUpAction : WalletAction() { - data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction() + sealed class TradeCryptoAction : WalletAction() { + object Buy : TradeCryptoAction() + object Sell : TradeCryptoAction() + data class FinishSelling(val transactionId: String) : TradeCryptoAction() + data class SendCrypto( + val currencyId: String, + val amount: String, + val destinationAddress: String, + val transactionId: String + ) : TradeCryptoAction() } data class ChangeSelectedAddress(val type: AddressType) : WalletAction() - sealed class TwinsAction : WalletAction() { - object ShowOnboarding : TwinsAction() - object SetOnboardingShown : TwinsAction() - data class SetTwinCard( - val number: TwinCardNumber, - val isCreatingTwinCardsAllowed: Boolean, - ) : TwinsAction() - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 5cfe80591f..96044f0e1c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -5,41 +5,57 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.extensions.isAboveZero import com.tangem.tap.common.entities.Button +import com.tangem.tap.common.extensions.toQrCode +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage +import com.tangem.tap.domain.extensions.buyIsAllowed +import com.tangem.tap.domain.extensions.sellIsAllowed import com.tangem.tap.domain.extensions.toSendableAmounts -import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken +import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.tap.store import org.rekotlin.StateType import java.math.BigDecimal +import kotlin.properties.ReadOnlyProperty data class WalletState( - val state: ProgressState = ProgressState.Done, - val error: ErrorType? = null, - val cardImage: Artwork? = null, - val hashesCountVerified: Boolean? = null, - val walletDialog: StateDialog? = null, - val twinCardsState: TwinCardsState? = null, - val mainWarningsList: List = mutableListOf(), - val wallets: List = emptyList(), - val walletManagers: List = emptyList(), - val isMultiwalletAllowed: Boolean = false, - val cardCurrency: CryptoCurrencyName? = null, - val selectedWallet: Currency? = null, - val primaryBlockchain: Blockchain? = null, - val primaryToken: Token? = null + val state: ProgressState = ProgressState.Done, + val error: ErrorType? = null, + val cardImage: Artwork? = null, + val hashesCountVerified: Boolean? = null, + val walletDialog: StateDialog? = null, + val mainWarningsList: List = mutableListOf(), + val wallets: List = emptyList(), + val walletManagers: List = emptyList(), + val isMultiwalletAllowed: Boolean = false, + val cardCurrency: CryptoCurrencyName? = null, + val selectedWallet: Currency? = null, + val primaryBlockchain: Blockchain? = null, + val primaryToken: Token? = null, + val isTestnet: Boolean = false, ) : 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 + val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null val shouldShowDetails: Boolean = - primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard && - primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain + primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && + primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain val blockchains: List get() = walletManagers.map { it.wallet.blockchain } @@ -49,29 +65,19 @@ data class WalletState( fun getWalletManager(token: Token?): WalletManager? { if (token == null) return null - val ethereumWalletManager = walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum } - return if (ethereumWalletManager?.presetTokens?.contains(token) == true) { - ethereumWalletManager - } else { - val primaryWalletManager = walletManagers.find { it.wallet.blockchain == primaryBlockchain } - if (primaryWalletManager?.presetTokens?.contains(token) == true) { - primaryWalletManager - } else { - ethereumWalletManager - } - } + return walletManagers.find { it.wallet.blockchain == token.blockchain } } - fun getWalletManager(currency: Currency?) : WalletManager? { + fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null return walletManagers.find { it.wallet.blockchain == currency.blockchain } } - fun getWalletManager(blockchain: Blockchain) : WalletManager? { + fun getWalletManager(blockchain: Blockchain): WalletManager? { return walletManagers.find { it.wallet.blockchain == blockchain } } - fun getWalletData(currency: Currency?) : WalletData? { + fun getWalletData(currency: Currency?): WalletData? { if (currency == null) return null return wallets.find { it.currency == currency } } @@ -98,7 +104,7 @@ data class WalletState( ?: return true if (walletData.currency is Currency.Blockchain && - walletManager.presetTokens.isNotEmpty() + walletManager.cardTokens.isNotEmpty() ) { return false } @@ -110,9 +116,9 @@ data class WalletState( wallet.amounts.toSendableAmounts().isEmpty() } else if (walletData.currency is Currency.Token) ( return wallet.recentTransactions.toPendingTransactionsForToken( - walletData.currency.token, wallet.address).isEmpty() + walletData.currency.token, wallet.address).isEmpty() && wallet.amounts[AmountType.Token(token = walletData.currency.token)] - ?.isAboveZero() != true + ?.isAboveZero() != true ) } return false @@ -127,12 +133,39 @@ data class WalletState( fun replaceWalletInWallets(walletData: WalletData?): List { if (walletData == null) return wallets - return wallets.filter { it.currencyData.currency != walletData.currencyData.currency } + walletData + var changed = false + val updatedWallets = wallets.map { + if (it.currency == walletData.currency) { + changed = true + walletData + } else { + it + } + } + return if (changed) updatedWallets else wallets + walletData } fun replaceSomeWallets(newWallets: List): List { - val currencies = newWallets.map { it.currencyData.currency } - return wallets.filter { !currencies.contains(it.currencyData.currency) } + newWallets + val remainingWallets: MutableList = newWallets.toMutableList() + val updatedWallets = wallets.map { wallet -> + val newWallet = newWallets + .firstOrNull { wallet.currency == it.currency } + if (newWallet == null) { + wallet + } else { + remainingWallets.remove(newWallet) + newWallet + } + } + return updatedWallets + remainingWallets + } + + fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletData: WalletData): WalletData { + return walletData.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData)) + } + + fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletDataList: List): List { + return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, it)) } } fun addWalletManagers(newWalletManagers: List): WalletState { @@ -142,17 +175,13 @@ data class WalletState( } } -sealed class WalletDialog: StateDialog { - data class QrDialog( - val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName? - ) : WalletDialog() - +sealed class WalletDialog : StateDialog { data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() - object ScanFailsDialog : WalletDialog() object SignedHashesMultiWalletDialog : WalletDialog() + object ChooseTradeActionDialog : WalletDialog() } -enum class ProgressState { Loading, Done, Error } +enum class ProgressState : WidgetState { Loading, Done, Error } enum class ErrorType { NoInternetConnection } @@ -162,21 +191,22 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { } data class WalletAddresses( - val selectedAddress: AddressData, - val list: List + val selectedAddress: AddressData, + val list: List ) data class AddressData( - val address: String, - val type: AddressType, - val shareUrl: String, - val exploreUrl: String, - val qrCode: Bitmap? = null -) + val address: String, + val type: AddressType, + val shareUrl: String, + val exploreUrl: String, +) { + val qrCode: Bitmap by lazy { shareUrl.toQrCode() } +} data class Artwork( - val artworkId: String, - val artwork: Bitmap? = null + val artworkId: String, + val artwork: Bitmap? = null ) { companion object { const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" @@ -186,40 +216,41 @@ data class Artwork( const val MARTA_CARD_ID = "BC02" const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" + + const val TEMP_CARDANO = "https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659" } } -data class TopUpState( - val allowed: Boolean = true, - val url: String? = null, - val redirectUrl: String? = null -) +data class TradeCryptoState( + val sellingAllowed: Boolean = false, + val buyingAllowed: Boolean = false, +) { + companion object { + fun from(moonpayStatus: MoonpayStatus?, walletData: WalletData): TradeCryptoState { + val status = moonpayStatus ?: return walletData.tradeCryptoState + val currency = walletData.currency -data class TwinCardsState( - val cardNumber: TwinCardNumber?, - val showTwinOnboarding: Boolean, - val isCreatingTwinCardsAllowed: Boolean -) + return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency)) + } + } +} data class WalletData( - val pendingTransactions: List = emptyList(), - val hashesCountVerified: Boolean? = null, - val walletAddresses: WalletAddresses? = null, - val currencyData: BalanceWidgetData = BalanceWidgetData(), - val updatingWallet: Boolean = false, - val topUpState: TopUpState = TopUpState(), - val allowToSend: Boolean = true, - val fiatRateString: String? = null, - val fiatRate: BigDecimal? = null, - val mainButton: WalletMainButton = WalletMainButton.SendButton(false), - val currency: Currency? = null + val pendingTransactions: List = emptyList(), + val hashesCountVerified: Boolean? = null, + val walletAddresses: WalletAddresses? = null, + val currencyData: BalanceWidgetData = BalanceWidgetData(), + val updatingWallet: Boolean = false, + val tradeCryptoState: TradeCryptoState = TradeCryptoState(), + val allowToSend: Boolean = true, + val fiatRateString: String? = null, + val fiatRate: BigDecimal? = null, + val mainButton: WalletMainButton = WalletMainButton.SendButton(false), + val currency: Currency ) { fun shouldShowMultipleAddress(): Boolean { val listOfAddresses = walletAddresses?.list ?: return false - return (currency?.blockchain == Blockchain.Bitcoin || - currency?.blockchain == Blockchain.BitcoinTestnet || - currency?.blockchain == Blockchain.CardanoShelley) && - listOfAddresses.size > 1 + return listOfAddresses.size > 1 } } @@ -229,9 +260,9 @@ sealed interface Currency { val currencySymbol: CryptoCurrencyName data class Token( - val token: com.tangem.blockchain.common.Token, - override val blockchain: com.tangem.blockchain.common.Blockchain + val token: com.tangem.blockchain.common.Token ) : Currency { + override val blockchain = token.blockchain override val currencySymbol: CryptoCurrencyName = token.symbol } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 905ecce398..8397f148e0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.withContext class MultiWalletMiddleware { fun handle( - action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?, + action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?, ) { when (action) { is WalletAction.MultiWallet.AddWalletManagers -> { @@ -35,7 +35,7 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.AddToken -> { - globalState?.scanNoteResponse?.card?.cardId?.let { + globalState?.scanResponse?.card?.cardId?.let { currenciesRepository.saveAddedToken(it, action.token) } addToken(action.token, walletState, globalState) @@ -44,7 +44,7 @@ class MultiWalletMiddleware { addTokens(action.tokens, walletState, globalState) } is WalletAction.MultiWallet.AddBlockchain -> { - globalState?.scanNoteResponse?.card?.let { card -> + globalState?.scanResponse?.card?.let { card -> currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain) if (walletState?.blockchains?.contains(action.blockchain) != true) { globalState.tapWalletManager.walletManagerFactory @@ -54,17 +54,26 @@ class MultiWalletMiddleware { } } store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain))) - store.dispatch(WalletAction.LoadWallet(blockchain = action.blockchain)) + store.dispatch(WalletAction.LoadWallet( + moonpayStatus = globalState?.moonpayStatus, + blockchain = action.blockchain + ) + ) } is WalletAction.MultiWallet.SaveCurrencies -> { - val cardId = globalState?.scanNoteResponse?.card?.cardId + val cardId = globalState?.scanResponse?.card?.cardId cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) } } is WalletAction.MultiWallet.RemoveWallet -> { - val cardId = globalState?.scanNoteResponse?.card?.cardId + val cardId = globalState?.scanResponse?.card?.cardId when (val currency = action.walletData.currency) { is Currency.Blockchain -> { - cardId?.let { currenciesRepository.removeBlockchain(it, currency.blockchain) } + cardId?.let { + currenciesRepository.removeBlockchain( + it, + currency.blockchain + ) + } } is Currency.Token -> { walletState?.getWalletManager(currency.token) @@ -74,10 +83,11 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindBlockchainsInUse -> { - val cardFirmware = globalState?.scanNoteResponse?.card?.firmwareVersion + val cardFirmware = globalState?.scanResponse?.card?.firmwareVersion val blockchains = currenciesRepository.getBlockchains(cardFirmware) - .filterNot { walletState?.blockchains?.contains(it) == true } - val walletManagers = action.factory.makeWalletManagersForApp(action.card, blockchains) + .filterNot { walletState?.blockchains?.contains(it) == true } + val walletManagers = + action.factory.makeWalletManagersForApp(action.card, blockchains) scope.launch { walletManagers.map { walletManager -> @@ -89,11 +99,16 @@ class MultiWalletMiddleware { if (coinAmount != null && !coinAmount.isZero()) { scope.launch(Dispatchers.Main) { if (walletState?.getWalletData(wallet.blockchain) == null) { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers( - listOfNotNull(walletManager))) - store.dispatch(WalletAction.MultiWallet.AddBlockchain( - wallet.blockchain - )) + store.dispatch( + WalletAction.MultiWallet.AddWalletManagers( + listOfNotNull(walletManager) + ) + ) + store.dispatch( + WalletAction.MultiWallet.AddBlockchain( + wallet.blockchain + ) + ) store.dispatch(WalletAction.LoadWallet.Success(wallet)) } } @@ -105,12 +120,12 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.FindTokensInUse -> { - val card = globalState?.scanNoteResponse?.card ?: return + val card = globalState?.scanResponse?.card ?: return val walletManager = walletState?.getWalletManager(Blockchain.Ethereum) - ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( - card = card, - blockchain = Blockchain.Ethereum - ) + ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( + card = card, + blockchain = Blockchain.Ethereum + ) val tokenFinder = walletManager as TokenFinder scope.launch { val result = tokenFinder.findTokens() @@ -131,7 +146,7 @@ class MultiWalletMiddleware { ) store.dispatch( WalletAction.MultiWallet.AddTokens( - walletManager.presetTokens.toList() + walletManager.cardTokens.toList() ) ) } @@ -144,23 +159,18 @@ class MultiWalletMiddleware { } private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) { - val card = globalState?.scanNoteResponse?.card ?: return + val card = globalState?.scanResponse?.card ?: return val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( card = card, - blockchain = Blockchain.Ethereum + blockchain = token.blockchain )?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } store.dispatch( - WalletAction.LoadFiatRate( - currency = Currency.Token( - token = token, - blockchain = walletManager?.wallet?.blockchain ?: Blockchain.Ethereum - ) - ) + WalletAction.LoadFiatRate(currency = Currency.Token(token)) ) scope.launch { @@ -179,20 +189,19 @@ class MultiWalletMiddleware { walletState: WalletState?, globalState: GlobalState?, ) { - val card = globalState?.scanNoteResponse?.card ?: return + val card = globalState?.scanResponse?.card ?: return val tokensWithManagers = tokens.map { token -> val walletManager = walletState?.getWalletManager(token) ?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( card = card, - blockchain = Blockchain.Ethereum + blockchain = token.blockchain )?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } - store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token( - token = token, - blockchain = walletManager?.wallet?.blockchain ?: Blockchain.Ethereum - ))) + store.dispatch( + WalletAction.LoadFiatRate(currency = Currency.Token(token)) + ) TokenWithManager(token, walletManager) } scope.launch { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt deleted file mode 100644 index e0706570f7..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.tap.features.wallet.redux.middlewares - -import android.net.Uri -import androidx.browser.customtabs.CustomTabsIntent -import com.tangem.tap.domain.TopUpHelper -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store - -class TopUpMiddleware { - fun handle(action: WalletAction.TopUpAction) { - when (action) { - is WalletAction.TopUpAction.TopUp -> { - val selectedWalletData = store.state.walletState.getSelectedWalletData() - val config = store.state.globalState.configManager?.config ?: return - val addresses = selectedWalletData?.walletAddresses ?: return - if (addresses.list.isEmpty()) return - - val defaultAddress = addresses.list[0].address - val url = TopUpHelper.getUrl( - selectedWalletData.currencyData.currencySymbol!!, - defaultAddress, - config.moonPayApiKey, - config.moonPayApiSecretKey - ) - val customTabsIntent = CustomTabsIntent.Builder() - .setToolbarColor(action.toolbarColor) - .build() - customTabsIntent.launchUrl(action.context, Uri.parse(url)); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt new file mode 100644 index 0000000000..2b2087dd25 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -0,0 +1,94 @@ +package com.tangem.tap.features.wallet.redux.middlewares + +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager +import com.tangem.blockchain.common.AmountType +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.topup.TopUpManager +import com.tangem.tap.domain.topup.TradeCryptoHelper +import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.scope +import com.tangem.tap.store +import kotlinx.coroutines.launch +import timber.log.Timber + + +class TradeCryptoMiddleware { + fun handle(action: WalletAction.TradeCryptoAction) { + when (action) { + is WalletAction.TradeCryptoAction.Buy -> startExchange(action) + is WalletAction.TradeCryptoAction.Sell -> startExchange(action) + is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action) + is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) + } + } + + private fun startExchange(action: WalletAction.TradeCryptoAction) { + val selectedWalletData = store.state.walletState.getSelectedWalletData() + val config = store.state.globalState.configManager?.config ?: return + val addresses = selectedWalletData?.walletAddresses ?: return + if (addresses.list.isEmpty()) return + + val defaultAddress = addresses.list[0].address + val currency = selectedWalletData.currency + val currencySymbol = selectedWalletData.currency?.currencySymbol ?: return + + val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) { + TradeCryptoHelper.Action.Buy + } else { + TradeCryptoHelper.Action.Sell + } + + if (exchangeAction == TradeCryptoHelper.Action.Buy && + currency is Currency.Token && currency.blockchain.isTestnet() + ) { + val walletManager = store.state.walletState.getWalletManager(currency.token) + if (walletManager !is EthereumWalletManager) return + + scope.launch { + TopUpManager().topUpTestErc20Tokens( + walletManager = walletManager, token = currency.token + ) + } + return + } + + val url = TradeCryptoHelper.getUrl( + exchangeAction, + currency?.blockchain, + currencySymbol, + defaultAddress, + config.moonPayApiKey, + config.moonPayApiSecretKey + ) + Timber.d("Moonpay $exchangeAction URL: $url") + store.dispatchOnMain(NavigationAction.OpenUrl(url)) + } + + private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { + val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val walletManager = + store.state.walletState.getWalletManager(selectedWalletData.currency) + store.dispatchOnMain(PrepareSendScreen( + coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin), + coinRate = selectedWalletData.fiatRate, + walletManager = walletManager + )) + store.dispatchOnMain(SendAction.SendSpecificTransaction( + sendAmount = action.amount, + destinationAddress = action.destinationAddress, + transactionId = action.transactionId + )) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send)) + } + + private fun openReceiptUrl(transactionId: String) { + store.dispatchOnMain(NavigationAction.PopBackTo()) + val url = TradeCryptoHelper.getSellCryptoReceiptUrl(transactionId) + store.dispatchOnMain(NavigationAction.OpenUrl(url)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt deleted file mode 100644 index 88ba60d1d1..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.tap.features.wallet.redux.middlewares - -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.store - -class TwinsMiddleware { - fun handle(action: WalletAction.TwinsAction) { - when (action) { - is WalletAction.TwinsAction.SetTwinCard -> { - val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() - if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding) - } - WalletAction.TwinsAction.SetOnboardingShown -> { - preferencesStorage.saveTwinsOnboardingShown() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index f1e3d5c0ae..a33d9dbb41 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -5,193 +5,201 @@ import android.net.Uri import androidx.core.content.ContextCompat import com.tangem.blockchain.common.* import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.isZero +import com.tangem.common.services.Result +import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.* import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler -import com.tangem.tap.common.extensions.copyToClipboard -import com.tangem.tap.common.extensions.shareText +import com.tangem.tap.common.extensions.* +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.extensions.toSendableAmounts -import com.tangem.tap.domain.twins.TwinsHelper -import com.tangem.tap.features.details.redux.DetailsAction -import com.tangem.tap.features.details.redux.twins.CreateTwinWallet +import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.network.NetworkStateChanged -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import org.rekotlin.Action +import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class WalletMiddleware { - private val topUpMiddleware = TopUpMiddleware() - private val twinsMiddleware = TwinsMiddleware() + private val tradeCryptoMiddleware = TradeCryptoMiddleware() private val warningsMiddleware = WarningsMiddleware() private val multiWalletMiddleware = MultiWalletMiddleware() val walletMiddleware: Middleware = { dispatch, state -> { next -> { action -> - val globalState = state()?.globalState - val walletState = state()?.walletState - when (action) { - is WalletAction.TopUpAction -> topUpMiddleware.handle(action) - is WalletAction.TwinsAction -> twinsMiddleware.handle(action) - is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) - is WalletAction.MultiWallet -> - multiWalletMiddleware.handle(action, walletState, globalState) + handleAction(action, dispatch) + next(action) + } + } + } - is WalletAction.LoadWallet -> { - scope.launch { - if (action.blockchain == null) { - walletState?.walletManagers?.map { walletManager -> - globalState?.tapWalletManager?.loadWalletData(walletManager) - } - } else { - val walletManager = walletState?.getWalletManager(action.blockchain) - walletManager?.let { globalState?.tapWalletManager?.loadWalletData(it) } - } - } - } - is WalletAction.LoadWallet.Success -> { - val coinAmount = action.wallet.amounts[AmountType.Coin]?.value - if (coinAmount != null && !coinAmount.isZero()) { - if (walletState?.getWalletData(action.wallet.blockchain) == null) { - store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain)) - store.dispatch(WalletAction.LoadWallet.Success(action.wallet)) - } - } - store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) - warningsMiddleware.tryToShowAppRatingWarning(action.wallet) - } - is WalletAction.LoadFiatRate -> { - scope.launch { - when { - action.wallet != null -> { - globalState?.tapWalletManager?.loadFiatRate( - globalState.appCurrency, action.wallet - ) - } - action.currency != null -> { - globalState?.tapWalletManager?.loadFiatRate( - globalState.appCurrency, action.currency - ) - } - else -> { - globalState?.tapWalletManager?.loadFiatRate( - fiatCurrency = globalState.appCurrency, - currencies = walletState?.wallets?.mapNotNull { it.currency } - ?: emptyList() - ) - } - } - } - } - is WalletAction.CreateWallet -> { - if (walletState?.twinCardsState != null) { - store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning( - globalState?.scanNoteResponse?.card?.cardId?.let { - TwinsHelper.getTwinCardNumber(it) - }, - CreateTwinWallet.CreateWallet - )) - } else { - scope.launch { - val result = tangemSdkManager.createWallet( - globalState?.scanNoteResponse?.card?.cardId - ) - when (result) { - is CompletionResult.Success -> { - val card = globalState?.scanNoteResponse?.card?.let { - result.data.copy( - isPin1Default = it.isPin1Default, - isPin2Default = it.isPin2Default, - ) - } ?: result.data - val scanNoteResponse = - globalState?.scanNoteResponse?.copy(card = card) - scanNoteResponse?.let { - globalState.tapWalletManager.onCardScanned(scanNoteResponse) - } - } - } - } - } - } - is WalletAction.UpdateWallet -> { - if (action.blockchain != null) { - scope.launch { - val walletManager = walletState?.getWalletManager(action.blockchain) - walletManager?.let { globalState?.tapWalletManager?.updateWallet(it) } - } - } else { - scope.launch { - if (walletState?.state == ProgressState.Done) { - walletState.walletManagers.map { walletManager -> - globalState?.tapWalletManager?.updateWallet(walletManager) - } - } - } - } + private fun handleAction(action: Action, dispatch: DispatchFunction) { + val globalState = store.state.globalState + val walletState = store.state.walletState + when (action) { + is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action) + is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) + is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState) + is WalletAction.LoadWallet -> { + scope.launch { + if (action.blockchain == null) { + walletState.walletManagers.map { walletManager -> + async { globalState.tapWalletManager.loadWalletData(walletManager) } + }.awaitAll() + } else { + val walletManager = walletState.getWalletManager(action.blockchain) + walletManager?.let { globalState.tapWalletManager.loadWalletData(it) } } - is WalletAction.Scan -> { - scope.launch { - val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler) - scope.launch(Dispatchers.Main) { - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - when (result) { - is CompletionResult.Success -> { - tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) - globalState?.tapWalletManager?.onCardScanned(result.data, true) - } - } - } + } + } + is WalletAction.LoadWallet.Success -> { + val coinAmount = action.wallet.amounts[AmountType.Coin]?.value + if (coinAmount != null && !coinAmount.isZero()) { + if (walletState.getWalletData(action.wallet.blockchain) == null) { + store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain)) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet)) + } + } + store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) + warningsMiddleware.tryToShowAppRatingWarning(action.wallet) + } + is WalletAction.LoadFiatRate -> { + scope.launch { + when { + action.wallet != null -> { + globalState.tapWalletManager.loadFiatRate( + globalState.appCurrency, action.wallet + ) } - } - is WalletAction.LoadData -> { - scope.launch { - val scanNoteResponse = globalState?.scanNoteResponse - ?: return@launch - if (!walletState?.wallets.isNullOrEmpty()) { - globalState.tapWalletManager.reloadData(scanNoteResponse) - } else { - globalState.tapWalletManager.loadData(scanNoteResponse) - } + action.currency != null -> { + globalState.tapWalletManager.loadFiatRate( + globalState.appCurrency, action.currency + ) } - } - is NetworkStateChanged -> { - globalState?.scanNoteResponse?.let { scanNoteResponse -> - store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) - scope.launch { - globalState.tapWalletManager.loadData(scanNoteResponse) - } - } - } - is WalletAction.CopyAddress -> { - action.context.copyToClipboard(action.address) - store.dispatch(WalletAction.CopyAddress.Success) - } - is WalletAction.ShareAddress -> { - action.context.shareText(action.address) - } - is WalletAction.ExploreAddress -> { - val uri = Uri.parse(action.exploreUrl) - val intent = Intent(Intent.ACTION_VIEW, uri) - ContextCompat.startActivity(action.context, intent, null) - } - is WalletAction.Send -> { - val newAction = prepareSendAction(action.amount, state()?.walletState) - store.dispatch(newAction) - if (newAction is PrepareSendScreen) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + else -> { + globalState.tapWalletManager.loadFiatRate( + fiatCurrency = globalState.appCurrency, + currencies = walletState.wallets.mapNotNull { it.currency } + ) } } } - next(action) + } + is WalletAction.CreateWallet -> { + scope.launch { + val result = tangemSdkManager.createWallet( + globalState.scanResponse?.card?.cardId + ) + when (result) { + is CompletionResult.Success -> { + val scanNoteResponse = globalState.scanResponse?.copy(card = result.data) + scanNoteResponse?.let { store.onCardScanned(scanNoteResponse, false) } + } + is CompletionResult.Failure -> { + (result.error as? TangemSdkError)?.let { error -> + FirebaseAnalyticsHandler.logCardSdkError( + error, + FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + card = store.state.detailsState.scanResponse?.card + ) + } + } + } + } + } + is WalletAction.UpdateWallet -> { + if (action.blockchain != null) { + scope.launch { + val walletManager = walletState.getWalletManager(action.blockchain) + walletManager?.let { globalState.tapWalletManager.updateWallet(it) } + } + } else { + scope.launch { + if (walletState.state == ProgressState.Done) { + walletState.walletManagers.map { walletManager -> + globalState.tapWalletManager.updateWallet(walletManager) + } + } + } + } + } + is WalletAction.Scan -> { + store.dispatch(HomeAction.ShouldScanCardOnResume(true)) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + is WalletAction.LoadCardInfo -> { + scope.launch { + val response = OnlineCardVerifier().getCardInfo( + action.card.cardId, action.card.cardPublicKey + ) + when (response) { + is Result.Success -> { + val actionList = listOf( + WalletAction.SetArtworkId(response.data.artwork?.id), + WalletAction.LoadArtwork(action.card, response.data.artwork?.id), + GlobalAction.SetIfCardVerifiedOnline(response.data.passed) + ) + withMainContext { actionList.forEach { store.dispatch(it) } } + } + is Result.Failure -> { + store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(false)) + } + } + store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded) + } + } + is WalletAction.LoadData -> { + scope.launch { + val scanNoteResponse = globalState.scanResponse ?: return@launch + if (!walletState.wallets.isEmpty()) { + globalState.tapWalletManager.reloadData(scanNoteResponse) + } else { + globalState.tapWalletManager.loadData(scanNoteResponse) + } + } + } + is NetworkStateChanged -> { + globalState.scanResponse?.let { scanNoteResponse -> + store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) + scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } + } + } + is WalletAction.CopyAddress -> { + action.context.copyToClipboard(action.address) + store.dispatch(WalletAction.CopyAddress.Success) + } + is WalletAction.ShareAddress -> { + action.context.shareText(action.address) + } + is WalletAction.ExploreAddress -> { + val uri = Uri.parse(action.exploreUrl) + val intent = Intent(Intent.ACTION_VIEW, uri) + ContextCompat.startActivity(action.context, intent, null) + } + is WalletAction.Send -> { + val newAction = prepareSendAction(action.amount, store.state.walletState) + store.dispatch(newAction) + if (newAction is PrepareSendScreen) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + } + } + is WalletAction.ShowDialog.QrCode -> { + val selectedWalletData = walletState.getWalletData(walletState.selectedWallet) ?: return + val selectedAddressData = selectedWalletData.walletAddresses?.selectedAddress ?: return + + val currency = selectedWalletData.currency + store.dispatchDialogShow(AppDialog.AddressInfoDialog(currency, selectedAddressData)) } } } @@ -214,12 +222,12 @@ class WalletMiddleware { when (currency) { is Currency.Blockchain -> { val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.Send.ChooseCurrency(amounts) + ?: return WalletAction.Send.ChooseCurrency(amounts) PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager) } is Currency.Token -> { val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.Send.ChooseCurrency(amounts) + ?: return WalletAction.Send.ChooseCurrency(amounts) prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager) } } @@ -235,8 +243,8 @@ class WalletMiddleware { } private fun prepareSendActionForToken( - amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?, - walletManager: WalletManager? + amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?, + walletManager: WalletManager? ): PrepareSendScreen { val coinRate = state?.getWalletData(wallet?.blockchain)?.fiatRate val tokenRate = if (state?.isMultiwalletAllowed == true) { @@ -245,7 +253,7 @@ class WalletMiddleware { selectedWalletData?.currencyData?.token?.fiatRate } return PrepareSendScreen( - wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager, - amount, tokenRate) + wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager, + amount, tokenRate) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 0ff512dc45..7dacae94b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -4,21 +4,20 @@ import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.SignatureCountValidator import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.commands.common.card.Card -import com.tangem.commands.common.card.CardType -import com.tangem.commands.verification.VerifyCardState -import com.tangem.common.extensions.getType +import com.tangem.common.card.Card +import com.tangem.common.card.FirmwareVersion import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.isGreaterThan import com.tangem.tap.common.redux.global.GlobalState +import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.extensions.hasSignedHashes import com.tangem.tap.domain.extensions.remainingSignatures import com.tangem.tap.domain.isMultiwalletAllowed -import com.tangem.tap.domain.twins.isTwinCard +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.preferencesStorage @@ -41,8 +40,8 @@ class WarningsMiddleware { } is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline() is WalletAction.Warnings.CheckHashesCount.SaveCardId -> { - val cardId = globalState?.scanNoteResponse?.card?.cardId - cardId?.let { preferencesStorage.saveScannedCardId(it) } + val cardId = globalState?.scanResponse?.card?.cardId + cardId?.let { preferencesStorage.usedCardsPrefStorage.scanned(it) } } is WalletAction.Warnings.AppRating.RemindLater -> { preferencesStorage.appRatingLaunchObserver.applyDelayedShowing() @@ -52,12 +51,12 @@ class WarningsMiddleware { } is WalletAction.Warnings.CheckRemainingSignatures -> { if (action.remainingSignatures != null && - action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { store.state.globalState.warningManager - ?.removeWarnings( - messageRes = R.string.warning_low_signatures_format - ) + ?.removeWarnings( + messageRes = R.string.warning_low_signatures_format + ) addWarningMessage( warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), @@ -82,18 +81,22 @@ class WarningsMiddleware { } private fun showCardWarningsIfNeeded(globalState: GlobalState?) { - globalState?.scanNoteResponse?.card?.let { card -> + globalState?.scanResponse?.let { scanResponse -> + val card = scanResponse.card globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local) - showWarningLowRemainingSignaturesIfNeeded(card) - if (card.getType() != CardType.Release) { - addWarningMessage(WarningMessagesManager.devCardWarning()) - } else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) { - checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) } + if (card.isTestCard) { + addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true) + return@let } - if (card.getType() == CardType.Release) { - if (globalState.scanNoteResponse.verifyResponse?.verificationState == - VerifyCardState.VerifiedOffline - ) { + + showWarningLowRemainingSignaturesIfNeeded(card) + if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) { + addWarningMessage(WarningMessagesManager.devCardWarning()) + } else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) { + checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) } + } + if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) { + if (!globalState.cardVerifiedOnline) { addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } } @@ -104,7 +107,7 @@ class WarningsMiddleware { private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) { val remainingSignatures = card.remainingSignatures if (remainingSignatures != null && - remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { addWarningMessage( WarningMessagesManager.remainingSignaturesNotEnough( @@ -115,12 +118,12 @@ class WarningsMiddleware { } private fun checkIfWarningNeeded( - card: Card, + scanResponse: ScanResponse, ): WarningMessage? { - if (card.isTwinCard()) return null + if (scanResponse.isTangemTwins()) return null - if (card.isMultiwalletAllowed) { - return if (card.hasSignedHashes()) { + if (scanResponse.card.isMultiwalletAllowed) { + return if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.signedHashesMultiWalletWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -131,7 +134,7 @@ class WarningsMiddleware { val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator return if (validator == null) { - if (card.hasSignedHashes()) { + if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -147,15 +150,16 @@ class WarningsMiddleware { if (store.state.walletState.hashesCountVerified != false) return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return - val card = store.state.globalState.scanNoteResponse?.card - if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return + val scanResponse = store.state.globalState.scanResponse + val card = scanResponse?.card + if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return - if (card.isTwinCard() || card.isMultiwalletAllowed) return + if (scanResponse.isTangemTwins() || card.isMultiwalletAllowed) return val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator scope.launch { - val signedHashes = card.getSingleWallet()?.signedHashes ?: 0 + val signedHashes = card.getSingleWallet()?.totalSignedHashes ?: 0 val result = validator?.validateSignatureCount(signedHashes) withContext(Dispatchers.Main) { when (result) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 2f90f29e0f..e0f1fec69b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.toFiatString @@ -38,8 +37,7 @@ class MultiWalletReducer { ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState(allowed = false), - currency = Currency.Blockchain(blockchain) + currency = Currency.Blockchain(blockchain), ) } @@ -63,8 +61,7 @@ class MultiWalletReducer { ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState(allowed = false), - currency = Currency.Blockchain(action.blockchain) + currency = Currency.Blockchain(action.blockchain), ) val newState = state.copy(wallets = state.replaceWalletInWallets(walletData)) if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) { @@ -108,7 +105,8 @@ class MultiWalletReducer { } ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + currency = Currency.Token(action.token) ) val wallets = state.replaceWalletInWallets(newTokenWalletData) state.copy(wallets = wallets) @@ -165,10 +163,6 @@ fun Token.toWallet(state: WalletState): WalletData? { ), walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState(allowed = false), - currency = Currency.Token( - token = this, - blockchain = walletManager?.blockchain ?: Blockchain.Ethereum - ) + currency = Currency.Token(this), ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 145bcbcbc2..1cad2a84c4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -3,14 +3,14 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Wallet import com.tangem.common.extensions.isZero -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.domain.getFirstToken import com.tangem.tap.features.wallet.models.removeUnknownTransactions import com.tangem.tap.features.wallet.models.toPendingTransactions -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -19,39 +19,39 @@ import java.math.RoundingMode class OnWalletLoadedReducer { - fun reduce(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null): WalletState { + fun reduce(wallet: Wallet, walletState: WalletState): WalletState { return if (!walletState.isMultiwalletAllowed) { - onSingleWalletLoaded(wallet, walletState, topUpAllowed) + onSingleWalletLoaded(wallet, walletState) } else { - onMultiWalletLoaded(wallet, walletState, topUpAllowed) + onMultiWalletLoaded(wallet, walletState) } } - private fun onMultiWalletLoaded( - wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null - ): WalletState { + private fun onMultiWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { val fiatCurrencySymbol = store.state.globalState.appCurrency - val amount = wallet.amounts[AmountType.Coin]?.value + val moonpayStatus = store.state.globalState.moonpayStatus + + val coinAmountValue = wallet.amounts[AmountType.Coin]?.value if (walletState.getWalletData(wallet.blockchain) == null) { return walletState } - val formattedAmount = amount?.toFormattedCurrencyString( + val formattedAmount = coinAmountValue?.toFormattedCurrencyString( wallet.blockchain.decimals(), wallet.blockchain.currency) val pendingTransactions = wallet.recentTransactions .toPendingTransactions(wallet.address) - val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty() + val coinSendButton = coinAmountValue?.isZero() == false && pendingTransactions.isEmpty() val balanceStatus = if (pendingTransactions.isNotEmpty()) { BalanceStatus.TransactionInProgress } else { BalanceStatus.VerifiedOnline } val walletData = walletState.getWalletData(wallet.blockchain) - ?: WalletData() - val fiatAmount = walletData.fiatRate?.let { amount?.toFiatValue(it) } - val newWalletData = walletData.copy( + + val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) } + val newWalletData = walletData?.copy( currencyData = walletData.currencyData.copy( status = balanceStatus, currency = wallet.blockchain.fullName, currencySymbol = wallet.blockchain.currency, @@ -60,7 +60,9 @@ class OnWalletLoadedReducer { fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(coinSendButton), + currency = Currency.Blockchain(wallet.blockchain), + tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData) ) val tokens = wallet.getTokens().mapNotNull { token -> @@ -71,23 +73,24 @@ class OnWalletLoadedReducer { pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress else -> BalanceStatus.VerifiedOnline } - val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> - wallet.getTokenAmount(token)?.value?.toFiatValue(rate) - } + val tokenAmountValue = wallet.getTokenAmount(token)?.value + val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate)} + + val tokenSendButton = tokenAmountValue?.isZero() == false && tokenPendingTransactions.isEmpty() tokenWalletData?.copy( currencyData = tokenWalletData.currencyData.copy( status = tokenBalanceStatus, - amount = wallet.getTokenAmount(token)?.value?.toFormattedCurrencyString( - token.decimals, token.symbol - ), + amount = tokenAmountValue?.toFormattedCurrencyString(token.decimals, token.symbol), fiatAmount = tokenFiatAmount, fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) ), pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(tokenSendButton), + tradeCryptoState = TradeCryptoState.from(moonpayStatus, tokenWalletData) ) } - val wallets = walletState.replaceSomeWallets((tokens + newWalletData)) + val newWallets = (tokens + newWalletData).mapNotNull { it } + val wallets = walletState.replaceSomeWallets((newWallets)) val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading @@ -99,12 +102,12 @@ class OnWalletLoadedReducer { ) } - private fun onSingleWalletLoaded( - wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null - ): WalletState { + private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { if (wallet.blockchain != walletState.primaryBlockchain) return walletState val fiatCurrencySymbol = store.state.globalState.appCurrency + val moonpayStatus = store.state.globalState.moonpayStatus + val token = wallet.getFirstToken() val tokenData = if (token != null) { val tokenAmount = wallet.getTokenAmount(token) @@ -150,7 +153,8 @@ class OnWalletLoadedReducer { fiatAmount = fiatAmountRaw ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletState.primaryWallet) ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() return walletState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt deleted file mode 100644 index ad83caca20..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.tap.features.wallet.redux.reducers - -import com.tangem.tap.features.wallet.redux.TwinCardsState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletState - -class TwinsReducer { - fun reduce(action: WalletAction.TwinsAction, state: WalletState): WalletState { - return when (action) { - is WalletAction.TwinsAction.SetTwinCard -> { - state.copy( - twinCardsState = TwinCardsState( - cardNumber = action.number, - showTwinOnboarding = state.twinCardsState?.showTwinOnboarding - ?: false, - isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed - ) - ) - } - is WalletAction.TwinsAction.ShowOnboarding -> { - state.copy( - twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = true) - ?: TwinCardsState(cardNumber = null, - showTwinOnboarding = true, - isCreatingTwinCardsAllowed = false) - ) - } - is WalletAction.TwinsAction.SetOnboardingShown -> { - state.copy( - twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = false) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 5daef27db2..097fbf28e3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -1,18 +1,22 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.commands.common.network.TangemService -import com.tangem.common.extensions.toHexString -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.store import org.rekotlin.Action import java.math.BigDecimal import java.math.RoundingMode @@ -26,53 +30,57 @@ class WalletReducer { private fun internalReduce(action: Action, state: AppState): WalletState { val multiWalletReducer = MultiWalletReducer() - val twinsReducer = TwinsReducer() val onWalletLoadedReducer = OnWalletLoadedReducer() if (action !is WalletAction) return state.walletState + val moonpayStatus = store.state.globalState.moonpayStatus var newState = state.walletState when (action) { is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState) - is WalletAction.TwinsAction -> newState = twinsReducer.reduce(action, newState) is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState) is WalletAction.ResetState -> newState = WalletState() + is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet) is WalletAction.EmptyWallet -> { - val creatingWalletAllowed = !(newState.twinCardsState != null && - newState.twinCardsState?.isCreatingTwinCardsAllowed != true) - newState = newState.copy( - state = ProgressState.Done, - wallets = listOf(WalletData( - currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), - mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed), - topUpState = TopUpState(false) - ))) + state = ProgressState.Done, + wallets = listOf( + WalletData( + currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), + mainButton = WalletMainButton.CreateWalletButton(true), + currency = Currency.Blockchain(Blockchain.Unknown) + ) + ) + ) } is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { val wallets = newState.wallets - .map { - it.copy(currencyData = it.currencyData.copy( - status = BalanceStatus.Unreachable - )) - } + .map { + it.copy( + currencyData = it.currencyData.copy( + status = BalanceStatus.Unreachable + ) + ) + } newState = newState.copy( - state = ProgressState.Error, - error = ErrorType.NoInternetConnection, - wallets = wallets + state = ProgressState.Error, + error = ErrorType.NoInternetConnection, + wallets = wallets ) } is TapError.UnknownBlockchain -> { newState = newState.copy( - state = ProgressState.Done, - wallets = listOf(WalletData( - currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain), - topUpState = TopUpState(false) - )) + state = ProgressState.Done, + wallets = listOf( + WalletData( + currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain), + currency = Currency.Blockchain(Blockchain.Unknown) + ) + ) ) } } @@ -80,73 +88,72 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.LoadData -> { newState = newState.copy( - state = ProgressState.Loading, - error = null, + state = ProgressState.Loading, + error = null, ) } is WalletAction.LoadWallet -> { if (action.blockchain == null) { val wallets = newState.wallets.map { wallet -> + wallet.copy( - currencyData = wallet.currencyData.copy( - status = BalanceStatus.Loading, - currency = wallet.currencyData.currency, - currencySymbol = wallet.currencyData.currencySymbol, - ), - mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState(allowed = action.allowTopUp - ?: wallet.topUpState.allowed) + currencyData = wallet.currencyData.copy( + status = BalanceStatus.Loading, + currency = wallet.currencyData.currency, + currencySymbol = wallet.currencyData.currencySymbol, + ), + mainButton = WalletMainButton.SendButton(false), + tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet) ) } newState = newState.copy( - state = ProgressState.Loading, - wallets = wallets + state = ProgressState.Loading, + wallets = wallets, ) } else { val walletManager = newState.getWalletManager(action.blockchain) ?: return newState val blockchain = walletManager.wallet.blockchain - val currencies = listOf(Currency.Blockchain(blockchain)) + walletManager.presetTokens.map { - Currency.Token(token = it, blockchain = blockchain) - } + val currencies = listOf(Currency.Blockchain(blockchain)) + + walletManager.cardTokens.map { Currency.Token(it) } val newWallets = newState.wallets.filter { currencies.contains(it.currency) } - .map { wallet -> - wallet.copy( - currencyData = wallet.currencyData.copy( - status = BalanceStatus.Loading, - currency = wallet.currencyData.currency, - currencySymbol = wallet.currencyData.currencySymbol, - ), - mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState( - allowed = action.allowTopUp ?: wallet.topUpState.allowed - ) - ) - } + .map { wallet -> + wallet.copy( + currencyData = wallet.currencyData.copy( + status = BalanceStatus.Loading, + currency = wallet.currencyData.currency, + currencySymbol = wallet.currencyData.currencySymbol, + ), + mainButton = WalletMainButton.SendButton(false), + tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet) + ) + } val wallets = newState.replaceSomeWallets(newWallets) - newState = newState.copy(wallets = wallets) + newState = newState.copy(wallets = newState.updateTradeCryptoState(moonpayStatus, wallets)) } } - is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState) + is WalletAction.LoadWallet.Success -> newState = + onWalletLoadedReducer.reduce(action.wallet, newState) is WalletAction.UpdateWallet.Success -> { newState = onWalletLoadedReducer.reduce(action.wallet, newState) } is WalletAction.LoadWallet.NoAccount -> { val walletData = newState.getWalletData(action.wallet.blockchain)?.copy( - currencyData = BalanceWidgetData( - BalanceStatus.NoAccount, action.wallet.blockchain.fullName, - currencySymbol = action.wallet.blockchain.currency, - amountToCreateAccount = action.amountToCreateAccount - ) + currencyData = BalanceWidgetData( + BalanceStatus.NoAccount, action.wallet.blockchain.fullName, + currencySymbol = action.wallet.blockchain.currency, + amountToCreateAccount = action.amountToCreateAccount + ) ) val wallets = newState.replaceWalletInWallets(walletData) - val progressState = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { - ProgressState.Loading - } else { - ProgressState.Done - } + val progressState = + if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { + ProgressState.Loading + } else { + ProgressState.Done + } newState = newState.copy( - state = progressState, - wallets = wallets + state = progressState, + wallets = newState.updateTradeCryptoState(moonpayStatus, wallets) ) } is WalletAction.LoadWallet.Failure -> { @@ -157,28 +164,31 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } val walletData = newState.getWalletData(action.wallet.blockchain) val newWalletData = walletData?.copy( - currencyData = walletData.currencyData.copy( - status = BalanceStatus.Unreachable, - errorMessage = message - ), - topUpState = TopUpState(false) + currencyData = walletData.currencyData.copy( + status = BalanceStatus.Unreachable, + errorMessage = message + ), ) val tokenWallets = action.wallet.getTokens() - .mapNotNull { newState.getWalletData(it) } - .map { - it.copy(currencyData = it.currencyData.copy( - status = BalanceStatus.Unreachable, errorMessage = message - )) - } + .mapNotNull { newState.getWalletData(it) } + .map { + it.copy( + currencyData = it.currencyData.copy( + status = BalanceStatus.Unreachable, errorMessage = message + ) + ) + } val wallets = newState.replaceSomeWallets(listOfNotNull(newWalletData) + tokenWallets) - val progressState = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { - ProgressState.Loading - } else { - ProgressState.Done - } + val progressState = + if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { + ProgressState.Loading + } else { + ProgressState.Done + } newState = newState.copy( - state = progressState, wallets = wallets + state = progressState, + wallets = newState.updateTradeCryptoState(moonpayStatus, wallets) ) } is WalletAction.SetArtworkId -> { @@ -193,60 +203,45 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.LoadFiatRate.Success -> newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState) is WalletAction.LoadArtwork -> { - val cardId = action.card.cardId - val cardPublicKey = action.card.cardPublicKey?.toHexString() - val artworkUrl = if (cardPublicKey != null && action.artworkId != null) { - TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId) - } else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) { - Artwork.SERGIO_CARD_URL - } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) { - Artwork.MARTA_CARD_URL - } else if (newState.twinCardsState?.cardNumber != null) { - when (newState.twinCardsState?.cardNumber) { + val artworkUrl = action.card.getArtworkUrl(action.artworkId) + ?: when (state.twinCardsState.cardNumber) { TwinCardNumber.First -> Artwork.TWIN_CARD_1 TwinCardNumber.Second -> Artwork.TWIN_CARD_2 - null -> Artwork.DEFAULT_IMG_URL + else -> Artwork.DEFAULT_IMG_URL } - } else { - Artwork.DEFAULT_IMG_URL - } newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl)) } - is WalletAction.ShowDialog.QrCode -> { - val selectedWalletData = newState.getWalletData(newState.selectedWallet) - newState = newState.copy( - walletDialog = WalletDialog.QrDialog( - selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(), - selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl, - selectedWalletData?.currencyData?.currency - ) - ) - } - is WalletAction.ShowDialog.ScanFails -> { - newState = newState.copy(walletDialog = WalletDialog.ScanFailsDialog) - } is WalletAction.ShowDialog.SignedHashesMultiWalletDialog -> { newState = newState.copy(walletDialog = WalletDialog.SignedHashesMultiWalletDialog) } + is WalletAction.ShowDialog.ChooseTradeActionDialog -> { + newState = newState.copy(walletDialog = WalletDialog.ChooseTradeActionDialog) + } is WalletAction.HideDialog -> { newState = newState.copy(walletDialog = null) } is WalletAction.Send.ChooseCurrency -> { newState = newState.copy( - walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts) + walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts) ) } is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null) - is WalletAction.TopUpAction -> return newState + is WalletAction.TradeCryptoAction -> return newState is WalletAction.ChangeSelectedAddress -> { val selectedWalletData = newState.getWalletData(newState.selectedWallet) - val walletAddresses = newState.getWalletData(selectedWalletData?.currency)?.walletAddresses + val walletAddresses = + newState.getWalletData(selectedWalletData?.currency)?.walletAddresses ?: return newState val address = walletAddresses.list.firstOrNull { it.type == action.type } - ?: return newState + ?: return newState val wallets = newState.replaceWalletInWallets( - selectedWalletData?.copy(walletAddresses = WalletAddresses(address, walletAddresses.list)) + selectedWalletData?.copy( + walletAddresses = WalletAddresses( + address, + walletAddresses.list + ) + ) ) newState = newState.copy(wallets = wallets) } @@ -257,30 +252,45 @@ private fun internalReduce(action: Action, state: AppState): WalletState { fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { if (wallet == null) return null - val listOfAddressData = mutableListOf() - // put a defaultAddress at the first place - wallet.addresses.forEach { - val addressData = AddressData(it.value, it.type, wallet.getShareUri(it.value), wallet.getExploreUrl(it.value)) - if (it.type == wallet.blockchain.defaultAddressType()) { - listOfAddressData.add(0, addressData) - } else { - listOfAddressData.add(addressData) - } - } - + val listOfAddressData = wallet.createAddressesData() // restore a selected wallet address var indexOfSelectedWallet = 0 walletAddresses?.let { - val index = listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } + val index = + listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } if (index != -1) indexOfSelectedWallet = index } return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData) } -private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: WalletState): WalletState { +fun Wallet.createAddressesData(): List { + val listOfAddressData = mutableListOf() + // put a defaultAddress at the first place + addresses.forEach { + val addressData = AddressData( + it.value, + it.type, + getShareUri(it.value), + getExploreUrl(it.value) + ) + if (it.type == blockchain.defaultAddressType()) { + listOfAddressData.add(0, addressData) + } else { + listOfAddressData.add(addressData) + } + } + return listOfAddressData +} + +private fun handleCheckSignedHashesActions( + action: WalletAction.Warnings, + state: WalletState +): WalletState { return when (action) { WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount -> state.copy(hashesCountVerified = true) - WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline -> state.copy(hashesCountVerified = false) + WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline -> state.copy( + hashesCountVerified = false + ) is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList) else -> state } @@ -288,8 +298,8 @@ private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: private fun setNewFiatRate( - fiatRate: Pair, - appCurrency: FiatCurrencyName, state: WalletState + fiatRate: Pair, + appCurrency: FiatCurrencyName, state: WalletState ): WalletState { val rate = fiatRate.second ?: return state val rateFormatted = rate.toFormattedCurrencyString(2, appCurrency, RoundingMode.HALF_UP) @@ -326,33 +336,34 @@ private fun setMultiWalletFiatRate( } private fun setSingeWalletFiatRate( - rate: BigDecimal, rateFormatted: String, currency: Currency, - appCurrency: FiatCurrencyName, state: WalletState + rate: BigDecimal, rateFormatted: String, currency: Currency, + appCurrency: FiatCurrencyName, state: WalletState ): WalletState { val wallet = state.walletManagers[0].wallet val token = wallet.getFirstToken() if (currency == state.primaryWallet?.currency) { val fiatAmount = wallet.amounts[AmountType.Coin]?.value - ?.toFiatString(rate, appCurrency) + ?.toFiatString(rate, appCurrency) val walletData = state.primaryWallet.copy( - currencyData = state.primaryWallet.currencyData.copy(fiatAmountFormatted = fiatAmount), - fiatRate = rate, - fiatRateString = rateFormatted + currencyData = state.primaryWallet.currencyData.copy(fiatAmountFormatted = fiatAmount), + fiatRate = rate, + fiatRateString = rateFormatted ) return state.copy(wallets = listOf(walletData)) } else if (currency is Currency.Token && currency.token == token) { val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency) val tokenData = state.primaryWallet?.currencyData?.token?.copy( - fiatAmount = tokenFiatAmount, - fiatRate = rate, - fiatRateString = rateFormatted + fiatAmount = tokenFiatAmount, + fiatRate = rate, + fiatRateString = rateFormatted ) val walletData = state.primaryWallet?.copy( - currencyData = state.primaryWallet.currencyData.copy( - token = tokenData - )) + currencyData = state.primaryWallet.currencyData.copy( + token = tokenData + ) + ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() return state.copy(wallets = wallets) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt index 66425c74a7..ecbad2650a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt @@ -23,14 +23,14 @@ class MultipleAddressUiHelper { return when (id) { R.id.chip_default -> { when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Segwit + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit Blockchain.CardanoShelley -> CardanoAddressType.Shelley else -> null } } R.id.chip_legacy -> { when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Legacy + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy Blockchain.CardanoShelley -> CardanoAddressType.Byron else -> null } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 16f8f2b17b..41a3dc9258 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -12,17 +12,16 @@ import androidx.transition.TransitionInflater import com.squareup.picasso.Picasso import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog import com.tangem.tap.store import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_details_twin_cards.* import kotlinx.android.synthetic.main.fragment_wallet_details.* -import kotlinx.android.synthetic.main.fragment_wallet_details.toolbar import kotlinx.android.synthetic.main.item_currency_wallet.view.* import kotlinx.android.synthetic.main.item_popular_token.view.* import kotlinx.android.synthetic.main.layout_balance_error.* @@ -86,11 +85,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } - btn_top_up.setOnClickListener { - store.dispatch( - WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray) - ) - } + btn_trade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) } + + btn_sell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) } } override fun newState(state: WalletState) { @@ -99,6 +96,14 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS val selectedWallet = state.getSelectedWalletData() ?: return tv_currency_title.text = selectedWallet.currencyData.currency + val currency = selectedWallet.currency + if (currency is Currency.Token) { + tv_currency_subtitle.text = currency.blockchain.tokenDisplayName() + tv_currency_subtitle.show() + } else { + tv_currency_subtitle.hide() + } + showPendingTransactionsIfPresent(selectedWallet.pendingTransactions) setupAddressCard(selectedWallet) @@ -123,8 +128,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS srl_wallet_details.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { store.dispatch(WalletAction.LoadWallet( - selectedWallet.topUpState.allowed, - selectedWallet.currency?.blockchain + blockchain = selectedWallet.currency?.blockchain )) } } @@ -132,13 +136,16 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS if (selectedWallet.currencyData.status != BalanceStatus.Loading) { srl_wallet_details.isRefreshing = false } + + btn_trade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed + btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed) } private fun handleCurrencyIcon(wallet: WalletData) { Picasso.get().loadCurrenciesIcon( imageView = iv_currency, textView = tv_token_letter, - blockchain = wallet.currency?.blockchain, + blockchain = wallet.currency.blockchain, token = (wallet.currency as? Currency.Token)?.token ) } @@ -170,10 +177,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS tv_address.text = state.walletAddresses.selectedAddress.address tv_explore?.setOnClickListener { store.dispatch(WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, - requireContext())) + state.walletAddresses.selectedAddress.exploreUrl, + requireContext())) } iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) + tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, state.currency) } } @@ -182,8 +190,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS if (state.error == ErrorType.NoInternetConnection) { srl_wallet_details?.isRefreshing = false (activity as? SnackbarHandler)?.showSnackbar( - text = R.string.wallet_notification_no_internet, - buttonTitle = R.string.common_retry + text = R.string.wallet_notification_no_internet, + buttonTitle = R.string.common_retry ) { store.dispatch(WalletAction.LoadData) } } } else { @@ -239,8 +247,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS tv_error_title.text = getText(R.string.wallet_error_no_account) tv_error_descriptions.text = getString( - R.string.wallet_error_no_account_subtitle_format, - data.amountToCreateAccount, data.currencySymbol + R.string.wallet_error_no_account_subtitle_format, + data.amountToCreateAccount, data.currencySymbol ) } } @@ -278,7 +286,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.meny_remove -> { + R.id.menu_remove -> { store.state.walletState.getSelectedWalletData()?.let { walletData -> store.dispatch(WalletAction.MultiWallet.RemoveWallet(walletData)) store.dispatch(WalletAction.MultiWallet.SelectWallet(null)) @@ -292,9 +300,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - if (store.state.walletState.canBeRemoved(store.state.walletState.getSelectedWalletData())) { - inflater.inflate(R.menu.wallet_details, menu) - } + inflater.inflate(R.menu.wallet_details, menu) + val walletCanBeRemoved = store.state.walletState.canBeRemoved( + store.state.walletState.getSelectedWalletData() + ) + menu.getItem(0).isEnabled = walletCanBeRemoved } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 98c2b0d6fe..f7978e35d8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -138,8 +138,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers)) - store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> + store.state.globalState.scanResponse?.let { scanNoteResponse -> store.dispatch(DetailsAction.PrepareScreen( - scanNoteResponse.card, scanNoteResponse, - store.state.walletState.walletManagers.map { it.wallet }, - store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed, - CardTou(), - store.state.globalState.appCurrency + scanNoteResponse, + store.state.walletState.walletManagers.map { it.wallet }, + CardTou(), + store.state.globalState.appCurrency )) store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) true diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index 62e9849d04..4d96d05004 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -18,7 +18,6 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.item_currency_wallet.view.* -import java.math.BigDecimal class WalletAdapter : ListAdapter(DiffUtilCallback) { @@ -28,36 +27,8 @@ class WalletAdapter } fun submitList(list: List, primaryBlockchain: Blockchain?, primaryToken: Token? = null) { - val listModified = list.toMutableList() - val primaryBlockchainWallet = when ( - val index = listModified.indexOfFirst { - (it.currency as? Currency.Blockchain)?.blockchain == primaryBlockchain - } - ) { - -1 -> null - else -> listModified.removeAt(index) - } - - - val primaryTokenWallet = if (primaryToken == null) null else when ( - val index = listModified.indexOfFirst { (it.currency as? Currency.Token)?.token == primaryToken } - ) { - -1 -> null - else -> listModified.removeAt(index) - } - - if (list.all { it.currencyData.fiatAmountFormatted == null }) { - val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + listModified - super.submitList(sortedList) - return - } - - val sorted = listModified.sortedWith( - compareByDescending { it.currencyData.fiatAmount ?: BigDecimal.ZERO } - .thenBy { it.currencyData.currency } - ) - val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + sorted - super.submitList(sortedList) + // We used this method to sort the list of currencies. Sorting is disabled for now. + super.submitList(list) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder { @@ -92,13 +63,13 @@ class WalletAdapter view.card_wallet.setOnClickListener { store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) } - val blockchain = wallet.currency?.blockchain + val blockchain = wallet.currency.blockchain val token = (wallet.currency as? Currency.Token)?.token Picasso.get().loadCurrenciesIcon( imageView = view.iv_currency, textView = view.tv_token_letter, - token = token, blockchain = blockchain + token = token, blockchain = blockchain, ) when (wallet.currencyData.status) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index c262bbb35c..2b793466c3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -73,7 +73,7 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { } private fun setupControlButtons(warning: WarningMessage) = when (warning.type) { - WarningMessage.Type.Permanent -> { + WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> { view.group_controls_temporary.hide() view.group_controls_rating.hide() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt new file mode 100644 index 0000000000..95ff1f65ea --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt @@ -0,0 +1,32 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.content.Context +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.dialog_wallet_trade.* + +class ChooseTradeActionDialog(context: Context) : BottomSheetDialog(context) { + + init { + this.setContentView(R.layout.dialog_wallet_trade) + } + + override fun show() { + super.show() + + this.setOnDismissListener { + store.dispatch(WalletAction.HideDialog) + } + + dialog_btn_buy.setOnClickListener { + store.dispatch(WalletAction.TradeCryptoAction.Buy) + dismiss() + } + dialog_btn_sell.setOnClickListener { + store.dispatch(WalletAction.TradeCryptoAction.Sell) + dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt deleted file mode 100644 index c3c866d867..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.tap.features.wallet.ui.dialogs - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.dialog_qrcode.* - -class QrDialog(context: Context) : Dialog(context) { - - init { - this.setContentView(R.layout.dialog_qrcode) - } - - fun showQr(qrCode: Bitmap, shareUrl: String, currencyName: CryptoCurrencyName?) { - this.setOnDismissListener { store.dispatch(WalletAction.HideDialog) } - this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideDialog) } - - this.tv_qr_dialog_address?.text = shareUrl - this.iv_qrcode?.setImageBitmap(qrCode) - if (currencyName == null) { - this.tv_qr_dialog_header.hide() - } else { - this.tv_qr_dialog_header.show() - this.tv_qr_dialog_header.text = context.getString( - R.string.wallet_qr_title_format, currencyName - ) - } - super.show() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index b3f63a0236..496a8ff731 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -2,10 +2,9 @@ package com.tangem.tap.features.wallet.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.feedback.ScanFailsEmail -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R @@ -22,11 +21,8 @@ class ScanFailsDialog { setPositiveButton(R.string.alert_button_request_support) { _, _ -> store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail())) } - setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { - store.dispatch(HomeAction.HideDialog) - store.dispatch(WalletAction.HideDialog) - } + setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> } + setOnDismissListener { store.dispatchDialogHide() } }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt deleted file mode 100644 index 1daec73f5b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.tap.features.wallet.ui.dialogs - -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.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_twin_cards.* -import kotlinx.android.synthetic.main.layout_twin_cards.* - -class TwinsOnboardingFragment : Fragment(R.layout.fragment_twin_cards) { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - } - }) - 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) - - store.dispatch(WalletAction.TwinsAction.SetOnboardingShown) - val secondTwinNumber = - store.state.walletState.twinCardsState?.cardNumber?.pairNumber()?.number ?: "" - val text = getString(R.string.twins_onboarding_description_format, secondTwinNumber) - tv_twin_cards_description_1.text = text - - setOnClickListeners() - - 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) - } - - private fun setOnClickListeners() { - btn_continue.setOnClickListener { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 5deff50687..aa0aaee36c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -4,7 +4,7 @@ import android.app.Dialog import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.redux.global.StateDialog +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.tokens.redux.TokensAction @@ -14,7 +14,6 @@ import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter -import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -42,7 +41,6 @@ class MultiWalletView : WalletView { private fun showMultiWalletView(fragment: WalletFragment) = with(fragment) { tv_twin_card_number.hide() - iv_twin_card.hide() rv_pending_transaction.hide() l_card_balance.hide() l_address.hide() @@ -135,9 +133,6 @@ class MultiWalletView : WalletView { val fragment = fragment ?: return val context = fragment.context ?: return when (walletDialog) { - is WalletDialog.ScanFailsDialog -> { - if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } - } is WalletDialog.SignedHashesMultiWalletDialog -> { if (dialog == null) { dialog = SignedHashesWarningDialog.create(context).apply { show() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index ce0623ac86..cb58832ebb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,20 +6,16 @@ import android.view.ViewGroup import android.widget.Button import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.redux.global.StateDialog -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceWidget import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter -import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog -import com.tangem.tap.features.wallet.ui.dialogs.QrDialog -import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog -import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog +import com.tangem.tap.features.wallet.ui.dialogs.* import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.card_balance.* @@ -74,7 +70,7 @@ class SingleWalletView : WalletView { state.primaryWallet ?: return setupTwinCards(state.twinCardsState, fragment) - setupButtons(state.primaryWallet, state.twinCardsState != null, fragment) + setupButtons(state.primaryWallet, state.isTangemTwins, fragment) setupAddressCard(state.primaryWallet, fragment) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) @@ -90,16 +86,15 @@ class SingleWalletView : WalletView { private fun setupBalance(state: WalletState, primaryWallet: WalletData) { fragment?.apply { this.l_balance.show() - BalanceWidget(this, primaryWallet.currencyData, state.twinCardsState != null).setup() + BalanceWidget(this, primaryWallet.currencyData, state.isTangemTwins).setup() } } private fun setupTwinCards( - twinCardsState: TwinCardsState?, fragment: WalletFragment + twinCardsState: TwinCardsState?, fragment: WalletFragment ) = with(fragment) { twinCardsState?.cardNumber?.let { cardNumber -> tv_twin_card_number.show() - iv_twin_card.show() val number = when (cardNumber) { TwinCardNumber.First -> "1" TwinCardNumber.Second -> "2" @@ -109,13 +104,7 @@ class SingleWalletView : WalletView { } if (twinCardsState?.cardNumber == null) { tv_twin_card_number.hide() - iv_twin_card.hide() } - if (twinCardsState?.showTwinOnboarding == true) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) - } - - } private fun setupButtons( @@ -124,12 +113,17 @@ class SingleWalletView : WalletView { setupButtonsType(state, fragment) - val btnConfirm = if (state.topUpState.allowed) { + val btnConfirm = if (state.tradeCryptoState.sellingAllowed || + state.tradeCryptoState.buyingAllowed + ) { btn_confirm_short } else { btn_confirm_long } - val btnScan = if (state.topUpState.allowed) { + + val btnScan = if (state.tradeCryptoState.sellingAllowed || + state.tradeCryptoState.buyingAllowed + ) { btn_scan_short } else { btn_scan_long @@ -146,17 +140,45 @@ class SingleWalletView : WalletView { store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext())) } } - btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } + btn_show_qr.setOnClickListener { + store.dispatch(WalletAction.ShowDialog.QrCode) + } - btn_top_up.setOnClickListener { - store.dispatch( - WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray) - ) + setupTradeButton(fragment, state.tradeCryptoState) + } + + private fun setupTradeButton(fragment: WalletFragment, tradeCryptoState: TradeCryptoState) { + val allowedToBuy = tradeCryptoState.buyingAllowed + val allowedToSell = tradeCryptoState.sellingAllowed + val action = when { + allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy + !allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell + allowedToBuy && allowedToSell -> WalletAction.ShowDialog.ChooseTradeActionDialog + else -> null + } + val text = when { + allowedToBuy && !allowedToSell -> R.string.wallet_button_buy + !allowedToBuy && allowedToSell -> R.string.wallet_button_sell + allowedToBuy && allowedToSell -> R.string.wallet_button_trade + else -> R.string.wallet_button_trade + } + val icon = when { + allowedToBuy && !allowedToSell -> R.drawable.ic_arrow_up_short_btn + !allowedToBuy && allowedToSell -> R.drawable.ic_arrow_down_short_button + allowedToBuy && allowedToSell -> R.drawable.ic_arrows_up_down_short_btn + else -> null + } + with(fragment) { + btn_trade.text = getText(text) + btn_trade.setCompoundDrawablesWithIntrinsicBounds(0, icon ?: 0, 0, 0) + btn_trade.setOnClickListener { if (action != null) store.dispatch(action) } } } private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) { - if (state.topUpState.allowed) { + if (state.tradeCryptoState.sellingAllowed || + state.tradeCryptoState.buyingAllowed + ) { l_buttons_long.hide() l_buttons_short.show() } else { @@ -222,28 +244,19 @@ class SingleWalletView : WalletView { private fun handleDialogs(walletDialog: StateDialog?, fragment: WalletFragment) { val context = fragment.context ?: return when (walletDialog) { - is WalletDialog.QrDialog -> { - if (walletDialog.qrCode != null && walletDialog.shareUrl != null) { - if (dialog == null) dialog = QrDialog(context).apply { - this.showQr( - walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName - ) - } - } - } is WalletDialog.SelectAmountToSendDialog -> { if (dialog == null) dialog = AmountToSendDialog(context).apply { this.show(walletDialog.amounts) } } - is WalletDialog.ScanFailsDialog -> { - if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } - } is WalletDialog.SignedHashesMultiWalletDialog -> { if (dialog == null) { dialog = SignedHashesWarningDialog.create(context).apply { show() } } } + is WalletDialog.ChooseTradeActionDialog -> { + if (dialog == null) dialog = ChooseTradeActionDialog(context).apply { show() } + } null -> { dialog?.dismiss() dialog = null diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt index fffc439820..cf497373cd 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt @@ -1,7 +1,7 @@ package com.tangem.tap.network.coinmarketcap -import com.tangem.commands.common.network.Result -import com.tangem.commands.common.network.performRequest +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.store import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt new file mode 100644 index 0000000000..5d1bfbb5c2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.network.moonpay + +import retrofit2.http.GET +import retrofit2.http.Query + +interface MoonpayApi { + @GET(MOOONPAY_IP_ADDRESS_REQUEST_URL) + suspend fun getUserStatus( + @Query("apiKey") moonpayApiKey: String, + ): MoonPayUserStatus + + @GET(MOOONPAY_CURRENCIES_REQUEST_URL) + suspend fun getCurrencies( + @Query("apiKey") moonpayApiKey: String, + ): List + + + companion object { + const val MOOONPAY_BASE_URL = "https://api.moonpay.com/" + const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/" + const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt new file mode 100644 index 0000000000..3e9696700d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt @@ -0,0 +1,90 @@ +package com.tangem.tap.network.moonpay + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest +import com.tangem.tap.network.createRetrofitInstance +import kotlinx.coroutines.coroutineScope + +class MoonpayService { + + private val moonpayApi: MoonpayApi by lazy { + createRetrofitInstance(MoonpayApi.MOOONPAY_BASE_URL) + .create(MoonpayApi::class.java) + } + + suspend fun getMoonpayStatus(moonpayApiKey: String): Result { + return try { + coroutineScope { + val userStatusResult = performRequest { moonpayApi.getUserStatus(moonpayApiKey) } + if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult + + val currenciesResult = performRequest { moonpayApi.getCurrencies(moonpayApiKey) } + if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult + + val userStatus = (userStatusResult as Result.Success).data + val currencies = (currenciesResult as Result.Success).data + + val currenciesToBuy = mutableListOf() + val currenciesToSell = mutableListOf() + + currencies.forEach { currencyStatus -> + if (currencyStatus.type != "crypto" || currencyStatus.isSuspended || + !currencyStatus.supportsLiveMode + ) { + return@forEach + } + + if (userStatus.countryCode == "USA") { + if (!currencyStatus.isSupportedInUS) return@forEach + if (currencyStatus.notAllowedUSStates.contains(userStatus.stateCode)) return@forEach + } + val currencyCode = currencyStatus.code.uppercase() + currenciesToBuy.add(currencyCode) + + if (currencyStatus.isSellSupported) currenciesToSell.add(currencyCode) + } + + Result.Success(MoonpayStatus( + isBuyAllowed = userStatus.isBuyAllowed, + isSellAllowed = userStatus.isSellAllowed, + availableToBuy = if (userStatus.isBuyAllowed) currenciesToBuy else emptyList(), + availableToSell = if (userStatus.isSellAllowed) currenciesToSell else emptyList() + )) + } + } catch (error: Error) { + Result.Failure(error) + } + } +} + +data class MoonpayStatus( + val isBuyAllowed: Boolean, + val isSellAllowed: Boolean, + val availableToBuy: List, + val availableToSell: List, +) + +@JsonClass(generateAdapter = true) +data class MoonPayUserStatus( + val isBuyAllowed: Boolean, + val isSellAllowed: Boolean, + @Json(name = "isAllowed") + val isMoonpayAllowed: Boolean, + @Json(name = "alpha3") + val countryCode: String, + @Json(name = "state") + val stateCode: String, +) + +@JsonClass(generateAdapter = true) +data class MoonpayCurrencies( + val type: String, + val code: String, + val supportsLiveMode: Boolean = false, + val isSuspended: Boolean = true, + val isSupportedInUS: Boolean = false, + val isSellSupported: Boolean = false, + val notAllowedUSStates: List = emptyList(), +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt index 2e691228ea..6eab298c6f 100644 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt @@ -1,8 +1,8 @@ package com.tangem.tap.network.payid import com.squareup.moshi.JsonClass -import com.tangem.commands.common.network.Result -import com.tangem.commands.common.network.performRequest +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest import com.tangem.tap.network.createRetrofitInstance import retrofit2.Retrofit diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt index 109dc5e97f..13d79d4591 100644 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt @@ -1,7 +1,7 @@ package com.tangem.tap.network.payid -import com.tangem.commands.common.network.Result -import com.tangem.commands.common.network.performRequest +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest import com.tangem.tap.network.createRetrofitInstance /** diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 871e3eb3d5..12d5eaa695 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -19,10 +19,13 @@ class PreferencesStorage(applicationContext: Application) { private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) val appRatingLaunchObserver: AppRatingLaunchObserver + val usedCardsPrefStorage: UsedCardsPrefStorage init { incrementLaunchCounter() appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) + usedCardsPrefStorage = UsedCardsPrefStorage(preferences) + usedCardsPrefStorage.migrate() } private val fiatCurrenciesAdapter: JsonAdapter> by lazy { @@ -54,20 +57,11 @@ class PreferencesStorage(applicationContext: Application) { fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) - fun saveScannedCardId(cardId: String) { - val scannedCardsIds: String = restoreScannedCardIds() - if (!scannedCardsIds.contains(cardId)) { - preferences.edit().putString(SCANNED_CARDS_IDS_KEY, "$scannedCardsIds$cardId, ").apply() - } - } - + @Deprecated("Use UsedCardsPrefStorage instead") fun wasCardScannedBefore(cardId: String): Boolean { - return restoreScannedCardIds().contains(cardId) + return usedCardsPrefStorage.wasScanned(cardId) } - private fun restoreScannedCardIds(): String = - preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: "" - fun saveDisclaimerAccepted() { preferences.edit().putBoolean(DISCLAIMER_ACCEPTED_KEY, true).apply() } @@ -93,7 +87,6 @@ class PreferencesStorage(applicationContext: Application) { private const val PREFERENCES_NAME = "tapPrefs" private const val APP_CURRENCY_KEY = "appCurrency" private const val FIAT_CURRENCIES_KEY = "fiatCurrencies" - private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted" private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown" private const val APP_LAUNCH_COUNT_KEY = "launchCount" @@ -102,20 +95,23 @@ class PreferencesStorage(applicationContext: Application) { } class AppRatingLaunchObserver( - private val preferences: SharedPreferences, - private val launchCounts: Int, + private val preferences: SharedPreferences, + private val launchCounts: Int, ) { private val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount" private val K_FUNDS_FOUND_DATE = "fundsFoundDate" private val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating" + private val FUNDS_FOUND_DATE_UNDEFINED = -1L private val deferShowing = 20 private val firstShowing = 3 private var fundsFoundDate: Calendar? = null init { - val dateTimeMs = preferences.getLong(K_FUNDS_FOUND_DATE, -1) - if (dateTimeMs > 0) fundsFoundDate = Calendar.getInstance().apply { timeInMillis = dateTimeMs } + val msWhenFundsWasFound = preferences.getLong(K_FUNDS_FOUND_DATE, FUNDS_FOUND_DATE_UNDEFINED) + if (msWhenFundsWasFound != FUNDS_FOUND_DATE_UNDEFINED) { + fundsFoundDate = Calendar.getInstance().apply { timeInMillis = msWhenFundsWasFound } + } } fun foundWalletWithFunds() { diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt new file mode 100644 index 0000000000..17238689f5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -0,0 +1,102 @@ +package com.tangem.tap.persistence + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.tap.common.extensions.replaceByOrAdd +import timber.log.Timber + +/** +[REDACTED_AUTHOR] + */ +class UsedCardsPrefStorage( + private val preferences: SharedPreferences, +) { + + private val jsonConverter = MoshiJsonConverter.INSTANCE + + internal fun migrate() { + val scannedIds = preferences.getString(SCANNED_CARDS_IDS_KEY, null) ?: return + + val usedCardsInfo = scannedIds.split(",").map { UsedCardInfo(it.trim(), true) } + if (save(usedCardsInfo.toMutableList())) { + preferences.edit { this.remove(SCANNED_CARDS_IDS_KEY) } + } + } + + fun scanned(cardId: String) { + val restoredList = restore() + val foundItem = findCardInfo(cardId, restoredList)?.copy(isScanned = true) + ?: UsedCardInfo(cardId, true) + + save(foundItem, restoredList) + } + + fun wasScanned(cardId: String): Boolean { + return findCardInfo(cardId)?.isScanned ?: false + } + + fun activationStarted(cardId: String) { + val restoredList = restore() + val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = true) + ?: UsedCardInfo(cardId, isActivationStarted = true) + + save(foundItem, restoredList) + } + + fun activationIsStarted(cardId: String): Boolean { + return findCardInfo(cardId)?.isActivationStarted ?: false + } + + fun activationFinished(cardId: String) { + val restoredList = restore() + val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = false) + ?: UsedCardInfo(cardId, isActivationStarted = false) + + save(foundItem, restoredList) + } + + private fun findCardInfo(cardId: String, list: MutableList? = null): UsedCardInfo? { + val findInList = list ?: restore() + return findInList.firstOrNull { it.cardId == cardId } + } + + private fun save(usedCardInfo: UsedCardInfo?, usedCardsInfo: MutableList) { + val info = usedCardInfo ?: return + + usedCardsInfo.replaceByOrAdd(info) { it.cardId == info.cardId } + save(usedCardsInfo) + } + + private fun save(list: MutableList): Boolean { + return try { + val json = jsonConverter.toJson(list) + preferences.edit { putString(USED_CARDS_INFO, json) } + true + } catch (ex: Exception) { + Timber.e(ex) + false + } + } + + private fun restore(): MutableList { + val json = preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf() + return try { + jsonConverter.fromJson(json, jsonConverter.typedList(UsedCardInfo::class.java))!! + } catch (ex: Exception) { + preferences.edit(true) { remove(USED_CARDS_INFO) } + mutableListOf() + } + } + + companion object { + private const val USED_CARDS_INFO = "usedCardsInfo" + private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" + } +} + +private data class UsedCardInfo( + val cardId: String, + val isScanned: Boolean = false, + val isActivationStarted: Boolean = false, +) \ No newline at end of file diff --git a/app/src/main/res/color/menu_item_color.xml b/app/src/main/res/color/menu_item_color.xml new file mode 100644 index 0000000000..2b59ebcebe --- /dev/null +++ b/app/src/main/res/color/menu_item_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/anim_progress_balance.xml b/app/src/main/res/drawable/anim_progress_balance.xml new file mode 100644 index 0000000000..ada4670265 --- /dev/null +++ b/app/src/main/res/drawable/anim_progress_balance.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_half_transparent_overlay.xml b/app/src/main/res/drawable/bg_half_transparent_overlay.xml new file mode 100644 index 0000000000..4b5937d793 --- /dev/null +++ b/app/src/main/res/drawable/bg_half_transparent_overlay.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_placeholder.xml b/app/src/main/res/drawable/card_placeholder.xml deleted file mode 100644 index 1cfef76e78..0000000000 --- a/app/src/main/res/drawable/card_placeholder.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/card_placeholder_black.xml b/app/src/main/res/drawable/card_placeholder_black.xml new file mode 100644 index 0000000000..83fab8e0f7 --- /dev/null +++ b/app/src/main/res/drawable/card_placeholder_black.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/card_placeholder_white.xml b/app/src/main/res/drawable/card_placeholder_white.xml new file mode 100644 index 0000000000..d980dc5599 --- /dev/null +++ b/app/src/main/res/drawable/card_placeholder_white.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_activation_success.xml b/app/src/main/res/drawable/ic_activation_success.xml new file mode 100644 index 0000000000..7957c82493 --- /dev/null +++ b/app/src/main/res/drawable/ic_activation_success.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_angle_down.xml b/app/src/main/res/drawable/ic_arrow_angle_down.xml new file mode 100644 index 0000000000..e773e9862f --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_angle_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_angle_right.xml b/app/src/main/res/drawable/ic_arrow_angle_right.xml new file mode 100644 index 0000000000..c009fd11a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_angle_right.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_down_short_button.xml b/app/src/main/res/drawable/ic_arrow_down_short_button.xml new file mode 100644 index 0000000000..18b58e3f6c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down_short_button.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_refresh.xml b/app/src/main/res/drawable/ic_arrow_refresh.xml new file mode 100644 index 0000000000..1acc645cec --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrows_up_down_short_btn.xml b/app/src/main/res/drawable/ic_arrows_up_down_short_btn.xml new file mode 100644 index 0000000000..51b285c947 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrows_up_down_short_btn.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_copy_green.xml b/app/src/main/res/drawable/ic_copy_green.xml new file mode 100644 index 0000000000..a4145be642 --- /dev/null +++ b/app/src/main/res/drawable/ic_copy_green.xml @@ -0,0 +1,19 @@ + + + + diff --git a/app/src/main/res/drawable/ic_progress_balance.xml b/app/src/main/res/drawable/ic_progress_balance.xml new file mode 100644 index 0000000000..d31defe1e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_progress_balance.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_share_green.xml b/app/src/main/res/drawable/ic_share_green.xml new file mode 100644 index 0000000000..57ee232b90 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_green.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ripple.xml b/app/src/main/res/drawable/ripple.xml new file mode 100644 index 0000000000..c6e07a265f --- /dev/null +++ b/app/src/main/res/drawable/ripple.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circle.xml b/app/src/main/res/drawable/shape_circle.xml index f857819f4d..59a662d141 100644 --- a/app/src/main/res/drawable/shape_circle.xml +++ b/app/src/main/res/drawable/shape_circle.xml @@ -1,6 +1,9 @@ - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circle_home_screen.xml b/app/src/main/res/drawable/shape_circle_home_screen.xml new file mode 100644 index 0000000000..6cc2f8cc4e --- /dev/null +++ b/app/src/main/res/drawable/shape_circle_home_screen.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_100.xml b/app/src/main/res/drawable/shape_rectangle_rounded_100.xml new file mode 100644 index 0000000000..7f26fb8be5 --- /dev/null +++ b/app/src/main/res/drawable/shape_rectangle_rounded_100.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml new file mode 100644 index 0000000000..a788f39093 --- /dev/null +++ b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_refresh_button.xml b/app/src/main/res/drawable/shape_refresh_button.xml new file mode 100644 index 0000000000..ac9508438d --- /dev/null +++ b/app/src/main/res/drawable/shape_refresh_button.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml new file mode 100644 index 0000000000..e635e3ee6f --- /dev/null +++ b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml new file mode 100644 index 0000000000..55617e452f --- /dev/null +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_wallet_trade.xml b/app/src/main/res/layout/dialog_wallet_trade.xml new file mode 100644 index 0000000000..062d0286d1 --- /dev/null +++ b/app/src/main/res/layout/dialog_wallet_trade.xml @@ -0,0 +1,47 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_tokens.xml b/app/src/main/res/layout/fragment_add_tokens.xml index 3ed5cbd260..792f48fc05 100644 --- a/app/src/main/res/layout/fragment_add_tokens.xml +++ b/app/src/main/res/layout/fragment_add_tokens.xml @@ -22,7 +22,7 @@ android:layout_height="?attr/actionBarSize" app:menu="@menu/popular_tokens" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" - app:title="Add Currencies" /> + app:title="Add tokens" /> diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 70c51c9eb3..90df722e29 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -43,27 +43,46 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="33dp"> + + + + + app:layout_constraintTop_toBottomOf="@id/tv_card_title" /> + + + + + - - + app:layout_constraintTop_toBottomOf="@id/tv_security_title" /> + app:layout_constraintTop_toBottomOf="@id/tv_erase_wallet" /> + @@ -187,6 +222,37 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_settings_title" /> + + + + + app:layout_constraintTop_toBottomOf="@id/tv_card_tou" /> - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_details_twin_cards.xml b/app/src/main/res/layout/fragment_details_twin_cards.xml deleted file mode 100644 index 7549f32998..0000000000 --- a/app/src/main/res/layout/fragment_details_twin_cards.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_twin_cards_warning.xml b/app/src/main/res/layout/fragment_details_twin_cards_warning.xml deleted file mode 100644 index 1343fbe116..0000000000 --- a/app/src/main/res/layout/fragment_details_twin_cards_warning.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_disclaimer.xml b/app/src/main/res/layout/fragment_disclaimer.xml index 0c771b9995..bec0ceace0 100644 --- a/app/src/main/res/layout/fragment_disclaimer.xml +++ b/app/src/main/res/layout/fragment_disclaimer.xml @@ -28,17 +28,15 @@ android:id="@+id/cl_details_confirm" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="33dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> + app:layout_constraintBottom_toBottomOf="parent"> @@ -62,18 +60,27 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintGuide_percent="0.37" /> + + + app:layout_constraintStart_toEndOf="@+id/guideline" + android:layout_marginBottom="24dp" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml deleted file mode 100644 index 33157a5fe6..0000000000 --- a/app/src/main/res/layout/fragment_home.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml new file mode 100644 index 0000000000..6d567ce91b --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml new file mode 100644 index 0000000000..77d9ef65f8 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_topup.xml b/app/src/main/res/layout/fragment_topup.xml index 8069c307f0..01182c0c4c 100644 --- a/app/src/main/res/layout/fragment_topup.xml +++ b/app/src/main/res/layout/fragment_topup.xml @@ -11,7 +11,7 @@ android:layout_height="?attr/actionBarSize" app:layout_constraintTop_toTopOf="parent" app:navigationIcon="@drawable/ic_close" - app:title="@string/wallet_button_topup" /> + app:title="@string/wallet_button_buy" /> - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index af281426f8..bc98ff9e41 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -13,7 +13,7 @@ style="@style/Widget.MaterialComponents.Toolbar.Surface" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/backgroundLightGray" + android:background="@color/toolbarColor" android:fitsSystemWindows="true" app:liftOnScroll="true"> @@ -58,42 +58,36 @@ android:elevation="3dp" android:maxHeight="215dp" android:scaleType="fitCenter" - android:src="@drawable/card_placeholder" + android:src="@drawable/card_placeholder_black" app:layout_constraintTop_toTopOf="parent" /> - - - + app:constraint_referenced_ids="iv_card,tv_twin_card_number" /> @@ -52,7 +53,6 @@ android:layout_height="wrap_content" android:paddingStart="16dp" android:paddingEnd="16dp" - android:paddingBottom="16dp" android:textColor="@color/darkGray6" android:textSize="32sp" android:textStyle="bold" @@ -60,22 +60,44 @@ app:layout_constraintTop_toTopOf="parent" tools:text="Bitcoin" /> + + + + + + app:layout_constraintTop_toBottomOf="@id/barrier_currency_title" /> @@ -93,13 +115,13 @@ app:layout_constraintTop_toBottomOf="@+id/l_wallet_details"> + android:textColor="@android:color/white" + android:textSize="14sp" /> - + + + + + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toEndOf="@id/btn_trade" + app:layout_constraintEnd_toStartOf="@id/btn_confirm" + app:layout_constraintTop_toBottomOf="@id/barrier" + app:layout_constraintVertical_bias="1" /> diff --git a/app/src/main/res/layout/item_currency_subtitle.xml b/app/src/main/res/layout/item_currency_subtitle.xml index c4ae27800d..677c928c32 100644 --- a/app/src/main/res/layout/item_currency_subtitle.xml +++ b/app/src/main/res/layout/item_currency_subtitle.xml @@ -2,6 +2,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml index d4a2a6dfd2..05af6fcb26 100644 --- a/app/src/main/res/layout/item_currency_wallet.xml +++ b/app/src/main/res/layout/item_currency_wallet.xml @@ -17,7 +17,8 @@ + android:layout_height="wrap_content" + android:minHeight="76dp"> + + + tools:text="Binance Smart Chain Optimal" /> + + + + + app:layout_constraintStart_toEndOf="@id/iv_currency" + app:layout_constraintTop_toTopOf="@id/guideline" /> - - diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml index 8d22ab57b4..976047b14b 100644 --- a/app/src/main/res/layout/item_popular_token.xml +++ b/app/src/main/res/layout/item_popular_token.xml @@ -3,9 +3,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="52dp" + android:layout_height="wrap_content" android:layout_marginBottom="1dp" - android:background="@android:color/white"> + android:background="@android:color/white" + android:minHeight="60dp"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_balance.xml b/app/src/main/res/layout/layout_balance.xml index b4ac0497dd..0e3f124d94 100644 --- a/app/src/main/res/layout/layout_balance.xml +++ b/app/src/main/res/layout/layout_balance.xml @@ -19,7 +19,7 @@ android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:text="Bitcoin" /> + tools:text="Binance Smart Chain Optimal" /> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_container_top.xml b/app/src/main/res/layout/layout_onboarding_container_top.xml new file mode 100644 index 0000000000..df0e6d2df5 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_container_top.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_home_top.xml b/app/src/main/res/layout/layout_onboarding_home_top.xml new file mode 100644 index 0000000000..fe45273f14 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_home_top.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_pseudo_toolbar.xml b/app/src/main/res/layout/layout_pseudo_toolbar.xml new file mode 100644 index 0000000000..f9ae5fe2ab --- /dev/null +++ b/app/src/main/res/layout/layout_pseudo_toolbar.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/app/src/main/res/layout/layout_receipt_total.xml b/app/src/main/res/layout/layout_receipt_total.xml index b130817a2c..7cca4f9165 100644 --- a/app/src/main/res/layout/layout_receipt_total.xml +++ b/app/src/main/res/layout/layout_receipt_total.xml @@ -45,10 +45,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" - android:gravity="end" android:layout_marginTop="4dp" - tools:text="@string/send_total_subtitle_format" - android:textColor="@color/darkGray1" /> + android:gravity="end" + android:textColor="@color/darkGray1" + tools:text="123.29837729 ADA 39487593.109342039402938049 will be sent" /> diff --git a/app/src/main/res/layout/layout_send_address_payid.xml b/app/src/main/res/layout/layout_send_address_payid.xml index 00a5617a9d..232b778f22 100644 --- a/app/src/main/res/layout/layout_send_address_payid.xml +++ b/app/src/main/res/layout/layout_send_address_payid.xml @@ -32,6 +32,7 @@ android:id="@+id/etAddressOrPayId" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@color/backgroundLightGray" android:ellipsize="middle" android:inputType="textNoSuggestions" android:paddingStart="0dp" @@ -139,7 +140,7 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_twin_cards.xml b/app/src/main/res/layout/layout_twin_cards.xml deleted file mode 100644 index cb2808a482..0000000000 --- a/app/src/main/res/layout/layout_twin_cards.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_twin_cards_orange.xml b/app/src/main/res/layout/layout_twin_cards_orange.xml deleted file mode 100644 index f524c5dd49..0000000000 --- a/app/src/main/res/layout/layout_twin_cards_orange.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_wallet_details.xml b/app/src/main/res/layout/layout_wallet_details.xml index 63bbb5f031..2532441b29 100644 --- a/app/src/main/res/layout/layout_wallet_details.xml +++ b/app/src/main/res/layout/layout_wallet_details.xml @@ -179,6 +179,20 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/btn_copy" /> + diff --git a/app/src/main/res/layout/layout_wallet_short_buttons.xml b/app/src/main/res/layout/layout_wallet_short_buttons.xml index 072306c7b7..61217164ee 100644 --- a/app/src/main/res/layout/layout_wallet_short_buttons.xml +++ b/app/src/main/res/layout/layout_wallet_short_buttons.xml @@ -9,6 +9,7 @@ android:id="@+id/btn_scan_short" style="@style/TapBlackButton" android:layout_width="0dp" + android:layout_height="52dp" android:layout_marginStart="16dp" android:layout_marginTop="30dp" android:layout_marginBottom="33dp" @@ -17,21 +18,22 @@ android:paddingTop="7dp" android:text="@string/wallet_button_scan" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@id/btn_top_up" + app:layout_constraintEnd_toStartOf="@id/btn_trade" app:layout_constraintStart_toStartOf="parent" app:layout_constraintVertical_bias="1" /> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_warning.xml b/app/src/main/res/layout/layout_warning.xml index a9a76d4637..e2f6f41d6f 100644 --- a/app/src/main/res/layout/layout_warning.xml +++ b/app/src/main/res/layout/layout_warning.xml @@ -44,7 +44,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginTop="12dp" + android:layout_marginTop="4dp" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:text="Test text messaging, or texting, is the act of composing and sending electronic messages, typically consisting of alphabetic and numeric characters, between" diff --git a/app/src/main/res/layout/lp_onboarding_create_wallet.xml b/app/src/main/res/layout/lp_onboarding_create_wallet.xml new file mode 100644 index 0000000000..3c5cd91655 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_create_wallet.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_done.xml b/app/src/main/res/layout/lp_onboarding_done.xml new file mode 100644 index 0000000000..e6f1cb76fa --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_done_activation.xml b/app/src/main/res/layout/lp_onboarding_done_activation.xml new file mode 100644 index 0000000000..1ed62c57f9 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done_activation.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_done_activation_twins.xml b/app/src/main/res/layout/lp_onboarding_done_activation_twins.xml new file mode 100644 index 0000000000..eeea439aad --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done_activation_twins.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml new file mode 100644 index 0000000000..6b924ea771 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_topup_wallet_twins.xml b/app/src/main/res/layout/lp_onboarding_topup_wallet_twins.xml new file mode 100644 index 0000000000..f03ba39274 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet_twins.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/test_leapfrog_fragment.xml b/app/src/main/res/layout/test_leapfrog_fragment.xml new file mode 100644 index 0000000000..52b720ad27 --- /dev/null +++ b/app/src/main/res/layout/test_leapfrog_fragment.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/test_leapfrog_widget.xml b/app/src/main/res/layout/test_leapfrog_widget.xml new file mode 100644 index 0000000000..c4fddb3183 --- /dev/null +++ b/app/src/main/res/layout/test_leapfrog_widget.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_bg_home.xml b/app/src/main/res/layout/view_bg_home.xml new file mode 100644 index 0000000000..4cba83727d --- /dev/null +++ b/app/src/main/res/layout/view_bg_home.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_bg_twins_welcome.xml b/app/src/main/res/layout/view_bg_twins_welcome.xml new file mode 100644 index 0000000000..acfee09420 --- /dev/null +++ b/app/src/main/res/layout/view_bg_twins_welcome.xml @@ -0,0 +1,49 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_confetti.xml b/app/src/main/res/layout/view_confetti.xml new file mode 100644 index 0000000000..ad07b6e0bd --- /dev/null +++ b/app/src/main/res/layout/view_confetti.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_onboarding_progress.xml b/app/src/main/res/layout/view_onboarding_progress.xml new file mode 100644 index 0000000000..6dbac9141f --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_progress.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/layout/view_onboarding_refresh_balance.xml b/app/src/main/res/layout/view_onboarding_refresh_balance.xml new file mode 100644 index 0000000000..88daba2ce2 --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_refresh_balance.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_onboarding_tv_balance.xml b/app/src/main/res/layout/view_onboarding_tv_balance.xml new file mode 100644 index 0000000000..84aceb28fa --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_tv_balance.xml @@ -0,0 +1,54 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/wallet_details.xml b/app/src/main/res/menu/wallet_details.xml index 9622311e82..08ba8df389 100644 --- a/app/src/main/res/menu/wallet_details.xml +++ b/app/src/main/res/menu/wallet_details.xml @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/app/src/main/res/raw/anim_confetti.json b/app/src/main/res/raw/anim_confetti.json new file mode 100644 index 0000000000..f2d1e7f974 --- /dev/null +++ b/app/src/main/res/raw/anim_confetti.json @@ -0,0 +1 @@ +{"v":"5.6.3","fr":30,"ip":0,"op":120,"w":1242,"h":2688,"nm":"Comp 1","ddd":1,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[692,1011.9999999999999,0],"ix":2},"a":{"a":0,"k":[71,-332,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-48,204],[72.555,109.763],[-9.41,53.993],[11.376,60.133],[-20.778,48.214],[-43.71,34.149],[-28,80]],"o":[[48,-204],[-46.308,-70.056],[6.44,-36.951],[-18.438,-97.46],[10.775,-25.002],[128,-100],[28,-80]],"v":[[71,-332],[151,-620],[209.484,-736.613],[141,-856],[199.096,-959.097],[193,-1114],[371,-1280]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14.077,"s":[0]},{"t":46.384765625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11.923,"s":[0]},{"t":42.0771484375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":2,"op":112,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[648,1040,0],"ix":2},"a":{"a":0,"k":[27,-304,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[28,112],[154,-50],[122,52],[60,164]],"o":[[0,0],[-24,-96],[-150.386,48.827],[-141.68,-60.388],[-60,-164]],"v":[[27,-304],[-107,-530],[-249,-720],[-335,-908],[-537,-1284]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":35.615234375,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[0]},{"t":27,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[674,1228,0],"ix":2},"a":{"a":0,"k":[53,-116,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-34,-98],[21,-81],[-20,-38],[-18,-48],[-8,-34],[-62,-38],[-24,-36],[-62,-30],[-84,-22]],"o":[[34,98],[-11.008,42.46],[12.721,24.17],[18,48],[9.222,39.192],[62,38],[24,36],[62,30],[84,22]],"v":[[53,-116],[134,25],[94,107],[181,157],[150,265],[237,323],[250,463],[383,540],[480,694]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.03137254901960784,0.807843137254902,0.3607843137254902,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":22.692,"s":[0]},{"t":55,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20.538,"s":[0]},{"t":50.6923828125,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":6,"op":112,"st":6,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[704,1180,0],"ix":2},"a":{"a":0,"k":[83,-164,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-38,-60],[-40.126,23.279],[42.5,-0.5],[-35,2.5],[-47.28,-26.863],[-20,-30],[-21.113,-24.13],[-34,-42],[-16,-40]],"o":[[38,60],[65.5,-38],[-20.024,0.236],[41.06,-2.933],[44,25],[20,30],[14,16],[34,42],[16,40]],"v":[[83,-164],[166.5,-75],[217.5,-41.5],[222.5,-81.5],[377,-77],[378,1],[458,17],[478,91],[616,178]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.615,"s":[0]},{"t":39.9228515625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":31.3076171875,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-3,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[730,1100,0],"ix":2},"a":{"a":0,"k":[109,-244,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-72,-10],[-51,10],[13,-16],[-24.905,-7.729],[-10,10]],"o":[[72,10],[51,-10],[-10.168,12.514],[29,9],[10,-10]],"v":[[109,-244],[483,-178],[533,-129],[556,-83],[618,-93]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":16.23,"s":[0]},{"t":48.5380859375,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14.077,"s":[0]},{"t":44.23046875,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":112,"st":3,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[678,1086,0],"ix":2},"a":{"a":0,"k":[57,-258,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-106,94],[-78,82],[-2,37],[-15,15],[-27.82,25.594],[-23,24],[-38.877,17.279],[-24,-14]],"o":[[106,-94],[78,-82],[2.405,-44.488],[18.028,-18.028],[25,-23],[19.594,-20.446],[36,-16],[24,14]],"v":[[57,-258],[204,-404],[337,-493],[346,-597],[426,-624],[454,-699],[542,-728],[611,-805]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.615,"s":[0]},{"t":39.9228515625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":31.3076171875,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-3,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[640,1170,0],"ix":2},"a":{"a":0,"k":[19,-174,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[92,-22],[38,-38],[78,-8],[38,-10],[10,-20]],"o":[[-92,22],[-38,38],[-78,8],[-38,10],[-10,20]],"v":[[19,-174],[-115,-120],[-283,-46],[-423,-50],[-517,32]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.615,"s":[0]},{"t":39.9228515625,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5.462,"s":[0]},{"t":35.615234375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[661,1191,0],"ix":2},"a":{"a":0,"k":[40,-153,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.398,-37.618],[29.992,-37.49],[41,-30],[60.195,-37.77],[80.591,1.465],[-31.635,-5.678],[109.189,24.816],[22,-30]],"o":[[-34,78],[-44,55],[-59.359,43.434],[-51,32],[-55,-1],[39,7],[-132,-30],[-22,30]],"v":[[40,-153],[-41,-58],[-104,84],[-253,140],[-359,228],[-381,175],[-459,204],[-652,321]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":18.385,"s":[0]},{"t":50.6923828125,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14.077,"s":[0]},{"t":42.0771484375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":2,"op":112,"st":2,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[646,1126,0],"ix":2},"a":{"a":0,"k":[25,-218,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[90,52],[20,-18],[44,46],[60,-36],[34,64],[-46,-10],[34,-50],[52,12],[28,10]],"o":[[-90,-52],[-20,18],[-44,-46],[-60,36],[-34,-64],[46,10],[-34,50],[-52,-12],[-28,-10]],"v":[[25,-218],[-175,-248],[-271,-286],[-379,-328],[-487,-310],[-535,-444],[-469,-396],[-547,-342],[-627,-366]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5.462,"s":[0]},{"t":37.76953125,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3.308,"s":[0]},{"t":35.615234375,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Out Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":10,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":184,"ix":10},"p":{"a":0,"k":[633,1188,0],"ix":2},"a":{"a":0,"k":[12,-198,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":112,"st":0,"bm":0},{"ddd":1,"ind":11,"ty":4,"nm":"Shape Layer 41","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0.179,"s":[6.395,-188.007,0],"to":[3.99,-626.075,396.962],"ti":[-3.99,626.075,-396.962]},{"t":34.7802734375,"s":[30.337,-3944.458,2381.774]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":12,"ty":4,"nm":"Shape Layer 40","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":80.50390625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0.179,"s":[6.395,-188.007,0],"to":[172.235,-647.46,153.434],"ti":[-172.235,647.46,-153.434]},{"t":80,"s":[1039.806,-4072.764,920.605]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":13,"ty":4,"nm":"Shape Layer 39","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0.179,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0.179,"s":[6.395,-188.007,0],"to":[140.553,-166.199,-42.398],"ti":[-140.553,166.199,42.398]},{"t":15.0078125,"s":[849.715,-1185.203,-254.39]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":14,"ty":3,"nm":"Null 2","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-49,"ix":10},"p":{"a":0,"k":[621,1344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":112,"st":-4,"bm":0},{"ddd":0,"ind":15,"ty":3,"nm":"Null 3","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":82,"ix":10},"p":{"a":0,"k":[621,1344,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":112,"st":-4,"bm":0},{"ddd":1,"ind":16,"ty":4,"nm":"Shape Layer 105","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":87.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[727.321,51.433,435.455],"ti":[-727.321,-51.433,-435.455]},{"t":34.544921875,"s":[4250.962,186.167,2612.729]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":17,"ty":4,"nm":"Shape Layer 104","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":80.50390625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[527.848,55.985,153.434],"ti":[-527.848,-55.985,-153.434]},{"t":54.552734375,"s":[3054.124,213.477,920.605]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":18,"ty":4,"nm":"Shape Layer 103","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[4.145,138.496,-42.398],"ti":[-4.145,-138.496,42.398]},{"t":15.0078125,"s":[-88.093,708.543,-254.39]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":19,"ty":4,"nm":"Shape Layer 102","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-0.179,"s":[-112.965,-122.431,0],"to":[-61.615,187.567,26.907],"ti":[61.615,-187.567,-26.907]},{"t":31.0732421875,"s":[-482.654,1002.974,161.442]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":20,"ty":4,"nm":"Shape Layer 101","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":84.2109375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.824},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-90.863,90.863,0],"ti":[88.083,-357.903,-768.642]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.164},"t":17.479,"s":[-658.144,422.748,0],"to":[-84.157,341.951,734.383],"ti":[-714.89,-84.078,-197.259]},{"t":86.6826171875,"s":[3631.196,927.217,1183.551]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":21,"ty":4,"nm":"Shape Layer 100","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.796},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-134.704,42.073,0],"ti":[7.203,-205.415,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.157},"t":28,"s":[-921.188,130.006,0],"to":[-19.567,558.035,0],"ti":[-540.328,-229.432,0]},{"t":116,"s":[2320.778,1506.598,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":22,"ty":4,"nm":"Shape Layer 99","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.775},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-115.257,9.245,0],"ti":[52.068,-163.912,-53.249]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.155},"t":19.951,"s":[-804.504,-66.963,0],"to":[-48.777,153.551,49.883],"ti":[-156.371,-109.917,-25.485]},{"t":91,"s":[2733.865,1051.661,319.492]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":23,"ty":4,"nm":"Shape Layer 98","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.855},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-138.996,-53.461,0],"ti":[-421.763,15.031,16.673]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.174},"t":21.187,"s":[-946.942,-443.198,0],"to":[421.763,-15.031,-16.673],"ti":[-560.76,-38.43,16.673]},{"t":100.2763671875,"s":[2417.616,-212.616,-100.037]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":24,"ty":4,"nm":"Shape Layer 97","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[59]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.673},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-87.16,25.736,-45.544],"ti":[-273.542,-218.754,122.377]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.14},"t":31.073,"s":[-660.653,26.246,-273.265],"to":[129.994,103.957,-58.156],"ti":[-320.287,-255.677,-112.981]},{"t":111.3984375,"s":[3683.402,989.215,1150.251]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[3.444,9.757]],"o":[[0,0],[0,0],[0,0],[-4.941,-14]],"v":[[72,-317],[4,-279],[24.151,-217.936],[88.307,-269.617]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":25,"ty":4,"nm":"Shape Layer 96","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.853},"o":{"x":0.167,"y":0.167},"t":0,"s":[-89.038,-0.153,0],"to":[-552.568,-84.135,-9.152],"ti":[-1514.035,-462.134,43.281]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.183},"t":38,"s":[95.207,-392.324,-54.91],"to":[684.779,209.018,-19.576],"ti":[-315.276,-115.122,160.297]},{"t":97,"s":[1986.864,298.408,-1016.689]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":26,"ty":4,"nm":"Shape Layer 95","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-14,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.774},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-110.347,-144.949,-65.532],"ti":[-365.662,-268.839,-214.28]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.147},"t":26,"s":[-799.777,-997.863,-393.192],"to":[365.662,268.839,214.28],"ti":[-476.009,-413.788,-279.812]},{"t":76.796875,"s":[2056.275,1484.866,1285.683]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":27,"ty":4,"nm":"Shape Layer 94","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-90.481,-247.593,46.629],"ti":[90.481,247.593,-46.629]},{"t":44,"s":[-680.583,-1613.73,279.773]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":28,"ty":4,"nm":"Shape Layer 93","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[69.003,-110.281,-16.168],"ti":[-69.003,110.281,16.168]},{"t":32,"s":[276.323,-789.855,-97.005]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":29,"ty":4,"nm":"Shape Layer 92","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-10,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[305.417,119.743,41.862],"ti":[-305.417,-119.743,-41.862]},{"t":27,"s":[1694.805,590.289,251.17]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":30,"ty":4,"nm":"Shape Layer 91","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[318.521,75.426,-43.518],"ti":[-318.521,-75.426,43.518]},{"t":24,"s":[1773.433,324.385,-261.107]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":31,"ty":4,"nm":"Shape Layer 90","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.154296875,"s":[1800]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[77.354,134.863,-215.584],"ti":[-77.354,-134.863,215.584]},{"t":27.3662109375,"s":[326.43,681.005,-1293.506]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":32,"ty":4,"nm":"Shape Layer 89","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[301.698,164.851,-47.459],"ti":[-301.698,-164.851,47.459]},{"t":103.9833984375,"s":[1697.223,866.674,-284.755]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[84.659,-333.362],[4,-279],[45.665,-243.617],[119.735,-297.935]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":33,"ty":4,"nm":"Shape Layer 88","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[159.503,223.195,-0.572],"ti":[-159.503,-223.195,0.572]},{"t":37.251953125,"s":[844.051,1216.741,-3.43]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":34,"ty":4,"nm":"Shape Layer 87","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[33.76,196.346,-7.096],"ti":[-33.76,-196.346,7.096]},{"t":40.958984375,"s":[89.594,1055.648,-42.576]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":35,"ty":4,"nm":"Shape Layer 86","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-16.649,163.663,0],"ti":[16.649,-163.663,0]},{"t":24.89453125,"s":[-212.861,859.547,0]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":36,"ty":4,"nm":"Shape Layer 85","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-71.747,128.05,0],"ti":[71.747,-128.05,0]},{"t":49.609375,"s":[-543.449,645.87,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":37,"ty":4,"nm":"Shape Layer 84","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":112.6337890625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.728},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-81.837,27.324,33.544],"ti":[-153.275,-262.436,-33.544]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.131},"t":21,"s":[-603.986,41.512,201.262],"to":[153.275,262.436,33.544],"ti":[-235.112,-235.112,0]},{"t":50.845703125,"s":[806.688,1452.185,201.262]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":38,"ty":4,"nm":"Shape Layer 83","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-6,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.652},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-171.831,20.57,3.486],"ti":[602.026,-193.886,-848.553]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.142},"t":35,"s":[-1143.953,0.988,20.917],"to":[-778.519,946.911,884.934],"ti":[-1189.71,-488.979,-884.978]},{"t":139,"s":[5994.306,2934.863,5330.786]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":39,"ty":4,"nm":"Shape Layer 82","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-23,"s":[0]},{"t":97.8046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,135],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.883},"o":{"x":0.167,"y":0.167},"t":0,"s":[-112.965,-122.431,0],"to":[-120.682,-96.313,17.582],"ti":[57.484,-266.769,15.802]},{"i":{"x":0.833,"y":0.817},"o":{"x":0.167,"y":0.198},"t":15.008,"s":[-837.059,-700.31,105.493],"to":[-25.433,118.027,-6.991],"ti":[-511.749,-461.825,46.841]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.143},"t":54.553,"s":[271.713,386.113,403.89],"to":[308.693,278.579,-28.255],"ti":[-142.63,-100.689,10.833]},{"t":76.796875,"s":[1060.877,1065.009,-67.253]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":40,"ty":4,"nm":"Shape Layer 81","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-12,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[135.751,-389.001,217.62],"ti":[-135.751,389.001,-217.62]},{"t":27,"s":[676.808,-2462.175,1305.721]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":41,"ty":4,"nm":"Shape Layer 80","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-25,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.849},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-70.649,26.407,0],"ti":[-291.085,-122.356,90.248]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.171},"t":16,"s":[-561.59,30.269,0],"to":[291.085,122.356,-90.248],"ti":[-361.735,-95.95,90.248]},{"t":82.9755859375,"s":[1608.818,605.966,-541.488]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":42,"ty":4,"nm":"Shape Layer 79","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.704},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[-49.159,-63.557,4.223],"ti":[-0.005,148.838,-20.562]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.144},"t":24,"s":[-432.65,-509.511,25.34],"to":[0.017,-477.286,65.936],"ti":[-415.767,197.78,-61.713]},{"t":91.6259765625,"s":[2061.954,-1696.192,395.618]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":43,"ty":4,"nm":"Shape Layer 78","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-20,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[33.977,-166.361,22.596],"ti":[-33.977,166.361,-22.596]},{"t":30,"s":[66.167,-1126.338,135.575]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":44,"ty":4,"nm":"Shape Layer 77","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-16,"s":[0]},{"t":99.041015625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[345.992,-256.663,189.679],"ti":[-345.992,256.663,-189.679]},{"t":27,"s":[1938.258,-1668.151,1138.074]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":45,"ty":4,"nm":"Shape Layer 76","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":45.90234375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":105.2197265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.826},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[120.875,-60.552,0],"ti":[-340.061,254.939,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.163},"t":27.366,"s":[587.554,-491.48,0],"to":[340.061,-254.939,0],"ti":[-219.187,194.387,0]},{"t":82.9755859375,"s":[1902.674,-1657.802,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":46,"ty":4,"nm":"Shape Layer 75","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-18,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.926},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[208.471,-17.277,73.2],"ti":[-257.939,74.184,269.358]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.228},"t":15.897,"s":[1113.129,-231.834,439.203],"to":[257.939,-74.184,-269.358],"ti":[-49.469,56.907,342.559]},{"t":90,"s":[1409.942,-573.277,-1616.149]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":47,"ty":4,"nm":"Shape Layer 74","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-24,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,311],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-137.695,-128.17,0],"to":[308.185,154.066,17.938],"ti":[-308.185,-154.066,-17.938]},{"t":24.89453125,"s":[1711.415,796.226,107.63]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[51,51,51],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":48,"ty":4,"nm":"Shape Layer 73","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-47.653,624.272,396.962],"ti":[47.653,-624.272,-396.962]},{"t":34.7802734375,"s":[-267.632,3580.053,2381.774]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":49,"ty":4,"nm":"Shape Layer 72","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":80.50390625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-143.363,403.815,153.434],"ti":[143.363,-403.815,-153.434]},{"t":54.552734375,"s":[-841.892,2257.314,920.605]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":50,"ty":4,"nm":"Shape Layer 71","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-196.222,199.787,-42.398],"ti":[196.222,-199.787,42.398]},{"t":32.30859375,"s":[-1159.042,1033.147,-254.39]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":51,"ty":4,"nm":"Shape Layer 70","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.868},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-181.982,76.554,26.907],"ti":[353.39,-64.568,-26.907]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.207},"t":31.073,"s":[-1073.604,293.746,161.442],"to":[-353.39,64.568,26.907],"ti":[171.408,11.986,0]},{"t":73.08984375,"s":[-2102.054,221.83,161.442]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":52,"ty":4,"nm":"Shape Layer 69","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":84.2109375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-128.187,-8.964,0],"ti":[128.187,8.964,0]},{"t":33,"s":[-750.834,-219.36,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":53,"ty":4,"nm":"Shape Layer 68","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-120.126,-74.06,0],"ti":[120.126,74.06,0]},{"t":39,"s":[-702.471,-609.938,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":54,"ty":4,"nm":"Shape Layer 67","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.723},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-82.592,-80.92,0],"ti":[157.866,-68.24,-53.249]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.139},"t":27,"s":[-477.265,-651.099,0],"to":[-147.887,63.926,49.883],"ti":[-19.633,-190.127,-25.485]},{"t":80.50390625,"s":[-147.831,2175.133,319.492]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":55,"ty":4,"nm":"Shape Layer 66","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":146,"s":[1440]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.749},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-50.842,-139.975,0],"ti":[92.088,-447.218,16.673]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.14},"t":36,"s":[-286.764,-1005.43,0],"to":[-92.088,447.218,-16.673],"ti":[41.246,-587.193,16.673]},{"t":100.2763671875,"s":[-534.238,2517.728,-100.037]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":56,"ty":4,"nm":"Shape Layer 65","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[59]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[-6.333,-132.667,0],"ti":[83.687,200.398,-334.198]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.153},"t":26,"s":[-31.605,-984.007,0],"to":[-63.434,-151.901,253.321],"ti":[-17.165,-409.463,-112.981]},{"t":111.3984375,"s":[283.153,3184.715,1150.251]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[3.444,9.757]],"o":[[0,0],[0,0],[0,0],[-4.941,-14]],"v":[[72,-317],[4,-279],[24.151,-217.936],[88.307,-269.617]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.671,0.345,0.345,0.5,0.655,0.247,0.461,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":57,"ty":4,"nm":"Shape Layer 64","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.836},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[64.5,-169.594,-9.152],"ti":[-113.79,-117.061,113.731]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.168},"t":27,"s":[393.395,-1205.571,-54.91],"to":[155.887,160.368,-155.806],"ti":[28,-314.6,160.297]},{"t":79,"s":[225.395,682.029,-1016.689]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":58,"ty":4,"nm":"Shape Layer 63","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-14,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.787},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[37,-178.375,-65.532],"ti":[-37,-452.343,-214.28]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.151},"t":28,"s":[228.395,-1258.256,-393.192],"to":[37,452.343,214.28],"ti":[0,-630.718,-279.812]},{"t":88,"s":[228.395,2526.051,1285.683]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":59,"ty":4,"nm":"Shape Layer 62","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[127.5,-230.723,46.629],"ti":[-127.5,230.723,-46.629]},{"t":38,"s":[771.395,-1572.344,279.773]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":60,"ty":4,"nm":"Shape Layer 61","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[259.979,-20.273,-16.168],"ti":[-259.979,20.273,16.168]},{"t":19,"s":[1566.267,-309.648,-97.005]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":61,"ty":4,"nm":"Shape Layer 60","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-10,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[110,309.06,41.862],"ti":[-110,-309.06,-41.862]},{"t":31,"s":[666.395,1666.35,251.17]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":62,"ty":4,"nm":"Shape Layer 59","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[111,205.897,-43.518],"ti":[-111,-205.897,43.518]},{"t":33,"s":[672.395,1047.376,-261.107]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":63,"ty":4,"nm":"Shape Layer 58","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.154296875,"s":[1800]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[14.845,244.879,-215.584],"ti":[-14.845,-244.879,215.584]},{"t":27.3662109375,"s":[95.463,1281.264,-1293.506]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":64,"ty":4,"nm":"Shape Layer 57","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[44.909,224.342,-47.459],"ti":[-44.909,-224.342,47.459]},{"t":58.259765625,"s":[287.74,1180.472,-284.755]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[84.659,-333.362],[4,-279],[45.665,-243.617],[119.735,-297.935]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":65,"ty":4,"nm":"Shape Layer 56","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-63.805,266.808,-0.572],"ti":[63.805,-266.808,0.572]},{"t":51,"s":[-364.539,1435.268,-3.43]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":66,"ty":4,"nm":"Shape Layer 55","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.877},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-126.036,154.294,-7.096],"ti":[142.905,-395.535,7.096]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.203},"t":39,"s":[-737.928,760.184,-42.576],"to":[-142.905,395.535,-7.096],"ti":[16.869,-241.241,0]},{"t":117,"s":[-839.144,2207.633,-42.576]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":67,"ty":4,"nm":"Shape Layer 54","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-233.472,311.697,-17.966],"ti":[233.472,-311.697,17.966]},{"t":24,"s":[-1382.547,1704.606,-107.798]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":68,"ty":4,"nm":"Shape Layer 53","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.762},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-130.292,21.146,0],"ti":[485.802,-51.233,12.363]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.14},"t":22,"s":[-763.467,-38.702,0],"to":[-485.802,51.233,-12.363],"ti":[355.509,-30.087,12.363]},{"t":57,"s":[-2896.522,141.82,-74.176]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":69,"ty":4,"nm":"Shape Layer 52","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":112.6337890625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.78},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-74.311,-43.837,33.544],"ti":[97.505,-287.852,-33.544]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.149},"t":34,"s":[-427.58,-428.6,201.262],"to":[-97.505,287.852,33.544],"ti":[23.194,-331.689,0]},{"t":102,"s":[-566.743,1561.534,201.262]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":70,"ty":4,"nm":"Shape Layer 51","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-6,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-124.762,-149.834,3.486],"ti":[124.762,149.834,-3.486]},{"t":26,"s":[-730.286,-1064.579,20.917]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":71,"ty":4,"nm":"Shape Layer 50","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-23,"s":[0]},{"t":97.8046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,184],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.77},"o":{"x":0.167,"y":0.167},"t":0,"s":[18.288,-165.578,0],"to":[-6.486,-154.267,17.582],"ti":[239.046,-131.632,15.802]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.142},"t":30,"s":[-20.629,-1091.18,105.493],"to":[-105.762,58.238,-6.991],"ti":[-17.583,-173.702,10.833]},{"t":76.796875,"s":[-107.774,1499.363,-67.253]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":72,"ty":4,"nm":"Shape Layer 49","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-12,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[382.643,-152.755,217.62],"ti":[-382.643,152.755,-217.62]},{"t":33,"s":[2302.253,-1104.538,1305.721]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":73,"ty":4,"nm":"Shape Layer 48","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-25,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.831},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[64.5,-93.5,0],"ti":[-64.5,-194.5,90.248]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.166},"t":25,"s":[393.395,-749.007,0],"to":[64.5,194.5,-90.248],"ti":[0,-288,90.248]},{"t":82.9755859375,"s":[393.395,978.993,-541.488]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":74,"ty":4,"nm":"Shape Layer 47","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.885},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[65.626,-110,4.223],"ti":[-147.468,-10.238,-26.905]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.19},"t":20,"s":[400.149,-848.007,25.34],"to":[361.407,25.09,65.936],"ti":[-121.292,-241.908,-61.713]},{"t":91.6259765625,"s":[1127.898,603.439,395.618]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":75,"ty":4,"nm":"Shape Layer 46","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-20,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.816},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[147.845,-83.5,22.596],"ti":[-263.557,-41.975,24.609]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.159},"t":24,"s":[893.467,-689.007,135.575],"to":[408.719,65.094,-38.164],"ti":[88.693,-68.281,27.128]},{"t":74,"s":[-447.485,1243.193,-405.659]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":76,"ty":4,"nm":"Shape Layer 45","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-16,"s":[0]},{"t":99.041015625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.875},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[217.737,24,189.679],"ti":[-563.841,-88.203,-189.679]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.201},"t":26,"s":[1312.816,-44.007,1138.074],"to":[563.841,88.203,189.679],"ti":[-346.104,-64.203,0]},{"t":76.796875,"s":[3389.44,341.208,1138.074]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":77,"ty":4,"nm":"Shape Layer 44","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":45.90234375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":105.2197265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.866},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[125,51.5,0],"ti":[-125,-69.882,151.388]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.196},"t":27.366,"s":[756.395,120.993,0],"to":[125,69.882,-151.388],"ti":[0,-18.382,151.388]},{"t":72,"s":[756.395,231.284,-908.328]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":78,"ty":4,"nm":"Shape Layer 43","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-18,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[149.808,146,73.2],"ti":[-149.808,-146,-73.2]},{"t":26,"s":[905.246,687.993,439.203]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":79,"ty":4,"nm":"Shape Layer 42","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-24,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[85.912,333.667,17.938],"ti":[-85.912,-333.667,-17.938]},{"t":24.89453125,"s":[521.87,1813.993,107.63]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[64,64,64],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":80,"ty":4,"nm":"Shape Layer 38","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[176.199,-89.062,26.907],"ti":[-176.199,89.062,-26.907]},{"t":31.0732421875,"s":[1063.587,-722.379,161.442]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":81,"ty":4,"nm":"Shape Layer 37","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":84.2109375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[128.5,0,0],"ti":[-128.5,0,0]},{"t":30,"s":[777.395,-188.007,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":82,"ty":4,"nm":"Shape Layer 36","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[125,65.5,0],"ti":[-125,-65.5,0]},{"t":22,"s":[756.395,204.993,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":83,"ty":4,"nm":"Shape Layer 35","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.782},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[88.036,74.962,0],"ti":[-235.276,83.183,-29.696]},{"i":{"x":0.833,"y":0.693},"o":{"x":0.167,"y":0.155},"t":30,"s":[534.609,261.763,0],"to":[283.126,-100.101,35.736],"ti":[32.848,188.294,-25.485]},{"t":123,"s":[-62.525,-2846.235,319.492]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":84,"ty":4,"nm":"Shape Layer 33","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":454.311,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":144.7646484375,"s":[59]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.737},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[42.167,-122.667,0],"ti":[-345.153,74.903,-198.64]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.151},"t":28,"s":[880.395,419.993,0],"to":[432.939,-93.953,249.162],"ti":[-17.165,-409.463,-112.981]},{"t":127,"s":[1064.167,5582.141,1150.251]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[3.444,9.757]],"o":[[0,0],[0,0],[0,0],[-4.941,-14]],"v":[[72,-317],[4,-279],[24.151,-217.936],[88.307,-269.617]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":85,"ty":4,"nm":"Shape Layer 32","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.841},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[21.5,-126.594,-9.152],"ti":[-109.46,-148.367,74.781]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.169},"t":25,"s":[756.395,396.429,-54.90999999999999],"to":[113.887,154.368,-77.806],"ti":[7.464,-520.935,-7.703]},{"t":124,"s":[711.613,3522.04,-8.689]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":86,"ty":4,"nm":"Shape Layer 31","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-14,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.843},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[37,-178.375,-65.532],"ti":[-48.635,-750.211,-214.28]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.169},"t":29,"s":[849.395,85.744,-393.192],"to":[48.635,750.211,214.28],"ti":[-11.635,-928.586,-279.812]},{"t":145,"s":[919.2079999999999,5657.262,1285.683]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":87,"ty":4,"nm":"Shape Layer 30","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":100.2763671875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[127.5,-230.723,46.629],"ti":[-127.5,230.723,-46.629]},{"t":25,"s":[1392.395,-228.34400000000002,279.773]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":88,"ty":4,"nm":"Shape Layer 29","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":91.6259765625,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[128.5,-20.273,-16.168],"ti":[-128.5,20.273,16.168]},{"t":44,"s":[1398.395,1034.352,-97.005]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":89,"ty":4,"nm":"Shape Layer 28","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-10,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[110,309.06,41.862],"ti":[-110,-309.06,-41.862]},{"t":41,"s":[1287.395,3010.35,251.17]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":90,"ty":4,"nm":"Shape Layer 27","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[111,205.897,-43.518],"ti":[-111,-205.897,43.518]},{"t":31,"s":[1293.395,2391.376,-261.107]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":91,"ty":4,"nm":"Shape Layer 26","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":89.154296875,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.154296875,"s":[1800]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[1,335.659,-215.584],"ti":[-1,-335.659,215.584]},{"t":58,"s":[633.395,3169.948,-1293.506]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":92,"ty":4,"nm":"Shape Layer 25","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1.057,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.rotation;"},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[-147.439,-471.248,-47.459],"ti":[147.439,471.248,47.459]},{"t":137.349609375,"s":[-878.24,-3015.497,-284.755]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[84.659,-333.362],[4,-279],[45.665,-243.617],[119.735,-297.935]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":93,"ty":4,"nm":"Shape Layer 24","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[0]},{"t":95.3330078125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[53.803,-474.312,-0.572],"ti":[-53.803,474.312,0.572]},{"t":71,"s":[329.216,-3033.882,-3.43]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":94,"ty":4,"nm":"Shape Layer 23","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[436.949,-283.973,0],"ti":[-436.949,283.973,0]},{"t":71,"s":[2628.09,-1891.843,0]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":95,"ty":4,"nm":"Shape Layer 22","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-11,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[127.5,-103.954,0],"ti":[-127.5,103.954,0]},{"t":34,"s":[771.395,-811.733,0]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":96,"ty":4,"nm":"Shape Layer 21","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":101.51171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[128.5,-30.183,0],"ti":[-128.5,30.183,0]},{"t":26,"s":[777.395,-369.106,0]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":97,"ty":4,"nm":"Shape Layer 20","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-4.765,"s":[0]},{"t":112.6337890625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.716},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[77.188,38.547,33.544],"ti":[-71.564,470.424,-33.544]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.136},"t":35,"s":[469.524,43.272,201.262],"to":[71.564,-470.424,33.544],"ti":[5.624,508.971,0]},{"t":100,"s":[435.782,-3010.554,201.262]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":98,"ty":4,"nm":"Shape Layer 19","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-6,"s":[0]},{"t":85.447265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[134.91,140.766,3.486],"ti":[-134.91,-140.766,-3.486]},{"t":21,"s":[815.857,656.587,20.917]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":99,"ty":4,"nm":"Shape Layer 18","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-23,"s":[0]},{"t":97.8046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.828},"o":{"x":0.167,"y":0.167},"t":0,"s":[6.395,-188.007,0],"to":[17.232,153.439,17.582],"ti":[-229.282,147.986,15.802]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.165},"t":21,"s":[109.785,732.626,105.493],"to":[101.442,-65.474,-6.991],"ti":[29.657,172.052,10.833]},{"t":76.796875,"s":[76.884,-1982.01,-729.705]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":100,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-12,"s":[0]},{"t":102.748046875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[382.643,-152.755,217.62],"ti":[-382.643,152.755,-217.62]},{"t":25,"s":[2923.253,239.462,1305.721]}],"ix":2},"a":{"a":0,"k":[56.08,-285.558,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7,-27],[35,7],[-41.906,32.862],[0,0]],"o":[[-7,27],[32,27],[23.167,-18.167],[0,0]],"v":[[72,-317],[4,-279],[91,-263.167],[107.167,-289.667]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.345,0.381,0.671,0.5,0.491,0.265,0.624,1,0.638,0.15,0.577],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":101,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-25,"s":[0]},{"t":81.740234375,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.829},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[64.5,-93.5,0],"ti":[-78.679,-339.952,90.248]},{"i":{"x":0.833,"y":0.714},"o":{"x":0.167,"y":0.165},"t":30,"s":[1014.395,594.993,0],"to":[78.679,339.952,-90.248],"ti":[-14.179,-433.452,90.248]},{"t":121,"s":[1099.471,3195.705,-541.488]}],"ix":2},"a":{"a":0,"k":[-262,-435,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[46,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.969,0.18,0.404,0.5,0.97,0.346,0.318,1,0.972,0.512,0.233],"ix":9}},"s":{"a":0,"k":[-49,0],"ix":5},"e":{"a":0,"k":[68,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-262,-435],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":102,"ty":4,"nm":"Shape Layer 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.822]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":102,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.828},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[65.626,-110,4.223],"ti":[-147.468,-10.238,-26.905]},{"i":{"x":0.833,"y":0.887},"o":{"x":0.167,"y":0.163},"t":28,"s":[1021.149,495.993,25.34],"to":[361.407,25.09,65.936],"ti":[-126.131,-151.254,-61.713]},{"t":71,"s":[1777.9360000000001,1403.514,395.618]}],"ix":2},"a":{"a":0,"k":[14.5,-433,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[27,96],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.5,-433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":103,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-20,"s":[0]},{"t":87.9189453125,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[147.845,-83.5,22.596],"ti":[-147.845,83.5,-22.596]},{"t":19,"s":[1514.467,654.993,135.575]}],"ix":2},"a":{"a":0,"k":[-180.917,-577,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[36,32],[26,-104]],"o":[[0,0],[-36,-32],[-11.157,44.627]],"v":[[-161,-650],[-193,-562],[-165,-504]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":104,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-16,"s":[0]},{"t":99.041015625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[393.671,83.751,189.679],"ti":[-393.671,-83.751,-189.679]},{"t":58,"s":[2989.419,1658.499,1138.074]}],"ix":2},"a":{"a":0,"k":[-45,-602,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-45,-602],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":105,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-2.293,"s":[0]},{"t":45.90234375,"s":[1080]}],"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-3.528,"s":[0]},{"t":105.2197265625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[249,109.5,0],"ti":[-249,-109.5,0]},{"t":68,"s":[2121.395,1812.9929999999997,0]}],"ix":2},"a":{"a":0,"k":[232.5,-595.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[11,67],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.17254901960784313,0.5529411764705883,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[232.5,-595.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":106,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-18,"s":[0]},{"t":86.6826171875,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[499.008,399.431,73.2],"ti":[-499.008,-399.431,-73.2]},{"t":96,"s":[3621.4449999999997,3552.5799999999995,439.20300000000003]}],"ix":2},"a":{"a":0,"k":[120,-142,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,44],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.22745098039215686,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[120,-142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":1,"ind":107,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-24,"s":[0]},{"t":94.09765625,"s":[1080]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[627.395,1155.993,0],"to":[535.305,103.127,17.938],"ti":[-535.305,-103.127,-17.938]},{"t":82,"s":[3839.226,1774.756,107.63000000000001]}],"ix":2},"a":{"a":0,"k":[116,-254,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[26,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.9490196078431372,0.9490196078431372,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.5372549019607843,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[116,-254],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":112,"st":-1,"bm":0},{"ddd":0,"ind":108,"ty":1,"nm":"Medium Gray-Blue Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[621,1344,0],"ix":2},"a":{"a":0,"k":[621,1344,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":1242,"sh":2688,"sc":"#272d41","ip":112,"op":112,"st":0,"bm":0,"hidden":0}],"markers":[]} diff --git a/app/src/main/res/values-h680dp/dimens.xml b/app/src/main/res/values-h680dp/dimens.xml new file mode 100644 index 0000000000..b8c8d265ed --- /dev/null +++ b/app/src/main/res/values-h680dp/dimens.xml @@ -0,0 +1,9 @@ + + + -10dp + 0dp + 16dp + 106dp + 67dp + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index fc5a1fd7b2..5d0fe290b1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,6 +5,8 @@ #0022D4 #1ACE80 + @color/backgroundLightGray + @color/accent #661ACE80 @color/darkGray6 @@ -14,6 +16,7 @@ #C4291C #FFB71B + #FFFFFF #8E8E93 #636366 #48484A @@ -21,7 +24,7 @@ #2C2C2E #1C1C1E - #F9F9FA + #FFFFFF #F9F9F9 #F4F5F6 #DE000000 @@ -29,10 +32,13 @@ #14181D + #F3F3F3 #F8F8FB + #DADADF + #D0D0D5 #C7C7CC #D1D1D6 - #F2F2F7 + #C9C9CD #1F000000 #14212121 @@ -43,6 +49,8 @@ #CBE4FF #E0E6FA + #55000000 + #1C1C1E #FFB71B #CA0F03 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index fd2c18a380..496ccebf23 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -35,4 +35,10 @@ 32sp 16dp + -30dp + -20dp + 8dp + 70dp + 37dp + diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 0ff017a1aa..c28a51e88b 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -1,5 +1,9 @@ - Top Up + Scan card failed. Try again + + Buy + Sell + Trade Default Compatibility Legacy @@ -10,25 +14,23 @@ Tangem Twin One wallet. Two cards. - This one that you are holding in your hands -and the other one with number %s.\n\nBoth cards can be used to extract funds from -this wallet. - + This one that you are holding in your hands and the other one with number #%s. Card %s of 2 Re-create twin wallet - This action is irreversible. You will not have access to the old wallet.\nThe wallet re-creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. - Re-create wallet - Step %s - Tap the #%s twin card - Preparing card - Creating wallet - The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. - Tap the card #%s - You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Tap the #%s twin card + Preparing card + Creating wallet + The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. + Tap the card #%s + You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. + You\'ve tapped the same card. To create twin wallet you need to tap a second twin card. + The twin address was successfully created //Common @@ -56,6 +58,7 @@ this wallet. Reason: %s. Do you want to send feedback? Are you having difficulty scanning your card? Please try to tap the card exactly as shown in the animation or request support. + I\'m okay Try again Request support Send feedback @@ -87,13 +90,17 @@ this wallet. ex. USD Coin ex. USDC Added + Remove token + Token icon //Details Blockchain Manage tokens Blockchains - Ethereum Tokens + Ethereum tokens + Binance Smart Chain tokens + Binance Chain tokens Copy Share + Add tokens @@ -156,6 +163,49 @@ this wallet. This card can’t be used to establish WalletConnect session The operation couldn\'t be completed The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. + Failed to establish WalletConnect session: timeout error. Please, try again later. + + This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. + + + Do you want to buy or sell crypto? Oops, the current version of the application is not ready to work with this card, please check for updates. + + + Welcome to Tangem + The safest way to buy, use and\n store cryptocurrency + Get a new card + + Activating card + How it works? + Receive crypto + + Create a wallet + Let’s generate all the keys on your card and create a secure wallet + Create wallet + + Top up your wallet + To get started, simply top up the card with any amount + To get started, simply top up the wallet with more than %s %s. + Buy crypto + Show the wallet’s address + Scan the address to top up your wallet + + Success! + Your crypto card is activated and ready to be used + Continue + Balance + Send only %s (%s) to this address. Sending any other currency will result in its irreversible loss. + Send only %s (%s) from %s network to this address. Sending any other currency will result in its irreversible loss. + + If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over. + The twinning process is partly complete. You can’t exit it now. + + Internal error: can\'t create wallet manager + + Blockchain is unreachable. Try later + + Ok, Got it! + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ae260a7854..286cddae59 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,8 +6,9 @@ @color/colorSecondary @color/darkGray6 true - @color/backgroundLightGray - false + true + true + @null + + + - + + + + + + - \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index f2dcf4b2fe..0ac373259b 100644 --- a/build.gradle +++ b/build.gradle @@ -10,9 +10,8 @@ buildscript { classpath "com.android.tools.build:gradle:${versions.build_gradle}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" - classpath 'com.google.gms:google-services:4.3.5' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' -// classpath 'com.google.firebase:perf-plugin:1.3.1' + classpath 'com.google.gms:google-services:4.3.8' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' } } diff --git a/dependencies.gradle b/dependencies.gradle index 32b0ea5280..43b263845b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.5.10', - build_gradle: '4.2.1', + kotlin : '1.5.30', + build_gradle: '7.0.2', ] diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f987e8eb91..06543032c9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -46,7 +46,7 @@ platform :android do 'versionName' => options[:versionName], }) gradle( - task: "assemble", + task: "bundle", build_type: "Release", properties: { 'versionCode' => options[:versionCode], diff --git a/fastlane/README.md b/fastlane/README.md index 7ef939cccf..5a974e90ae 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -12,7 +12,7 @@ Install _fastlane_ using ``` [sudo] gem install fastlane -NV ``` -or alternatively using `brew cask install fastlane` +or alternatively using `brew install fastlane` # Available Actions ## Android diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c6dab66ff6..8e5b6ad562 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip