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/app/build.gradle b/app/build.gradle index d94e9cbb30..0b53a8df5e 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,7 @@ android { buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"dev\"' } debug_beta { - initWith debug + initWith release versionNameSuffix "-beta" applicationIdSuffix ".debug" buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"' @@ -57,7 +57,6 @@ android { exclude 'lib/x86_64/freebsd/libscrypt.so' exclude 'lib/x86_64/linux/libscrypt.so' } - buildToolsVersion '29.0.3' } repositories { @@ -67,23 +66,25 @@ 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 "androidx.core:core-ktx:1.6.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-42' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-91' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-91' // 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..c2d4929889 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -81,9 +81,33 @@ + - + + + + + + + + + + + + + + + + + + + + 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/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 544e1d7cda..6f99403572 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -7,10 +7,7 @@ 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 @@ -20,7 +17,6 @@ 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 +26,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 +59,30 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + systemActions() store.state.globalState.feedbackManager?.updateAcivity(this) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 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(WalletConnectAction.RestoreSessions) } + 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..114c17c7cd 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,12 @@ 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.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R import org.rekotlin.StoreSubscriber @@ -37,28 +40,38 @@ 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 WalletConnectDialog.UnsupportedCard -> - dialog = SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, - context = context + 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( - titleRes = R.string.wallet_connect, - messageRes = R.string.wallet_connect_same_wcuri, - context = context + 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..a804d20624 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,26 @@ class IntentHandler { } } + private fun handleSellCurrencyCallback(intent: Intent?) { + 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 + )) + } + + } \ 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/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..02c794329c 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,9 +1,12 @@ 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 @@ -13,17 +16,35 @@ 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.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.OnboardingTwinsFragment 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.name) + } + } + } + transaction.replace(R.id.fragment_container, fragment, screen.name) + if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) transaction.commitAllowingStateLoss() } @@ -38,9 +59,22 @@ 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 -> HomeFragment() + AppScreen.OnboardingNote -> OnboardingNoteFragment() + AppScreen.OnboardingWallet -> OnboardingWalletFragment() + AppScreen.OnboardingTwins -> OnboardingTwinsFragment() + AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() AppScreen.Wallet -> WalletFragment() AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() @@ -49,7 +83,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { 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..d470ae2bf6 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 @@ -48,6 +50,7 @@ fun Picasso.loadCurrenciesIcon( imageView.colorFilter = null textView.text = null } + if (blockchain.isTestnet()) imageView.tint(R.color.tint) } }) } @@ -70,12 +73,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 +96,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..ab289dc6b6 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 @@ -37,7 +37,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..f5fd832f2b 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,9 @@ 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.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -8,8 +11,57 @@ 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 +} + +fun Store<*>.dispatchOpenUrl(url: String) { + store.dispatch(NavigationAction.OpenUrl(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..8f7402ff1f --- /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 ?: "Unknown exception during WalletManager update" + 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..7d2a956bde 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,15 @@ class PicassoHelper { val picasso = Picasso.Builder(application) .downloader(OkHttp3Downloader(getOkHttpForPicasso(application))) .build() + picasso.isLoggingEnabled = 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 +39,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..884cef3f94 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt @@ -0,0 +1,258 @@ +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.tangem_sdk_new.extensions.dpToPx + +/** +[REDACTED_AUTHOR] + */ +class LeapfrogWidget( + private val parentContainer: FrameLayout, + private val calculator: PropertyCalculator = PropertyCalculator(), +) { + private val foldAnimationDuration = 500L + private val leapAnimationDuration = 800L + private val leapBackAnimationDuration = 800L + + private val leapViews = mutableListOf() + + private var foldUnfoldProgress: Int = 100 + private var leapProgress: Int = 100 + private var leapBackProgress: Int = 100 + + var isFolded: Boolean = true + private set + + 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() { + isFolded = true + leapViews.forEach { it.initView() } + } + + fun fold(onEnd: () -> Unit = {}) { + val animator = foldAnimator() + if (animator == null) { + onEnd() + } else { + animator.doOnEnd { onEnd() } + animator.start() + } + } + + fun unfold(onEnd: () -> Unit = {}) { + val animator = unfoldAnimator() + if (animator == null) { + onEnd() + } else { + animator.doOnEnd { onEnd() } + animator.start() + } + } + + fun foldAnimator(): Animator? { + if (!canFoldUnfold() || isFolded) return null + + return createFoldUnfoldAnimator(true, foldAnimationDuration) + } + + fun unfoldAnimator(): Animator? { + if (!canFoldUnfold() || !isFolded) return null + + return createFoldUnfoldAnimator(false, foldAnimationDuration) + } + + private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, duration: Long): Animator { + val animatorsList = mutableListOf() + leapViews.forEach { + if (isFoldAnimation) { + it.fold() + animatorsList.add(it.view.foldAnimation(duration, it.state.properties)) + } else { + it.unfold() + animatorsList.add(it.view.unfoldAnimation(duration, it.state.properties)) + } + } + animatorsList.add(createProgressListener(duration) { + foldUnfoldProgress = it + if (it == 100) isFolded = isFoldAnimation + }) + + val animator = AnimatorSet() + animator.interpolator = AccelerateDecelerateInterpolator() + animator.playTogether(animatorsList.toList()) + return animator + } + + fun leap(onEnd: () -> Unit = {}) { + if (!canLeap()) { + onEnd() + return + } + + val duration = leapAnimationDuration + 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(onEnd: () -> Unit = {}) { + if (!canLeapBack()) { + onEnd() + return + } + + val duration = leapBackAnimationDuration + 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(isFolded, leapViews.map { it.state }) + + fun applyState(state: LeapfrogWidgetState) { + isFolded = state.isFolded + state.leapViewStates.forEach { state -> + leapViews.firstOrNull { it.index == state.index }?.applyState(state) + } + } + + 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 + isFolded -> false + leapBackInProgress() -> false + leapProgress < 70 -> false + else -> true + } + } + + private fun canLeapBack(): Boolean { + return when { + !viewIsFullFledged() -> false + isFolded -> 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 isFolded: Boolean, + val leapViewStates: List +) + +class PropertyCalculator( + val elevationFactor: Float = 1f, + val decreaseScaleFactor: Float = 0.1f, + val yTranslationFactor: Float = 20f, + 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..38c76819b3 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,9 @@ 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.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 @@ -19,6 +22,9 @@ fun appReducer(action: Action, state: AppState?): AppState { 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), sendState = SendScreenReducer.reduce(action, state.sendState), detailsState = DetailsReducer.reduce(action, state), 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..40e0c80077 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,13 @@ 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.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,6 +31,9 @@ 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 sendState: SendState = SendState(), val detailsState: DetailsState = DetailsState(), @@ -36,10 +45,14 @@ data class AppState( companion object { fun getMiddleware(): List> { return listOf( - logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware, - HomeMiddleware().homeMiddleware, + logMiddleware, navigationMiddleware, notificationsMiddleware, + GlobalMiddleware.handler, + HomeMiddleware.handler, + OnboardingNoteMiddleware.handler, + OnboardingWalletMiddleware.handler, + OnboardingOtherCardsMiddleware.handler, WalletMiddleware().walletMiddleware, - sendMiddleware, + SendMiddleware().sendMiddleware, DetailsMiddleware().detailsMiddleware, DisclaimerMiddleware().disclaimerMiddleware, TokensMiddleware().tokensMiddleware, 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..36e1a749a5 --- /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 + +/** +[REDACTED_AUTHOR] + */ +interface StateDialog + +sealed class AppDialog : StateDialog { + object ScanFailsDialog : AppDialog() + data class AddressInfoDialog( + val addressData: AddressData, + val onCopyAddress: () -> Unit, + val onExploreAddress: () -> Unit + ) : 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..297ba9fd40 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,52 @@ 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.MoonPayUserStatus 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 + + // 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 +69,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 GetMoonPayUserStatus : GlobalAction() { + data class Success(val moonPayUserStatus: MoonPayUserStatus) : 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..ea80ab463c 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,32 @@ 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 -val globalMiddleware: Middleware = { dispatch, appState -> +class GlobalMiddleware { + companion object { + val handler = globalMiddlewareHandler + } +} + +private val globalMiddlewareHandler: Middleware = { dispatch, appState -> { nextDispatch -> { action -> when (action) { @@ -22,8 +37,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) @@ -58,7 +72,35 @@ val globalMiddleware: Middleware = { dispatch, appState -> } is GlobalAction.UpdateFeedbackInfo -> { store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) + ?.setWalletsInfo(action.walletManagers) + } + is GlobalAction.GetMoonPayUserStatus -> { + val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey + if (apiKey != null) { + scope.launch { + val userStatusResponse = MoonpayService().getUserStatus(apiKey) + if (userStatusResponse is Result.Success) { + store.dispatchOnMain( + GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data) + ) + } + } + } + } + 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.card) + 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..29ec84876e 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,8 @@ 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.preferencesStorage import org.rekotlin.Action fun globalReducer(action: Action, state: AppState): GlobalState { @@ -12,6 +12,14 @@ fun globalReducer(action: Action, state: AppState): GlobalState { val globalState = state.globalState return when (action) { + 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 +27,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,36 +38,18 @@ 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 wallet = globalState.scanResponse?.card + ?.wallet(action.walletPublicKey) + ?.copy( + totalSignedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures + ) + val card = globalState.scanResponse?.card + wallet?.let { globalState.scanResponse.card.updateWallet(wallet) } if (card != null) { - globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card)) + globalState.copy(scanResponse = globalState.scanResponse.copy(card = card)) } else { globalState } @@ -73,6 +63,12 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.HideDialog -> { globalState.copy(dialog = null) } + is GlobalAction.GetMoonPayUserStatus.Success -> { + globalState.copy(moonPayUserStatus = action.moonPayUserStatus) + } + 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..e27641b854 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,33 @@ 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.MoonPayUserStatus 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 moonPayUserStatus: MoonPayUserStatus? = null, ) : StateType 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/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index 5cbdb915f6..331dc07e1a 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 @@ -1,15 +1,30 @@ package com.tangem.tap.common.redux.navigation +import android.view.View import androidx.fragment.app.FragmentActivity +import androidx.transition.TransitionSet 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 ActivityCreated(val activity: WeakReference) : NavigationAction() object ActivityDestroyed : NavigationAction() -} \ No newline at end of file +} + +data class FragmentShareTransition( + val shareElements: List, + val enterTransitionSet: TransitionSet, + val exitTransitionSet: TransitionSet +) + +data class ShareElement(val wView: WeakReference, val name: String) \ 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..31fe730615 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,34 @@ 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.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) } } } 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..80cd5e3260 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 @@ -27,5 +27,6 @@ private fun internalReduce(action: Action, state: AppState): NavigationState { } 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..4fa02150f0 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 @@ -5,12 +5,19 @@ import org.rekotlin.StateType import java.lang.ref.WeakReference data class NavigationState( - val backStack: List = listOf(AppScreen.Home), - val activity: WeakReference? = null + val backStack: List = listOf(AppScreen.Home), + val activity: WeakReference? = null ) : 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, + CreateTwinWalletWarning, CreateTwinWallet, + 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..e7ef08e026 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt @@ -0,0 +1,100 @@ +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() + + if (progressState == currentStateByView) return + + animateState(progressState) + viewSwitcher.showNext() + + } + + private fun animateState(state: ProgressState) { + when (state) { + ProgressState.Done, ProgressState.Error -> { + progressViewAnimation?.cancel() + } + ProgressState.Loading -> { + val animation = RotateAnimation( + 0f, 360f, + Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f + ) + animation.duration = 700 + animation.interpolator = AccelerateInterpolator() + animation.repeatCount = -1 + arrowView.startAnimation(animation) + progressViewAnimation = animation + } + } + } + + 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..02f0246c82 --- /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, Twin, 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..5253a9f91c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,103 +1,134 @@ 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.tap.domain.twins.isTangemTwin 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) { - suspendCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> - continuation.resume(result) - } + withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> + continuation.resume(result) } } + } 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 + tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTangemTwin()) 4 else null + } + + companion object { + val config = Config( + 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..c84e3f0d29 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,13 @@ 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) } data class ValidateTransactionErrors( @@ -60,7 +66,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..9133792893 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,23 @@ 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.domain.twins.getTwinCardNumber +import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction @@ -38,28 +40,17 @@ 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)) } } @@ -68,7 +59,7 @@ class TapWalletManager { suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) { Timber.d(wallet.getTokens().toString()) val currencies = wallet.getTokens() - .map { Currency.Token(it, wallet.blockchain) } + .map { Currency.Token(it) } .plus(Currency.Blockchain(wallet.blockchain)) loadFiatRate(fiatCurrency, currencies) } @@ -86,9 +77,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 +87,15 @@ 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 - )) + if (data.card.isTangemTwin()) { + data.card.getTwinCardNumber()?.let { + store.dispatch(WalletAction.TwinsAction.SetTwinCard(it, true)) } } - val blockchain = data.card.getBlockchain() + val blockchain = data.getBlockchain() if (blockchain == Blockchain.Ethereum || blockchain == Blockchain.EthereumTestnet) { store.dispatch(TokensAction.LoadCardTokens) @@ -115,14 +104,14 @@ class TapWalletManager { } } - private fun updateConfigManager(data: ScanNoteResponse) { + private 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,27 +120,27 @@ class TapWalletManager { } } - suspend fun loadData(data: ScanNoteResponse) { + suspend fun loadData(data: ScanResponse) { withContext(Dispatchers.Main) { - val artworkId = data.verifyResponse?.artworkInfo?.id + + store.dispatch(WalletAction.LoadCardInfo(data.card)) + when { - data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { + data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) - store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } - data.card.getStatus() == CardStatus.Empty || - (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> { + data.card.wallets.isEmpty() || + (data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> { store.dispatch(WalletAction.EmptyWallet) - store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } else -> { val config = store.state.globalState.configManager?.config ?: return@withContext - val blockchain = data.card.getBlockchain() + val blockchain = data.getBlockchain() val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) - if (blockchain != null && primaryWalletManager != null) { - val primaryToken = data.card.getToken() + if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { + val primaryToken = data.getPrimaryToken() store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) if (primaryToken != null) { @@ -170,31 +159,32 @@ class TapWalletManager { 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)) + val moonPayUserStatus = store.state.globalState.moonPayUserStatus + store.dispatch(WalletAction.LoadWallet( + allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true, + allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true, + )) store.dispatch(WalletAction.LoadFiatRate()) } } - store.dispatch(WalletAction.Warnings.CheckIfNeeded) } } private fun loadMultiWalletData( card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { - val presetTokens = primaryWalletManager?.presetTokens ?: emptySet() + val primaryTokens = primaryWalletManager?.cardTokens ?: emptySet() 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 + blockchains = setOf(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) store.dispatch(WalletAction.MultiWallet.SaveCurrencies( @@ -210,7 +200,7 @@ class TapWalletManager { } else { val blockchains = savedCurrencies.blockchains.toList() val walletManagers = if ( - presetTokens.isNotEmpty() && + primaryTokens.isNotEmpty() && primaryWalletManager != null && primaryBlockchain != null ) { val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain } @@ -226,14 +216,14 @@ class TapWalletManager { } } - suspend fun reloadData(data: ScanNoteResponse) { + suspend fun reloadData(data: ScanResponse) { withContext(Dispatchers.Main) { when { - data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { + data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) } - data.card.getStatus() == CardStatus.Empty || - (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> { + data.card.wallets.isEmpty() || + (data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> { store.dispatch(WalletAction.EmptyWallet) } else -> { @@ -242,7 +232,11 @@ class TapWalletManager { return@withContext } val config = store.state.globalState.configManager?.config ?: return@withContext - store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled)) + val moonpayUserStatus = store.state.globalState.moonPayUserStatus + store.dispatch(WalletAction.LoadWallet( + allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true, + allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true, + )) store.dispatch(WalletAction.LoadFiatRate()) } } @@ -254,23 +248,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..9003c9f4fc 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,25 @@ 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) - } + fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) + fun Card.isTangemWallet(): Boolean = tangemWalletBatches.contains(batchId) - fun Card.isMultiCurrencyWallet(): Boolean { - return multiCurrencyWalletsBatches.contains(cardData?.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 Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[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 +48,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.Binance, + "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() + && (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..e08ba924b5 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,25 @@ 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, + 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,10 +49,11 @@ 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) + config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed) } } @@ -60,38 +61,38 @@ 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( + 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( + 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..d0f99ccabc 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 @@ -15,7 +15,7 @@ class ConfigValueModel( val coinMarketCapKey: String, 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..d658847574 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 @@ -17,7 +16,7 @@ fun Blockchain.isNoAccountError(exception: Throwable): Boolean { fun Blockchain.amountToCreateAccount(token: Token? = null): Double? { return when (this) { Blockchain.Stellar -> if (token?.symbol == NODL) 1.5 else 1.toDouble() - Blockchain.XRP -> 20.toDouble() + Blockchain.XRP -> 10.toDouble() 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/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..99ed8231c4 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,13 @@ 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 +import com.tangem.tap.domain.twins.isTangemTwin fun WalletManagerFactory.makeWalletManagerForApp( card: Card, @@ -19,34 +20,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 (card.isTangemTwin() && 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 index 066158592c..43425eab48 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -3,51 +3,37 @@ 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.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.TangemError import com.tangem.common.extensions.toHexString +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.isExcluded -import com.tangem.tap.domain.TapWorkarounds.isMultiCurrencyWallet -import com.tangem.tap.domain.TapWorkarounds.isNote +import com.tangem.tap.domain.TapWorkarounds.isTangemNote import com.tangem.tap.domain.extensions.getSingleWallet -import com.tangem.tap.domain.extensions.getStatus +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.twins.TwinCardsManager -import com.tangem.tap.domain.twins.isTwinCard +import com.tangem.tap.domain.twins.isTangemTwin 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 { -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) { + 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 card = this.card ?: result.data val error = getErrorIfExcludedCard(card) if (error != null) { @@ -55,60 +41,42 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> 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) + if (card.isTangemTwin()) { + dealWithTwinCard(card, session, callback) + } else if (!card.isTangemNote() && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) { + createMissingWalletsIfNeeded(card, session, callback) } else { - callback(CompletionResult.Success(ScanNoteResponse(card, verifyResult.data))) + callback(CompletionResult.Success( + ScanResponse(card, ProductType.Other, session.environment.walletData))) } } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed())) - } } } } private fun createMissingWalletsIfNeeded( - card: Card, session: CardSession, verifyResponse: VerifyCardResponse, - callback: (result: CompletionResult) -> Unit + card: Card, session: CardSession, + callback: (result: CompletionResult) -> Unit, ) { - if (card.getStatus() == CardStatus.Empty) { - callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse))) + val walletData = session.environment.walletData + if (card.wallets.isEmpty()) { + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) return } val curvesPresent = card.wallets.map { it.curve } - val curvesToCreate = EllipticCurve.values().subtract(curvesPresent) +// val curvesToCreate = EllipticCurve.values().subtract(curvesPresent) + val curvesToCreate = card.supportedCurves.subtract(curvesPresent) if (curvesToCreate.isEmpty()) { - callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, walletData))) return } - val configs = curvesToCreate.map { curve -> - WalletConfig( - isReusable = null, prohibitPurgeWallet = null, curveId = curve, - signingMethods = null - ) - } - CreateWalletsTask(configs).run(session) { result -> + CreateWalletsTask(curvesToCreate.toList()).run(session) { result -> when (result) { is CompletionResult.Success -> - callback(CompletionResult.Success(ScanNoteResponse(result.data, verifyResponse))) + callback(CompletionResult.Success(ScanResponse(result.data, ProductType.Other, walletData))) is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } @@ -116,15 +84,15 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit + 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(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null))) return@run } val verified = TwinCardsManager.verifyTwinPublicKey( @@ -133,25 +101,28 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable - callback(CompletionResult.Success(ScanNoteResponse(card, null))) + callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, 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() + // Disable new multi-currency HD wallet cards on the old version of the app +// if (card.isMultiCurrencyWallet()) return UpdateAppToUseThisCard() return null } @@ -161,12 +132,4 @@ class ScanNoteTask(val card: Card? = null) : 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..3b3c6ea2ae --- /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.Twin -> 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 = card.getTangemNoteBlockchain()?.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..cdb63d456b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -0,0 +1,183 @@ +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.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.getTangemNoteBlockchain +import com.tangem.tap.domain.TapWorkarounds.isExcluded +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.domain.twins.isTangemTwin + +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 card.getTangemNoteBlockchain() ?: 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) + ) + } +} + +class ScanProductTask(val card: Card? = null) : CardSessionRunnable { + + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + ScanTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val card = this.card ?: result.data + + val error = getErrorIfExcludedCard(card) + if (error != null) { + callback(CompletionResult.Failure(error)) + return@run + } + + val commandProcessor = when { + card.isTangemNote() -> ScanNoteProcessor() + card.isTangemTwin() -> ScanTwinProcessor() + card.isTangemWallet() -> ScanWalletProcessor() + else -> ScanOtherCardsProcessor() + } + commandProcessor.proceed(card, session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun getErrorIfExcludedCard(card: Card): TangemError? { + if (card.isExcluded()) return TapSdkError.CardForDifferentApp + + return 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.Twin, null))) + return@run + } + val verified = TwinCardsManager.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.Twin, walletData, twinPublicKey.toHexString()) + callback(CompletionResult.Success(response)) + } else { + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, null))) + } + } + is CompletionResult.Failure -> + callback(CompletionResult.Success(ScanResponse(card, ProductType.Twin, 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..26aa55369c 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 blockchainsAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(Set::class.java, Blockchain::class.java) + ) private val tokensAdapter: JsonAdapter> = moshi.adapter( Types.newParameterizedType(Set::class.java, TokenDao::class.java) ) - private val blockchainsAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(Set::class.java, Blockchain::class.java) + private val obsoleteTokensAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(Set::class.java, ObsoleteTokenDao::class.java) ) fun loadCardCurrencies(cardId: String): CardCurrencies? { @@ -57,11 +61,20 @@ class CurrenciesRepository(val context: Application) { } private fun loadSavedTokens(cardId: String): Set { + val json = try { + context.readFileText(getFileNameForTokens(cardId)) + } catch (exception: Exception) { + return emptySet() + } + return try { - val json = context.readFileText(getFileNameForTokens(cardId)) tokensAdapter.fromJson(json)!!.map { it.toToken() }.toSet() } catch (exception: Exception) { - emptySet() + try { + obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() }.toSet() + } catch (exception: Exception) { + emptySet() + } } } @@ -93,79 +106,132 @@ 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)!!.map { 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" 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 ) { 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, ) { fun toCardCurrencies(): CardCurrencies { - return CardCurrencies(tokens = tokens.map { it.toToken() }.toSet(), - blockchains = blockchains) + return CardCurrencies( + tokens = tokens.map { it.toToken() }.toSet(), + blockchains = blockchains + ) } companion object { 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..f16a2591b2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt @@ -0,0 +1,102 @@ +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=" + + val AVAILABLE_TO_BUY: Set = setOf( + "ZRX", "AAVE", "ALGO", "AXS", "BAT", "BNB", "BUSD", "BTC", "BCH", "BTT", "ADA", "CELO", "CUSD", "LINK", "CHZ", "COMP", "ATOM", "DAI", "DASH", "MANA", "DGB", "DOGE", "EGLD", + "ENJ", "EOS", "ETC", "ETH", "KETH", "RINKETH", "FIL", "HBAR", "MIOTA", "KAVA", "KLAY", "LBC", "LTC", "LUNA", "MKR", "OM", "MATIC", "NANO", "NEAR", "XEM", "NEO", "NIM", "OKB", + "OMG", "ONG", "ONT", "DOT", "QTUM", "RVN", "RFUEL", "KEY", "SRM", "SOL", "XLM", "STMX", "SNX", "KRT", "UST", "USDT", "XTZ", "RUNE", "SAND", "TOMO", "AVA", "TRX", "TUSD", "UNI", + "USDC", "UTK", "VET", "WAXP", "WBTC", "XRP", "ZEC", "ZIL" + ) + + val AVAILABLE_TO_SELL: Set = setOf("BTC", "ETH", "BCH") + + 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..966fe4f1d1 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,17 @@ 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.TangemSdkError 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 class CreateSecondTwinWalletTask( private val firstPublicKey: String, @@ -17,25 +20,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())) return } session.setInitialMessage(preparingMessage) - PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response -> + 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)) @@ -48,10 +46,10 @@ class CreateSecondTwinWalletTask( private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { session.setInitialMessage(creatingWalletMessage) - CreateWalletTask().run(session) { result -> + 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 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..faa839cbe6 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 @@ -3,49 +3,60 @@ 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.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils +import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.readAssetAsString -import com.tangem.tap.domain.tasks.ScanNoteResponse +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 scanResponse: ScanResponse, context: Context) { - private val currentCardId: String = scanNoteResponse.card.cardId + private val currentCardId: String = scanResponse.card.cardId private var currentCardPublicKey: String? = null private var secondCardPublicKey: String? = null private val issuerKeyPair: KeyPair = getIssuerKeys( - context, scanNoteResponse.card.issuerPublicKey!!.toHexString() + context, scanResponse.card.issuer.publicKey.toHexString() ) suspend fun createFirstWallet(message: Message): SimpleResult { val response = tangemSdkManager.runTaskAsync( - CreateFirstTwinWalletTask(), currentCardId, message + CreateFirstTwinWalletTask(), currentCardId, message ) when (response) { is CompletionResult.Success -> { - currentCardPublicKey = response.data.walletPublicKey.toHexString() + currentCardPublicKey = response.data.wallet.publicKey.toHexString() return SimpleResult.Success } - is CompletionResult.Failure -> return SimpleResult.failure(response.error) + is CompletionResult.Failure -> { + (response.error as? TangemSdkError)?.let { error -> + FirebaseAnalyticsHandler.logCardSdkError( + error, + FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + card = scanResponse.card + ) + } + return SimpleResult.failure(response.error) + } } } suspend fun createSecondWallet( - initialMessage: Message, - preparingMessage: Message, - creatingWalletMessage: Message + initialMessage: Message, + preparingMessage: Message, + creatingWalletMessage: Message, ): SimpleResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, @@ -54,37 +65,53 @@ 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() + secondCardPublicKey = response.data.wallet.publicKey.toHexString() return SimpleResult.Success } - is CompletionResult.Failure -> return SimpleResult.failure(response.error) + is CompletionResult.Failure -> { + (response.error as? TangemSdkError)?.let { error -> + FirebaseAnalyticsHandler.logCardSdkError( + error, + FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + card = scanResponse.card + ) + } + return SimpleResult.failure(response.error) + } } } - 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 = scanResponse.card + ) + } + Result.failure(response.error) + } } } companion object { fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { - if (issuerData.size < 65) return false + if (issuerData.size < 65 || cardWalletPublicKey == null) 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)) + return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) } private fun getIssuerKeys(context: Context, publicKey: String): KeyPair { diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt new file mode 100644 index 0000000000..63f891eb4d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt @@ -0,0 +1,152 @@ +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.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 +) { + + init { + if (leapfrogWidget.getViewsCount() != 2) throw UnsupportedOperationException() + } + + fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)), + createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)) + ) + leapfrogWidget.fold { animator.start() } + } + + fun toLeapfrog(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)), + createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)) + ) + leapfrogWidget.fold { + animator.doOnEnd { + leapfrogWidget.initViews() + leapfrogWidget.unfold() + } + animator.start() + } + } + + fun toActivate(animate: Boolean = true, onEnd: () -> Unit = {}) { + val animator = createAnimator(animate, onEnd) + animator.playTogether( + createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)), + createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)) + ) + animator.start() + } + + private fun createAnimator(animate: Boolean, onEnd: () -> Unit): AnimatorSet { + return AnimatorSet().apply { + duration = if (animate) 400 else 0 + doOnEnd { onEnd() } + } + } + + 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 = -180f, + yTranslation = -350f, + rotation = -3f, + elevation = 1f, + scale = 1.1f, + ) + } + TwinCardNumber.Second -> { + TwinsCardProperties( + xTranslation = 180f, + yTranslation = 10f, + rotation = -3f, + elevation = 0f, + scale = 1.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.4f, + ) + } + + private fun getLeapViewByCardNumber(cardNumber: TwinCardNumber): LeapView { + return when (cardNumber) { + TwinCardNumber.First -> leapfrogWidget.getViewByPosition(0) + TwinCardNumber.Second -> leapfrogWidget.getViewByPosition(1) + } + } +} + +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..ebcbb5ff2b 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,10 +1,6 @@ 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 class TwinsHelper { companion object { @@ -37,22 +33,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 +46,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 +55,14 @@ enum class TwinCardNumber(val number: Int) { } } -fun Card.isTwinCard(): Boolean { - return this.cardData?.productMask?.contains(Product.TwinCard) == true +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/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 756d836287..74b37f90f4 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 @@ -3,10 +3,11 @@ 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 @@ -17,13 +18,13 @@ 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 card: Card, + val scanResponse: ScanResponse, + val wallets: List, + val isCreatingTwinWalletAllowed: Boolean?, + val cardTou: CardTou, + val fiatCurrencyName: FiatCurrencyName, + val fiatCurrencies: List? = null, ) : DetailsAction() object ShowDisclaimer : DetailsAction() @@ -80,7 +81,7 @@ sealed class DetailsAction : Action { } data class LaunchThirdStep(val message: Message) : CreateTwinWalletAction() { - data class Success(val scanNoteResponse: ScanNoteResponse) : CreateTwinWalletAction() + data class Success(val scanResponse: ScanResponse) : CreateTwinWalletAction() object Failure : CreateTwinWalletAction() } } @@ -93,6 +94,8 @@ sealed class DetailsAction : Action { } sealed class ManageSecurity : DetailsAction() { + data class CheckCurrentSecurityOption(val cardId: String?) : 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..15eca00858 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,7 +1,10 @@ package com.tangem.tap.features.details.redux -import com.tangem.commands.common.network.Result 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.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -81,15 +84,23 @@ class DetailsMiddleware { store.dispatch(NavigationAction.PopBackTo()) } is DetailsAction.EraseWallet.Confirm -> { + val card = store.state.detailsState.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.card + ) + } + } } } } @@ -113,6 +124,21 @@ class DetailsMiddleware { class ManageSecurityMiddleware { fun handle(action: DetailsAction.ManageSecurity) { when (action) { + is DetailsAction.ManageSecurity.CheckCurrentSecurityOption -> { + scope.launch { + when (val response = tangemSdkManager.checkUserCodes(action.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)) } @@ -148,8 +174,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.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..d2bb330e40 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,17 @@ 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.domain.twins.getTwinCardNumber +import com.tangem.tap.domain.twins.isTangemTwin 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.features.wallet.models.hasPendingTransactions import org.rekotlin.Action import java.util.* @@ -47,21 +46,10 @@ private fun internalReduce(action: Action, state: AppState): 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()) { + val twinsState = if (action.card.isTangemTwin()) { CreateTwinWalletState( - scanResponse = action.scanNoteResponse, - twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId), + scanResponse = action.scanResponse, + twinCardNumber = action.card.getTwinCardNumber(), createTwinWallet = null, showAlert = false, allowRecreatingWallet = action.isCreatingTwinWalletAllowed @@ -76,7 +64,6 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta appCurrencyState = AppCurrencyState( action.fiatCurrencyName ), - securityScreenState = SecurityScreenState(currentOption = securityOption), createTwinWalletState = twinsState, cardTermsOfUseUrl = action.cardTou.getUrl(action.card) ) @@ -85,13 +72,10 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta 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 notAllowedByAnyWallet = state.card?.wallets?.any { it.settings.isPermanent } ?: false + val notAllowedByCard = notAllowedByAnyWallet || state.card?.isWalletDataSupported == true 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 @@ -142,6 +126,20 @@ private fun handleSecurityAction( action: DetailsAction.ManageSecurity, state: DetailsState ): DetailsState { return when (action) { + is DetailsAction.ManageSecurity.SetCurrentOption -> { + val securityOption = when { + action.userCodes.isAccessCodeSet -> { + SecurityOption.AccessCode + } + action.userCodes.isPasscodeSet -> { + SecurityOption.PassCode + } + else -> { + SecurityOption.LongTap + } + } + state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption)) + } is DetailsAction.ManageSecurity.OpenSecurity -> { if (state.card?.isStart2Coin == true) { return state.copy(securityScreenState = state.securityScreenState?.copy( @@ -149,13 +147,10 @@ private fun handleSecurityAction( selectedOption = state.securityScreenState.currentOption )) } - if (state.card?.isPin2Default == null) { - return state.copy(securityScreenState = state.securityScreenState?.copy( - allowedOptions = EnumSet.noneOf(SecurityOption::class.java) - )) - } - val allowedSecurityOptions = prepareAllowedSecurityOptions(state.card) + val allowedSecurityOptions = prepareAllowedSecurityOptions( + state.card, state.securityScreenState?.currentOption + ) state.copy(securityScreenState = state.securityScreenState?.copy( allowedOptions = allowedSecurityOptions, selectedOption = state.securityScreenState.currentOption @@ -176,40 +171,38 @@ 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.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 @@ -218,6 +211,6 @@ private fun prepareAllowedSecurityOptions(card: Card): EnumSet { 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 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..bf16dd3674 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,7 +2,7 @@ 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.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 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 index fa81ea9a18..1a6c05c2b1 100644 --- 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 @@ -48,7 +48,7 @@ class CreateTwinWalletMiddleware { store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) } is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> { - store.state.globalState.scanNoteResponse?.let { + store.state.globalState.scanResponse?.let { twinsManager = TwinCardsManager(it, action.context) } scope.launch { @@ -106,7 +106,7 @@ class CreateTwinWalletMiddleware { } is DetailsAction.CreateTwinWalletAction.LaunchThirdStep.Success -> { scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(action.scanNoteResponse) + store.state.globalState.tapWalletManager.onCardScanned(action.scanResponse) } store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) 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 index 488ac431bc..22c96dd0bf 100644 --- 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 @@ -1,15 +1,15 @@ package com.tangem.tap.features.details.redux.twins -import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.tasks.product.ScanResponse 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 + val scanResponse: ScanResponse?, + val step: CreateTwinWalletStep = CreateTwinWalletStep.FirstStep, + val twinCardNumber: TwinCardNumber?, + val createTwinWallet: CreateTwinWallet?, + val showAlert: Boolean, + val allowRecreatingWallet: Boolean? = null ) enum class CreateTwinWalletStep { FirstStep, SecondStep, ThirdStep } 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..640cc47cf0 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 @@ -12,7 +12,7 @@ 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.twins.getTwinCardIdForUser -import com.tangem.tap.domain.twins.isTwinCard +import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.SecurityOption @@ -68,7 +68,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber + 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..ec7a45dd3d 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 @@ -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..6007d61e23 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 @@ -13,8 +13,7 @@ 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.stripZeroPlainString import com.tangem.tap.domain.TapWorkarounds import timber.log.Timber @@ -176,11 +175,10 @@ 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) { 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..d7b0c6d941 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,26 +1,62 @@ 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_bottom_action_views.* +import kotlinx.android.synthetic.main.layout_onboarding_home.* +import java.lang.ref.WeakReference -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() + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home + + 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(WeakReference(imv_front_card), imv_front_card.transitionName)), + 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 @@ -28,49 +64,15 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber { - 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 onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val inflater = TransitionInflater.from(requireContext()) - exitTransition = inflater.inflateTransition(R.transition.fade) + 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..46e097943a 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,83 @@ 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.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.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.GetMoonPayUserStatus) + 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 -> + 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.state.globalState.tapWalletManager.onCardScanned(scanResponse) } + 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..0b5b229bc4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -0,0 +1,44 @@ +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.redux.AppDialog +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) + stateDialog.onCopyAddress() + } + btn_fl_explore.setOnClickListener { + stateDialog.onExploreAddress() + } + } +} \ 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..38b2517ad1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -0,0 +1,27 @@ +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 = if (response.card.hasWallets()) + preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true + + fun whereToNavigate(scanResponse: ScanResponse): AppScreen { + return when (scanResponse.productType) { + ProductType.Note -> AppScreen.OnboardingNote + ProductType.Wallet -> AppScreen.OnboardingWallet + ProductType.Twin -> 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..2b4894d5b9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -0,0 +1,128 @@ +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.UrlBitmapLoader +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.Artwork +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.persistence.UsedCardsPrefStorage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** +[REDACTED_AUTHOR] + */ +class OnboardingManager( + var scanResponse: ScanResponse, + val usedCardsPrefStorage: UsedCardsPrefStorage, +) { + + var cardInfo: Result? = null + private set + + 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 { + return 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 currency = Currency.Blockchain(walletManager.wallet.blockchain) + val balance = if (valueOfAmount.isZero()) { + OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions(), currency) + } else { + OnboardingWalletBalance.done(valueOfAmount, false, currency) + } + 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 -> OnboardingWalletBalance.criticalError(error) + } + } + } + } + + 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, currency: Currency.Blockchain): OnboardingWalletBalance = + OnboardingWalletBalance(value, currency, hasTransactions, 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..7cdbff7733 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt @@ -0,0 +1,46 @@ +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.send.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 onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + configureTransitions() + } + + 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..6bf7ca96bf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -0,0 +1,184 @@ +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.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.postUi +import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget +import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.note.redux.* +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_bottom_action_views.* +import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.view_onboarding_progress.* +import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* + +/** +[REDACTED_AUTHOR] + */ +class OnboardingNoteFragment : BaseOnboardingFragment() { + + private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget + private var currentStep: OnboardingNoteStep = OnboardingNoteStep.None + + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + toolbar.setTitle(R.string.onboarding_title) + btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) + val resources = AndroidResources( + OnboardingStringResources( + R.string.copy_toast_msg + ) + ) + + store.dispatch(OnboardingNoteAction.LoadCardArtwork) + store.dispatch(OnboardingNoteAction.SetResources(resources)) + 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 + + state.cardArtwork?.let { imv_front_card.swapToBitmapDrawable(it.artwork) } + 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) + currentStep = state.currentStep + } + + private fun setBalance(state: OnboardingNoteState) { + if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return + + val balanceValue = state.walletBalance.value.stripZeroPlainString() + val currency = state.walletBalance.currency.currencySymbol + tv_balance_value.text = "$balanceValue $currency" + } + + 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) + } + + private fun setupTopUpWalletState(state: OnboardingNoteState) { + btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) + btn_main_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.TopUp) + } + + btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) + btn_alternative_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) + } + + 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) { + postUi(300) { + 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.currentStep == 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..798f8f4136 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.features.onboarding.products.note.redux + +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.features.wallet.redux.Artwork +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingNoteAction : Action { + // from user, ui + data class SetResources(val resources: AndroidResources) : OnboardingNoteAction() + object LoadCardArtwork : OnboardingNoteAction() + object CreateWallet : OnboardingNoteAction() + object TopUp : OnboardingNoteAction() + object ShowAddressInfoDialog : OnboardingNoteAction() + + // from redux + class SetArtworkUrl(val artwork: Artwork) : 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() + } + + 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..0a925dc7ee --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -0,0 +1,151 @@ +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.TapError +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.extensions.makePrimaryWalletManager +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 artwork = onboardingManager.loadArtwork() + withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artwork)) } + } + } + 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(500) { 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( + state = ProgressState.Loading, + error = null, + criticalError = null + ) + store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading)) + + scope.launch { + val loadedBalance = onboardingManager.updateBalance(walletManager) + loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } + delay(if (isLoadedBefore) 0 else 300) + withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } + } + + } + is OnboardingNoteAction.Balance.Set -> { + if (action.balance.balanceIsToppedUp()) { + store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done)) + } + } + is OnboardingNoteAction.ShowAddressInfoDialog -> { + val addressData = noteState.walletManager?.getAddressData() ?: return + val addressWasCopied = noteState.resources.strings.addressWasCopied + val appDialog = AppDialog.AddressInfoDialog( + addressData, + onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, + onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } + ) + store.dispatchDialogShow(appDialog) + } + is OnboardingNoteAction.TopUp -> { + val topUpUrl = noteState.walletManager?.getToUpUrl() ?: return + store.dispatchOpenUrl(topUpUrl) + } + OnboardingNoteAction.Done -> { + store.dispatch(GlobalAction.Onboarding.Stop) + scope.launch { + globalState.tapWalletManager.onCardScanned(scanResponse) + 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..03415eec84 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt @@ -0,0 +1,46 @@ +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(cardArtwork = action.artwork) + } + is OnboardingNoteAction.SetWalletManager -> { + state = state.copy(walletManager = action.walletManager) + } + is OnboardingNoteAction.SetResources -> { + state = state.copy(resources = action.resources) + } + is OnboardingNoteAction.Balance.Set -> { + state = state.copy(walletBalance = action.balance) + } + 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..d4f1c2bcb7 --- /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.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.features.wallet.redux.Artwork +import org.rekotlin.StateType + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingNoteState( + val walletManager: WalletManager? = null, + val resources: AndroidResources = AndroidResources(), + // UI + val cardArtwork: Artwork? = null, + val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(), + val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, + val steps: List = OnboardingNoteStep.values().toList(), + val showConfetti: Boolean = false, +) : StateType { + + val progress: Int + get() = steps.indexOf(currentStep) +} + +enum class OnboardingNoteStep { + None, CreateWallet, TopUpWallet, Done +} + +data class AndroidResources( + val strings: OnboardingStringResources = OnboardingStringResources() +) + +data class OnboardingStringResources( + val addressWasCopied: Int = -1 +) \ 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..ebdbe7d4c1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -0,0 +1,102 @@ +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.tangem.tap.common.extensions.getDrawableCompat +import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.note.swapToBitmapDrawable +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_bottom_action_views.* +import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.view_onboarding_progress.* + +/** +[REDACTED_AUTHOR] + */ +class OnboardingOtherCardsFragment : BaseOnboardingFragment() { + + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + 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 + + state.cardArtwork?.let { imv_front_card.swapToBitmapDrawable(it.artwork) } + 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) + } + + 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..44bdec53c8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import com.tangem.tap.features.wallet.redux.Artwork +import org.rekotlin.Action + +/** +[REDACTED_AUTHOR] + */ +sealed class OnboardingOtherCardsAction : Action { + // from user, ui + object LoadCardArtwork : OnboardingOtherCardsAction() + object CreateWallet : OnboardingOtherCardsAction() + + // from redux + class SetArtworkUrl(val artwork: Artwork) : 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..e775176f10 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -0,0 +1,91 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import com.tangem.common.CompletionResult +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 artwork = onboardingManager.loadArtwork() + withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artwork)) } + } + } + 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(500) { 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 { + globalState.tapWalletManager.onCardScanned(onboardingManager.scanResponse) + 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..a6313b9199 --- /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(cardArtwork = action.artwork) + } + 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..b306101506 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.features.onboarding.products.otherCards.redux + +import com.tangem.tap.features.wallet.redux.Artwork +import org.rekotlin.StateType + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingOtherCardsState( + // UI + val cardArtwork: Artwork? = 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/wallet/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt new file mode 100644 index 0000000000..d0463040bd --- /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.onboarding.products.wallet.redux.OnboardingWalletState +import com.tangem.tap.features.send.BaseStoreFragment +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/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt index fb46ed08da..bb3046db5e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt @@ -4,11 +4,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar -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.* @@ -26,11 +24,10 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { 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) @@ -43,7 +40,7 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) } + toolbar?.setNavigationOnClickListener { activity?.onBackPressed() } } override fun onStart() { @@ -54,6 +51,7 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { override fun onStop() { storeSubscribersList.forEach { store.unsubscribe(it) } + storeSubscribersList.clear() super.onStop() } 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..b0b9263034 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 @@ -132,11 +132,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 +159,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/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 7dcececcda..26d2f8dd27 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 @@ -5,9 +5,11 @@ 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.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -19,7 +21,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 +41,43 @@ 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, ) { 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 +93,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,7 +105,7 @@ private fun verifyAndSendTransaction( } else -> { sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress, - sendState.transactionExtrasState, card, dispatch) + sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch) } } } @@ -97,9 +118,10 @@ private fun sendTransaction( 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)) } @@ -116,9 +138,9 @@ 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 ) ) } @@ -127,9 +149,18 @@ private fun sendTransaction( 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 +209,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 +236,7 @@ private fun sendTransaction( } } } - dispatch(SendAction.ChangeSendButtonState(SendButtonState.ENABLED)) + dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) } } } 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/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index d17531a84a..4f2e4e1a6d 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 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..bb675cf3fe 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 @@ -7,6 +7,7 @@ import android.text.method.DigitsKeyListener import android.view.View import android.view.inputmethod.EditorInfo import android.widget.EditText +import androidx.activity.OnBackPressedCallback import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener import androidx.recyclerview.widget.LinearLayoutManager @@ -20,6 +21,7 @@ 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.* @@ -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 @@ -60,6 +63,23 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private val sendSubscriber = SendStateSubscriber(this) private lateinit var keyboardObserver: KeyboardObserver + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + 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 onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -71,6 +91,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { setupAmountLayout() setupFeeLayout() setupWarningMessages() + store.dispatch(SendActionUi.CheckIfTransactionDataWasProvided) } private fun initSendButtonStates() { @@ -250,7 +271,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 } 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..2471b257de 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 @@ -10,7 +10,6 @@ import androidx.core.text.bold 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 @@ -116,20 +115,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 +143,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 +172,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 { @@ -198,6 +191,14 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber 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) { 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..e8afbe2d62 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,6 +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.domain.TapWorkarounds.isTestCard import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem import com.tangem.tap.store import org.rekotlin.Middleware @@ -13,9 +14,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/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt index 7767d74b32..918dd2916a 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 @@ -10,8 +10,10 @@ 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.wallet.redux.WalletAction import com.tangem.tap.store @@ -84,17 +86,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 +146,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)) @@ -175,11 +178,21 @@ sealed class CurrencyListItem { tokens: List ): List { val blockchainsTitle = R.string.add_tokens_subtitle_blockchains - val tokensTitle = R.string.add_tokens_subtitle_tokens + 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(tokensTitle)) + - tokens.map { TokenListItem(it) } + listOf(TitleListItem(ethereumTokensTitle)) + + ethereumTokens.map { TokenListItem(it) } + + listOf(TitleListItem(bscTokensTitle)) + + bscTokens.map { TokenListItem(it) } + + listOf(TitleListItem(binanceTokensTitle)) + + binanceTokens.map { TokenListItem(it) } } } } 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..492513e02c 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,6 +3,7 @@ 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 data class PendingTransaction( @@ -57,4 +58,12 @@ 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 recentTransactions.toPendingTransactions(address).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..6fd84ec7cd 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,7 +3,7 @@ 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 @@ -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 allowToSell: Boolean? = null, val allowToBuy: Boolean? = 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,8 +133,16 @@ 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() 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..d078cd0526 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,10 +5,13 @@ 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.toSendableAmounts +import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.toPendingTransactions @@ -32,7 +35,9 @@ data class WalletState( val cardCurrency: CryptoCurrencyName? = null, val selectedWallet: Currency? = null, val primaryBlockchain: Blockchain? = null, - val primaryToken: Token? = null + val primaryToken: Token? = null, + val isTestnet: Boolean = false, + val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability() ) : StateType { val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null @@ -49,17 +54,7 @@ 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? { @@ -98,7 +93,7 @@ data class WalletState( ?: return true if (walletData.currency is Currency.Blockchain && - walletManager.presetTokens.isNotEmpty() + walletManager.cardTokens.isNotEmpty() ) { return false } @@ -148,11 +143,11 @@ sealed class WalletDialog: StateDialog { ) : WalletDialog() 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 } @@ -171,8 +166,9 @@ data class AddressData( val type: AddressType, val shareUrl: String, val exploreUrl: String, - val qrCode: Bitmap? = null -) +) { + val qrCode: Bitmap by lazy { shareUrl.toQrCode() } +} data class Artwork( val artworkId: String, @@ -186,13 +182,20 @@ 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, +) + +data class TradeCryptoAvailability( + val sellingAllowed: Boolean = false, + val buyingAllowed: Boolean = false, + val availableToSell: Set = TradeCryptoHelper.AVAILABLE_TO_SELL, ) data class TwinCardsState( @@ -202,24 +205,21 @@ data class TwinCardsState( ) 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? = null ) { 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 +229,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..026cc26e4d 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 @@ -57,14 +57,19 @@ class MultiWalletMiddleware { store.dispatch(WalletAction.LoadWallet(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 +79,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 +95,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 +116,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 +142,7 @@ class MultiWalletMiddleware { ) store.dispatch( WalletAction.MultiWallet.AddTokens( - walletManager.presetTokens.toList() + walletManager.cardTokens.toList() ) ) } @@ -144,23 +155,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 +185,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/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index f1e3d5c0ae..3b3a4f31c1 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,10 +5,14 @@ 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.dispatchOnMain import com.tangem.tap.common.extensions.shareText import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -18,16 +22,16 @@ 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.launch import org.rekotlin.Action import org.rekotlin.Middleware class WalletMiddleware { - private val topUpMiddleware = TopUpMiddleware() + private val tradeCryptoMiddleware = TradeCryptoMiddleware() private val twinsMiddleware = TwinsMiddleware() private val warningsMiddleware = WarningsMiddleware() private val multiWalletMiddleware = MultiWalletMiddleware() @@ -38,12 +42,11 @@ class WalletMiddleware { val globalState = state()?.globalState val walletState = state()?.walletState when (action) { - is WalletAction.TopUpAction -> topUpMiddleware.handle(action) + is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action) is WalletAction.TwinsAction -> twinsMiddleware.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) { @@ -93,7 +96,7 @@ class WalletMiddleware { is WalletAction.CreateWallet -> { if (walletState?.twinCardsState != null) { store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning( - globalState?.scanNoteResponse?.card?.cardId?.let { + globalState?.scanResponse?.card?.cardId?.let { TwinsHelper.getTwinCardNumber(it) }, CreateTwinWallet.CreateWallet @@ -101,22 +104,25 @@ class WalletMiddleware { } else { scope.launch { val result = tangemSdkManager.createWallet( - globalState?.scanNoteResponse?.card?.cardId + globalState?.scanResponse?.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) + globalState?.scanResponse?.copy(card = result.data) scanNoteResponse?.let { globalState.tapWalletManager.onCardScanned(scanNoteResponse) } } + is CompletionResult.Failure -> { + (result.error as? TangemSdkError)?.let { error -> + FirebaseAnalyticsHandler.logCardSdkError( + error, + FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + card = store.state.detailsState.card + ) + } + } } } } @@ -139,22 +145,38 @@ class WalletMiddleware { } is WalletAction.Scan -> { + store.dispatch(HomeAction.ShouldScanCardOnResume(true)) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } + is WalletAction.LoadCardInfo -> { 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) - } + val response = OnlineCardVerifier().getCardInfo( + action.card.cardId, action.card.cardPublicKey + ) + when (response) { + is Result.Success -> { + store.dispatchOnMain( + WalletAction.SetArtworkId(response.data.artwork?.id) + ) + store.dispatchOnMain( + WalletAction.LoadArtwork(action.card, response.data.artwork?.id) + ) + store.dispatchOnMain( + GlobalAction.SetIfCardVerifiedOnline(response.data.passed) + ) + } + is Result.Failure -> { + store.dispatchOnMain( + GlobalAction.SetIfCardVerifiedOnline(false) + ) } } + store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded) } } is WalletAction.LoadData -> { scope.launch { - val scanNoteResponse = globalState?.scanNoteResponse + val scanNoteResponse = globalState?.scanResponse ?: return@launch if (!walletState?.wallets.isNullOrEmpty()) { globalState.tapWalletManager.reloadData(scanNoteResponse) @@ -164,7 +186,7 @@ class WalletMiddleware { } } is NetworkStateChanged -> { - globalState?.scanNoteResponse?.let { scanNoteResponse -> + globalState?.scanResponse?.let { scanNoteResponse -> store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) 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..39f84c1b09 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.twins.isTangemTwin 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() @@ -82,18 +81,21 @@ class WarningsMiddleware { } private fun showCardWarningsIfNeeded(globalState: GlobalState?) { - globalState?.scanNoteResponse?.card?.let { card -> + globalState?.scanResponse?.card?.let { card -> globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local) + if (card.isTestCard) { + addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true) + return@let + } + showWarningLowRemainingSignaturesIfNeeded(card) - if (card.getType() != CardType.Release) { + if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) { addWarningMessage(WarningMessagesManager.devCardWarning()) - } else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) { + } else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) { checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) } } - if (card.getType() == CardType.Release) { - if (globalState.scanNoteResponse.verifyResponse?.verificationState == - VerifyCardState.VerifiedOffline - ) { + if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) { + if (!globalState.cardVerifiedOnline) { addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } } @@ -117,7 +119,7 @@ class WarningsMiddleware { private fun checkIfWarningNeeded( card: Card, ): WarningMessage? { - if (card.isTwinCard()) return null + if (card.isTangemTwin()) return null if (card.isMultiwalletAllowed) { return if (card.hasSignedHashes()) { @@ -147,15 +149,15 @@ 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 card = store.state.globalState.scanResponse?.card + if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return - if (card.isTwinCard() || card.isMultiwalletAllowed) return + if (card.isTangemTwin() || 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..030c2e2ab6 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,12 @@ class MultiWalletReducer { ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState(allowed = false), - currency = Currency.Blockchain(blockchain) + currency = Currency.Blockchain(blockchain), + tradeCryptoState = TradeCryptoState( + sellingAllowed = state.tradeCryptoAllowed.sellingAllowed && + state.tradeCryptoAllowed.availableToSell.contains(blockchain.currency), + buyingAllowed = state.tradeCryptoAllowed.buyingAllowed + ) ) } @@ -63,8 +66,12 @@ class MultiWalletReducer { ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), - topUpState = TopUpState(allowed = false), - currency = Currency.Blockchain(action.blockchain) + currency = Currency.Blockchain(action.blockchain), + tradeCryptoState = TradeCryptoState( + sellingAllowed = state.tradeCryptoAllowed.sellingAllowed && + state.tradeCryptoAllowed.availableToSell.contains(action.blockchain.currency), + buyingAllowed = state.tradeCryptoAllowed.buyingAllowed + ) ) val newState = state.copy(wallets = state.replaceWalletInWallets(walletData)) if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) { @@ -165,10 +172,11 @@ 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), + tradeCryptoState = TradeCryptoState( + sellingAllowed = state.tradeCryptoAllowed.sellingAllowed && + state.tradeCryptoAllowed.availableToSell.contains(this.symbol), + buyingAllowed = state.tradeCryptoAllowed.buyingAllowed ) ) } \ 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..d999216bba 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 @@ -2,12 +2,11 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType 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.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.* @@ -39,40 +38,46 @@ private fun internalReduce(action: Action, state: AppState): WalletState { 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(creatingWalletAllowed), + ) + ) + ) } 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), + ) + ) ) } } @@ -80,73 +85,87 @@ 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( + sellingAllowed = action.allowToSell ?: wallet.tradeCryptoState.sellingAllowed && + state.walletState.tradeCryptoAllowed.availableToSell.contains(wallet.currencyData.currencySymbol), + buyingAllowed = action.allowToBuy + ?: wallet.tradeCryptoState.buyingAllowed + ) ) } + val tradeCryptoAllowed = TradeCryptoAvailability( + sellingAllowed = action.allowToSell ?: false, + buyingAllowed = action.allowToBuy ?: false + ) newState = newState.copy( - state = ProgressState.Loading, - wallets = wallets + state = ProgressState.Loading, + wallets = wallets, + tradeCryptoAllowed = tradeCryptoAllowed ) } 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( + sellingAllowed = action.allowToSell ?: wallet.tradeCryptoState.sellingAllowed && + state.walletState.tradeCryptoAllowed.availableToSell.contains(wallet.currencyData.currencySymbol), + buyingAllowed = action.allowToBuy + ?: wallet.tradeCryptoState.buyingAllowed ) - } + ) + } val wallets = newState.replaceSomeWallets(newWallets) newState = newState.copy(wallets = 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 = wallets ) } is WalletAction.LoadWallet.Failure -> { @@ -157,28 +176,30 @@ 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 = wallets ) } is WalletAction.SetArtworkId -> { @@ -193,60 +214,55 @@ 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) { - TwinCardNumber.First -> Artwork.TWIN_CARD_1 - TwinCardNumber.Second -> Artwork.TWIN_CARD_2 - null -> Artwork.DEFAULT_IMG_URL - } - } else { - Artwork.DEFAULT_IMG_URL - } + val artworkUrl = action.card.getArtworkUrl(action.artworkId) + ?: when (newState.twinCardsState?.cardNumber) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + 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 - ) + 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 +273,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 +319,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 +357,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/dialogs/TwinsOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/TwinsOnboardingFragment.kt rename to app/src/main/java/com/tangem/tap/features/wallet/ui/OnboardingTwinsFragment.kt index 1daec73f5b..7ecd724f9e 100644 --- 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/OnboardingTwinsFragment.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.wallet.ui.dialogs +package com.tangem.tap.features.wallet.ui import android.os.Bundle import android.view.View @@ -15,7 +15,7 @@ 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) { +class OnboardingTwinsFragment : Fragment(R.layout.fragment_twin_cards) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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..1592ae4eb2 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,7 +12,7 @@ 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.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.* @@ -86,11 +86,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_top_up.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) } + + btn_sell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) } } override fun newState(state: WalletState) { @@ -99,6 +97,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 +129,9 @@ 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 + allowToBuy = selectedWallet.tradeCryptoState.buyingAllowed, + allowToSell = selectedWallet.tradeCryptoState.sellingAllowed, + blockchain = selectedWallet.currency?.blockchain )) } } @@ -132,6 +139,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS if (selectedWallet.currencyData.status != BalanceStatus.Loading) { srl_wallet_details.isRefreshing = false } + + btn_top_up.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed + btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed) } private fun handleCurrencyIcon(wallet: WalletData) { 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..dbd285226f 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 @@ -159,7 +159,7 @@ 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 }, 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..b90adc2cb6 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 @@ -98,7 +98,7 @@ class WalletAdapter 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/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index b3f63a0236..0f164965d4 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 @@ -23,10 +22,7 @@ class ScanFailsDialog { store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail())) } setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { - store.dispatch(HomeAction.HideDialog) - store.dispatch(WalletAction.HideDialog) - } + setOnDismissListener { store.dispatchDialogHide() } }.create() } } 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..d2e4e77cc5 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,7 +6,7 @@ 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.StateDialog import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.twins.TwinCardNumber @@ -16,10 +16,7 @@ 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.* @@ -99,7 +96,6 @@ class SingleWalletView : WalletView { ) = 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,10 +105,9 @@ 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)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } @@ -124,12 +119,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 @@ -149,14 +149,26 @@ class SingleWalletView : WalletView { btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } btn_top_up.setOnClickListener { - store.dispatch( - WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray) - ) + tradeCryptoAction(state.tradeCryptoState) } } + private fun tradeCryptoAction(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 + } + 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 { @@ -236,14 +248,14 @@ class SingleWalletView : WalletView { 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..f3e062abf3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt @@ -0,0 +1,16 @@ +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 + + companion object { + const val MOOONPAY_BASE_URL = "https://api.moonpay.com/v4/" + const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "ip_address/" + } +} \ 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..9ef4b8b678 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.network.moonpay + +import com.squareup.moshi.JsonClass +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest +import com.tangem.tap.network.createRetrofitInstance + +class MoonpayService { + + private val moonpayApi: MoonpayApi by lazy { + createRetrofitInstance(MoonpayApi.MOOONPAY_BASE_URL) + .create(MoonpayApi::class.java) + } + + suspend fun getUserStatus(moonpayApiKey: String): Result { + return performRequest { moonpayApi.getUserStatus(moonpayApiKey) } + } +} + +@JsonClass(generateAdapter = true) +data class MoonPayUserStatus( + val isBuyAllowed: Boolean, + val isSellAllowed: Boolean +) \ 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/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/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_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_home_back_card.xml b/app/src/main/res/drawable/ic_home_back_card.xml new file mode 100644 index 0000000000..f15c47ba3f --- /dev/null +++ b/app/src/main/res/drawable/ic_home_back_card.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_home_front_card.xml b/app/src/main/res/drawable/ic_home_front_card.xml new file mode 100644 index 0000000000..19969e806e --- /dev/null +++ b/app/src/main/res/drawable/ic_home_front_card.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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_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_bottom_action_views.xml b/app/src/main/res/layout-h680dp/layout_onboarding_bottom_action_views.xml new file mode 100644 index 0000000000..a603013d47 --- /dev/null +++ b/app/src/main/res/layout-h680dp/layout_onboarding_bottom_action_views.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + \ 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..92c780e969 --- /dev/null +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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_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..86c9138e69 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + \ 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" /> @@ -61,39 +61,33 @@ android:src="@drawable/card_placeholder" app:layout_constraintTop_toTopOf="parent" /> - - - + app:constraint_referenced_ids="iv_card,tv_twin_card_number" /> + + + + + + app:layout_constraintTop_toBottomOf="@id/barrier_currency_title" /> @@ -93,13 +114,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_top_up" + 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_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml index d4a2a6dfd2..3b74abdffd 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"> + + + + diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml index 8d22ab57b4..a052651d61 100644 --- a/app/src/main/res/layout/item_popular_token.xml +++ b/app/src/main/res/layout/item_popular_token.xml @@ -3,7 +3,8 @@ 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:minHeight="52dp" android:layout_marginBottom="1dp" android:background="@android:color/white"> @@ -31,9 +32,16 @@ app:layout_constraintTop_toTopOf="@id/iv_currency" tools:text="J" /> + + @@ -93,4 +104,12 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_bottom_action_views.xml b/app/src/main/res/layout/layout_onboarding_bottom_action_views.xml new file mode 100644 index 0000000000..54f8722018 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_bottom_action_views.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_home.xml b/app/src/main/res/layout/layout_onboarding_home.xml new file mode 100644 index 0000000000..6e4d367320 --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_home.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_note.xml b/app/src/main/res/layout/layout_onboarding_note.xml new file mode 100644 index 0000000000..8393ecde9e --- /dev/null +++ b/app/src/main/res/layout/layout_onboarding_note.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + \ 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_send_address_payid.xml b/app/src/main/res/layout/layout_send_address_payid.xml index 00a5617a9d..2b0f046335 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" @@ -150,6 +151,7 @@ android:id="@+id/etMemo" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@color/backgroundLightGray" android:ellipsize="middle" android:paddingStart="0dp" android:paddingEnd="0dp" @@ -180,6 +182,7 @@ android:id="@+id/etDestinationTag" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@color/backgroundLightGray" android:ellipsize="end" android:inputType="number" android:paddingStart="0dp" diff --git a/app/src/main/res/layout/layout_send_amount.xml b/app/src/main/res/layout/layout_send_amount.xml index e2b5441fa2..b2e8700482 100644 --- a/app/src/main/res/layout/layout_send_amount.xml +++ b/app/src/main/res/layout/layout_send_amount.xml @@ -51,6 +51,7 @@ android:id="@+id/etAmountToSend" android:layout_width="match_parent" android:layout_height="82dp" + android:background="@color/backgroundLightGray" android:fontFamily="sans-serif-light" android:inputType="numberDecimal" android:paddingStart="0dp" diff --git a/app/src/main/res/layout/layout_token.xml b/app/src/main/res/layout/layout_token.xml index 8e03d11ca1..fc9486f79c 100644 --- a/app/src/main/res/layout/layout_token.xml +++ b/app/src/main/res/layout/layout_token.xml @@ -20,7 +20,7 @@ android:id="@+id/v_token_background" android:layout_width="match_parent" android:layout_height="0dp" - android:background="@color/backgroundGray" + android:background="@color/backgroundLightGray" app:layout_constraintTop_toBottomOf="@id/tv_token_title" app:layout_constraintBottom_toBottomOf="@id/tv_token_fiat_amount"/> 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..be494ee95b 100644 --- a/app/src/main/res/layout/layout_wallet_short_buttons.xml +++ b/app/src/main/res/layout/layout_wallet_short_buttons.xml @@ -28,10 +28,10 @@ android:layout_marginStart="8dp" android:layout_marginTop="30dp" android:layout_marginBottom="33dp" - android:drawableTop="@drawable/ic_arrow_up_short_btn" + android:drawableTop="@drawable/ic_arrows_up_down_short_btn" android:gravity="bottom|center_horizontal" android:paddingTop="1dp" - android:text="@string/wallet_button_topup" + android:text="@string/wallet_button_trade" app:drawableTint="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/btn_confirm_short" 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..fa4505f353 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_create_wallet.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + \ 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..c6294b65ca --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + \ 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..fab21fdfb2 --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_done_activation.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + \ 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..65705f3fce --- /dev/null +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + \ 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..3fbf992180 --- /dev/null +++ b/app/src/main/res/layout/test_leapfrog_widget.xml @@ -0,0 +1,23 @@ + + + + + + + + + + \ 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..415f8bd19c --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_progress.xml @@ -0,0 +1,10 @@ + + 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..c9bf3aacb2 --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_refresh_balance.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..5dd54d7067 --- /dev/null +++ b/app/src/main/res/layout/view_onboarding_tv_balance.xml @@ -0,0 +1,29 @@ + + + + + + + + \ 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..17edb1b769 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 @@ -93,7 +97,9 @@ this wallet. Manage tokens Blockchains - Ethereum Tokens + Ethereum tokens + Binance Smart Chain tokens + Binance Chain tokens Copy Share + Add tokens @@ -156,6 +162,37 @@ 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 + What does it mean? + 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 + Buy crypto + Show the wallet’s address + Scan the address to top up your wallet + + Done! + Your crypto card is activated and ready to be used + Continue + Balance + + Internal error: can\'t create wallet manager + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ae260a7854..f008a00608 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -5,9 +5,11 @@ @color/colorPrimary @color/colorSecondary @color/darkGray6 - true @color/backgroundLightGray - false + 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