Updated on 2026-08-14
This commit is contained in:
commit
1b4efb53d7
226 changed files with 16251 additions and 2742 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -13,10 +13,7 @@ local.properties
|
|||
.gradle
|
||||
|
||||
# User-specific configurations
|
||||
.idea/caches/
|
||||
.idea/libraries/
|
||||
.idea/*.xml
|
||||
.idea/codeStyles
|
||||
.idea
|
||||
|
||||
# LayoutInspector files
|
||||
/captures
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
}
|
||||
|
|
@ -81,9 +81,33 @@
|
|||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="wc"/>
|
||||
|
||||
<data android:scheme="wc" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="success.tangem.com"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="sell-request.tangem.com"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
|
|
|||
3086
app/src/main/assets/binance_tokens.json
Normal file
3086
app/src/main/assets/binance_tokens.json
Normal file
File diff suppressed because it is too large
Load diff
14
app/src/main/assets/binance_tokens_testnet.json
Normal file
14
app/src/main/assets/binance_tokens_testnet.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
782
app/src/main/assets/bsc_tokens.json
Normal file
782
app/src/main/assets/bsc_tokens.json
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
99
app/src/main/assets/bsc_tokens_testnet.json
Normal file
99
app/src/main/assets/bsc_tokens_testnet.json
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
4765
app/src/main/assets/ethereum_tokens.json
Normal file
4765
app/src/main/assets/ethereum_tokens.json
Normal file
File diff suppressed because it is too large
Load diff
46
app/src/main/assets/ethereum_tokens_testnet.json
Normal file
46
app/src/main/assets/ethereum_tokens_testnet.json
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
21
app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt
Normal file
21
app/src/main/java/com/tangem/tap/common/CustomTabsManager.kt
Normal file
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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<GlobalState> {
|
|||
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()
|
||||
}
|
||||
|
|
|
|||
26
app/src/main/java/com/tangem/tap/common/Handler.kt
Normal file
26
app/src/main/java/com/tangem/tap/common/Handler.kt
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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"),
|
||||
}
|
||||
|
|
@ -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<FirebaseAnalyticsHandler.AnalyticsParam, String>? = null,
|
||||
card: Card? = null
|
||||
)
|
||||
}
|
||||
|
|
@ -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<AnalyticsParam, String>?,
|
||||
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<AnalyticsParam, String> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,17 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
open class Button(val enabled: Boolean)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,18 @@ fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
|
|||
val toRemove = this.filter(predicate)
|
||||
this.removeAll(toRemove)
|
||||
return toRemove.isNotEmpty()
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.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 <T> MutableList<T>.replaceByOrAdd(item: T, predicate: (T) -> Boolean) {
|
||||
if (!replaceBy(item, predicate)) add(item)
|
||||
}
|
||||
|
|
@ -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 <T> withMainContext(block: suspend CoroutineScope.() -> T) {
|
||||
withContext(Dispatchers.Main, block)
|
||||
}
|
||||
|
||||
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) {
|
||||
withContext(Dispatchers.IO, block)
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
|
||||
filter { it.key != null && it.value != null } as Map<K, V>
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<AppState>.dispatchOnMain(action: Action) {
|
||||
fun Store<*>.dispatchOnMain(action: Action) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
store.dispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
app/src/main/java/com/tangem/tap/common/extensions/Toast.kt
Normal file
31
app/src/main/java/com/tangem/tap/common/extensions/Toast.kt
Normal file
|
|
@ -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) }
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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<Wallet> = 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]
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<LeapView>()
|
||||
|
||||
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<Animator>()
|
||||
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<Animator>()
|
||||
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<Animator>()
|
||||
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<LeapViewState>
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<Middleware<AppState>> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<Any>?): String {
|
|||
val notificationsMiddleware: Middleware<AppState> = { 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
|
||||
}
|
||||
}
|
||||
|
||||
// Processed only in the debug builds
|
||||
interface Debug
|
||||
interface DebugNotification : Debug, NotificationAction
|
||||
interface DebugToastNotification : Debug, ToastNotificationAction
|
||||
interface DebugErrorAction : Debug, ErrorAction
|
||||
17
app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
Normal file
17
app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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<ScanNoteResponse>) : GlobalAction()
|
||||
data class ChooseBehavior(val result: CompletionResult<ScanResponse>) : 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<WalletManager>) : GlobalAction()
|
||||
|
||||
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
|
||||
object HideDialog : GlobalAction()
|
||||
object GetMoonPayUserStatus : GlobalAction() {
|
||||
data class Success(val moonPayUserStatus: MoonPayUserStatus) : GlobalAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppState> = { dispatch, appState ->
|
||||
class GlobalMiddleware {
|
||||
companion object {
|
||||
val handler = globalMiddlewareHandler
|
||||
}
|
||||
}
|
||||
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
|
|
@ -22,8 +37,7 @@ val globalMiddleware: Middleware<AppState> = { 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<AppState> = { 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
typealias FiatCurrencyName = String
|
||||
|
|
@ -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<FragmentActivity>) : NavigationAction()
|
||||
object ActivityDestroyed : NavigationAction()
|
||||
}
|
||||
}
|
||||
|
||||
data class FragmentShareTransition(
|
||||
val shareElements: List<ShareElement>,
|
||||
val enterTransitionSet: TransitionSet,
|
||||
val exitTransitionSet: TransitionSet
|
||||
)
|
||||
|
||||
data class ShareElement(val wView: WeakReference<View>, val name: String)
|
||||
|
|
@ -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<AppState> = { 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,19 @@ import org.rekotlin.StateType
|
|||
import java.lang.ref.WeakReference
|
||||
|
||||
data class NavigationState(
|
||||
val backStack: List<AppScreen> = listOf(AppScreen.Home),
|
||||
val activity: WeakReference<FragmentActivity>? = null
|
||||
val backStack: List<AppScreen> = listOf(AppScreen.Home),
|
||||
val activity: WeakReference<FragmentActivity>? = 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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<View>(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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
8
app/src/main/java/com/tangem/tap/domain/ProductType.kt
Normal file
8
app/src/main/java/com/tangem/tap/domain/ProductType.kt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class ProductType {
|
||||
Note, Wallet, Twin, Other
|
||||
}
|
||||
|
|
@ -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<ScanNoteResponse> {
|
||||
suspend fun scanProduct(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanResponse> {
|
||||
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<Card> {
|
||||
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<ScanResponse>
|
||||
) {
|
||||
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<Card> {
|
||||
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<PurgeWalletResponse> {
|
||||
return runTaskAsyncReturnOnMain(PurgeWalletCommand(
|
||||
TangemSdkConstants.getDefaultWalletIndex()),
|
||||
cardId,
|
||||
initialMessage = Message(activity.getString(R.string.initial_message_purge_wallet_body)))
|
||||
suspend fun eraseWallet(card: Card): CompletionResult<SuccessResponse> {
|
||||
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<SetPinResponse> {
|
||||
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<SuccessResponse> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
SetUserCodeCommand.changePasscode(null),
|
||||
cardId,
|
||||
initialMessage = Message(context.getString(R.string.initial_message_change_passcode_body)))
|
||||
}
|
||||
|
||||
suspend fun setAccessCode(cardId: String?): CompletionResult<SetPinResponse> {
|
||||
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<SuccessResponse> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
SetUserCodeCommand.changeAccessCode(null),
|
||||
cardId,
|
||||
initialMessage = Message(context.getString(R.string.initial_message_change_access_code_body)))
|
||||
}
|
||||
|
||||
suspend fun setLongTap(cardId: String?): CompletionResult<SetPinResponse> {
|
||||
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<SuccessResponse> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
SetUserCodeCommand.resetUserCodes(),
|
||||
cardId,
|
||||
initialMessage = Message(context.getString(R.string.initial_message_tap_header)))
|
||||
}
|
||||
|
||||
suspend fun checkUserCodes(cardId: String?): CompletionResult<CheckUserCodesResponse> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
CheckUserCodesCommand(),
|
||||
cardId,
|
||||
initialMessage = Message(context.getString(R.string.initial_message_tap_header)))
|
||||
}
|
||||
|
||||
suspend fun <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> =
|
||||
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 <T : CommandResponse> runTaskAsyncReturnOnMain(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
): CompletionResult<T> {
|
||||
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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ByteArray> =
|
||||
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<ByteArray>,
|
||||
cardId: String,
|
||||
walletPublicKey: ByteArray
|
||||
walletPublicKey: ByteArray,
|
||||
): CompletionResult<List<ByteArray>> =
|
||||
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(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TangemSignerResponse(
|
||||
val totalSignedHashes: Int?,
|
||||
val remainingSignatures: Int?,
|
||||
)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
class TapBlockchainConfig {
|
||||
companion object {
|
||||
val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT"
|
||||
val blockcypherTokens: Set<String> = setOf(
|
||||
"aa8184b0e0894b88a5688e01b3dc1e82",
|
||||
"56c4ca23c6484c8f8864c32fde4def8d",
|
||||
"66a8a37c5e9d4d2c9bb191acfe7f93aa"
|
||||
)
|
||||
val infuraProjectId: String = "613a0b14833145968b1f656240c7d245"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Any>? = null
|
||||
@StringRes val messageResource: Int,
|
||||
override val args: List<Any>? = 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<Any>) : 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<Pair<Int, List<Any>?>> {
|
|||
val idList = mutableListOf<Pair<Int, List<Any>?>>()
|
||||
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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
49
app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt
Normal file
49
app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt
Normal file
|
|
@ -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<Bitmap>) -> 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<Target>()
|
||||
|
||||
// It adds the ability to trigger multiple downloads with a unique callback.
|
||||
open class DownloadTarget(
|
||||
val callback: (Result<Bitmap>) -> 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?) {
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ConfigModel>,
|
||||
private val remoteLoader: Loader<ConfigModel>
|
||||
private val localLoader: Loader<ConfigModel>,
|
||||
private val remoteLoader: Loader<ConfigModel>
|
||||
) {
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class ConfigValueModel(
|
|||
val coinMarketCapKey: String,
|
||||
val moonPayApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
val blockchairApiKey: String?,
|
||||
val blockchairAuthorizationToken: String?,
|
||||
val blockcypherTokens: Set<String>?,
|
||||
val infuraProjectId: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<EllipticCurve>? {
|
||||
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"
|
||||
|
|
@ -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<CardVerifyAndGetInfo.Response.Item>? = 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?
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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>): 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<Blockchain>,
|
||||
): List<WalletManager> {
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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<Card>, PreflightReadCapable {
|
||||
override val requiresPin2 = false
|
||||
override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
|
||||
@Deprecated("Use CreateProductWalletAndRescanTask instead")
|
||||
class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> 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<Card>, 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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WalletConfig> = emptyList()) : CardSessionRunnable<Card> {
|
||||
override val requiresPin2 = false
|
||||
@Deprecated("Use CreateProductWalletsResponse instead")
|
||||
class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnable<Card> {
|
||||
|
||||
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<Card>) -> 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<Card>) -> Unit
|
||||
curve: EllipticCurve, session: CardSession,
|
||||
callback: (result: CompletionResult<Card>) -> 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<WalletConfig> = emptyList()) : CardSession
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun EllipticCurve.toWalletIndex(): Int {
|
||||
return when (this) {
|
||||
EllipticCurve.Secp256k1 -> 0
|
||||
EllipticCurve.Ed25519 -> 1
|
||||
EllipticCurve.Secp256r1 -> 2
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanResponse> {
|
||||
|
||||
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse>, PreflightReadCapable {
|
||||
override val requiresPin2 = false
|
||||
|
||||
override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> 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<ScanNoteRespons
|
|||
return@run
|
||||
}
|
||||
|
||||
verifyCard(card, session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyCard(
|
||||
card: Card, session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> 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<ScanNoteResponse>) -> Unit
|
||||
card: Card, session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> 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<ScanNoteRespons
|
|||
}
|
||||
|
||||
private fun dealWithTwinCard(
|
||||
card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
|
||||
card: Card, session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> 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<ScanNoteRespons
|
|||
if (verified) {
|
||||
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
|
||||
callback(CompletionResult.Success(
|
||||
ScanNoteResponse(card, verifyResponse, twinPublicKey.toHexString())
|
||||
ScanResponse(
|
||||
card = card,
|
||||
ProductType.Other,
|
||||
walletData = session.environment.walletData,
|
||||
secondTwinPublicKey = twinPublicKey.toHexString())
|
||||
))
|
||||
return@run
|
||||
} else {
|
||||
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Other, null)))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
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<ScanNoteRespons
|
|||
return WalletManagerFactory(blockchainSdkConfig)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UpdateAppToUseThisCard : TangemError {
|
||||
override val code: Int = 50005
|
||||
override var customMessage: String = code.toString()
|
||||
override val messageResId: Int = R.string.error_update_app
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
|
||||
class TangemSignHashResponse(
|
||||
val signature: ByteArray,
|
||||
val totalSignedHashes: Int?,
|
||||
val remainingSignatures: Int?,
|
||||
) : CommandResponse
|
||||
|
||||
class SignHashTask(
|
||||
private val hash: ByteArray,
|
||||
private val walletPublicKey: ByteArray,
|
||||
) : CardSessionRunnable<TangemSignHashResponse> {
|
||||
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashResponse>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ByteArray>,
|
||||
val totalSignedHashes: Int?,
|
||||
val remainingSignatures: Int?,
|
||||
) : CommandResponse
|
||||
|
||||
class SignHashesTask(
|
||||
private val hashes: Collection<ByteArray>,
|
||||
private val walletPublicKey: ByteArray,
|
||||
) : CardSessionRunnable<TangemSignHashesResponse> {
|
||||
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashesResponse>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Card> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> 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<CreateWalletResponse> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> 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<CreateWalletResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> 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<CreateWalletResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit
|
||||
) {
|
||||
//TODO:
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletResponse> {
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CreateWalletResponse>
|
||||
) : CommandResponse
|
||||
|
||||
class CreateProductWalletsTask(
|
||||
private val curves: List<EllipticCurve>,
|
||||
) : CardSessionRunnable<CreateProductWalletsResponse> {
|
||||
|
||||
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateProductWalletsResponse>) -> Unit) {
|
||||
val curve = curves[createdWalletsResponses.size]
|
||||
createWallet(curve, session, callback)
|
||||
}
|
||||
|
||||
private fun createWallet(
|
||||
curve: EllipticCurve,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletsResponse>) -> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T> {
|
||||
fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<T>) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<ScanResponse> {
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> 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<ScanResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Note, session.environment.walletData)))
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanWalletProcessor : ProductCommandProcessor<ScanResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanTwinProcessor : ProductCommandProcessor<ScanResponse> {
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> 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<ScanResponse> {
|
||||
|
||||
override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult<ScanResponse>) -> 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<EllipticCurve> {
|
||||
val curvesPresent = wallets.map { it.curve }
|
||||
val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent)
|
||||
return curvesForNonCreatedWallets.toList()
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<Set<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(Set::class.java, Blockchain::class.java)
|
||||
)
|
||||
private val tokensAdapter: JsonAdapter<Set<TokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(Set::class.java, TokenDao::class.java)
|
||||
)
|
||||
private val blockchainsAdapter: JsonAdapter<Set<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(Set::class.java, Blockchain::class.java)
|
||||
private val obsoleteTokensAdapter: JsonAdapter<Set<ObsoleteTokenDao>> = 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<Token> {
|
||||
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<Token> {
|
||||
val json = context.assets.readJsonFileToString(POPULAR_TOKENS_FILE_NAME)
|
||||
return tokensAdapter.fromJson(json)!!.map { it.toToken() }
|
||||
fun getPopularTokens(isTestNet: Boolean = false): List<Token> {
|
||||
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<Blockchain> {
|
||||
return if (cardFirmware == null || cardFirmware.major < 4) {
|
||||
secp256k1Blockchains
|
||||
fun getBlockchains(
|
||||
cardFirmware: FirmwareVersion?,
|
||||
isTestNet: Boolean = false
|
||||
): List<Blockchain> {
|
||||
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<TokenDao>,
|
||||
val blockchains: Set<Blockchain>,
|
||||
) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> = 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<String> = 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 }
|
||||
}
|
||||
|
|
@ -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<CreateWalletResponse> {
|
||||
override val requiresPin2 = false
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CreateWalletResponse> {
|
||||
override val requiresPin2 = true
|
||||
|
||||
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> 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<CreateWalletResponse>) -> 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
|
||||
|
|
|
|||
|
|
@ -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<ScanNoteResponse> {
|
||||
override val requiresPin2 = true
|
||||
) : CardSessionRunnable<ScanResponse> {
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> 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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ScanNoteResponse> {
|
||||
suspend fun complete(message: Message): Result<ScanResponse> {
|
||||
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 {
|
||||
|
|
|
|||
152
app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt
Normal file
152
app/src/main/java/com/tangem/tap/domain/twins/TwinsCardWidget.kt
Normal file
|
|
@ -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<PropertyValuesHolder> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<WriteIssuerDataResponse> {
|
||||
override val requiresPin2 = true
|
||||
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,
|
||||
) : CardSessionRunnable<SuccessResponse> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> 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<SuccessResponse>) -> 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<WriteIssuerDataResponse>
|
||||
) -> Unit) {
|
||||
twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
|
||||
readResponse: ReadIssuerDataResponse,
|
||||
session: CardSession,
|
||||
callback: (
|
||||
result: CompletionResult<SuccessResponse>,
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Wallet>,
|
||||
val isCreatingTwinWalletAllowed: Boolean?,
|
||||
val cardTou: CardTou,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = null,
|
||||
val card: Card,
|
||||
val scanResponse: ScanResponse,
|
||||
val wallets: List<Wallet>,
|
||||
val isCreatingTwinWalletAllowed: Boolean?,
|
||||
val cardTou: CardTou,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = 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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SecurityOption> {
|
||||
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<SecurityOption> {
|
||||
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<SecurityOption> {
|
|||
|
||||
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)}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<WalletConnectSession>) :
|
||||
WalletConnectAction()
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue