Updated on 2026-08-14
This commit is contained in:
commit
c22e1bdf5b
84 changed files with 3747 additions and 1089 deletions
|
|
@ -77,9 +77,9 @@ dependencies {
|
|||
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-52'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-115'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-115'
|
||||
implementation 'com.tangem:blockchain:develop-59'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-127'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-127'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
|
@ -110,6 +110,9 @@ dependencies {
|
|||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
implementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
|
||||
// AppsFlyer
|
||||
implementation 'com.appsflyer:af-android-sdk:6.5.1'
|
||||
|
||||
// WalletConnect
|
||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
|
@ -35,7 +36,8 @@
|
|||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:fullBackupContent">
|
||||
|
||||
|
||||
<activity
|
||||
|
|
|
|||
1637
app/src/main/assets/avalanche_tokens.json
Normal file
1637
app/src/main/assets/avalanche_tokens.json
Normal file
File diff suppressed because it is too large
Load diff
24
app/src/main/assets/avalanche_tokens_testnet.json
Normal file
24
app/src/main/assets/avalanche_tokens_testnet.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"customIconUrl": "https://i.imgur.com/LPZDXoT.png",
|
||||
"symbol": "FUJISTABLE",
|
||||
"name": "The Fuji stablecoin",
|
||||
"contractAddress": "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22",
|
||||
"blockchain": {
|
||||
"key": "avalanche",
|
||||
"testnet": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"customIconUrl": "https://i.imgur.com/jNTqyQA.png",
|
||||
"symbol": "FUJIMOON",
|
||||
"name": "To the Moon",
|
||||
"contractAddress": "0x97132C109c6816525F7f338DCb7435E1412A7668",
|
||||
"blockchain": {
|
||||
"key": "avalanche",
|
||||
"testnet": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"blockchain" : {
|
||||
"key" : "binance",
|
||||
"curve" : "secp256k1",
|
||||
"testnet" : false
|
||||
"testnet" : true
|
||||
},
|
||||
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MATIC-84A/logo.png",
|
||||
"name" : "Hemster - 452"
|
||||
|
|
|
|||
519
app/src/main/assets/solana_tokens.json
Normal file
519
app/src/main/assets/solana_tokens.json
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
[
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "SOLAPE",
|
||||
"name": "SolAPE Finance",
|
||||
"contractAddress": "GHvFFSZ9BctWsEc5nujR1MTmmJWY7tgQz2AXE6WVFtGN",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 1,
|
||||
"symbol": "FLOOF",
|
||||
"name": "FLOOF",
|
||||
"contractAddress": "3jzdrXXKxwkBk82u2eCWASZLCKoZs1LQTg87HBEAmBJw",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "MNDE",
|
||||
"name": "Marinade",
|
||||
"contractAddress": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "BTC",
|
||||
"name": "Wrapped BTC",
|
||||
"contractAddress": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 8,
|
||||
"symbol": "TWT",
|
||||
"name": "Trust Wallet",
|
||||
"contractAddress": "HZNpqL7RT9gxf9eWoWsWzC5DfjzQ41XTQgEA7p3VzaaD",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "CCAI",
|
||||
"name": "Cryptocurrencies.Ai",
|
||||
"contractAddress": "E5ndSkaB17Dm7CsD22dvcjfrYSDLCxFcMd6z8ddCk5wp",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "ETH",
|
||||
"name": "Wrapped ETH",
|
||||
"contractAddress": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "XRP",
|
||||
"name": "Wrapped XRP",
|
||||
"contractAddress": "Ga2AXHpfAF6mv2ekZwcsJFqu7wB4NV331qNH7fW9Nst8",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 5,
|
||||
"symbol": "KIN",
|
||||
"name": "Kin",
|
||||
"contractAddress": "kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "mSOL",
|
||||
"name": "Marinade staked",
|
||||
"contractAddress": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "GENE",
|
||||
"name": "Genopets",
|
||||
"contractAddress": "GENEtH5amGSi8kHAtQoezp1XEXwZJ8vcuePYnXdKrMYz",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "MM",
|
||||
"name": "Million",
|
||||
"contractAddress": "FiCiuX9DetEE89PgRAU1hmoptnem8b1fkpEq8PGYTYkd",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "DFL",
|
||||
"name": "DeFi Land",
|
||||
"contractAddress": "DFL1zNkaGPWm1BqAVqRjCZvHmwTFrEaJtbzJWgseoNJh",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "CAVE",
|
||||
"name": "Crypto Cavemen",
|
||||
"contractAddress": "4SZjjNABoqhbd4hnapbvoEPEqT8mnNkfbEoAwALf1V8t",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "FTT",
|
||||
"name": "Wrapped FTX",
|
||||
"contractAddress": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "SOL",
|
||||
"name": "Wrapped SOL",
|
||||
"contractAddress": "So11111111111111111111111111111111111111112",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 8,
|
||||
"symbol": "ATLAS",
|
||||
"name": "Star Atlas",
|
||||
"contractAddress": "ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 4,
|
||||
"symbol": "BRZ",
|
||||
"name": "Brazilian Digital Token",
|
||||
"contractAddress": "FtgGSFADXBtroxq8VCausXRr2of47QBf5AS1NtZCu4GD",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "USDC",
|
||||
"name": "USD Coin",
|
||||
"contractAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "SLIM",
|
||||
"name": "Solanium",
|
||||
"contractAddress": "xxxxa1sKNGwFtw2kFn8XauW9xq8hBZ5kVtcSesTT9fW",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "USDT",
|
||||
"name": "Wrapped USDT",
|
||||
"contractAddress": "BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "HOLD",
|
||||
"name": "Holdana",
|
||||
"contractAddress": "8GQsW3f7mdwfjqJon2myADcBsSsRjpXmxHYDG8q1pvV6",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "MAPS",
|
||||
"name": "Maps.me",
|
||||
"contractAddress": "MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "SRM",
|
||||
"name": "Serum",
|
||||
"contractAddress": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "JET",
|
||||
"name": "Jet Protocol",
|
||||
"contractAddress": "JET6zMJWkCN9tpRT2v2jfAmm5VnQFDpUBCyaKojmGtz",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "STNK",
|
||||
"name": "Stonks",
|
||||
"contractAddress": "43VWkd99HjqkhFTZbWBpMpRhjG469nWa7x7uEsgSH7We",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "SKEM",
|
||||
"name": "SKEM",
|
||||
"contractAddress": "3x7UeXDF4imKSKnizK9mYyx1M5bTNzpeALfPeB8S6XT9",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "MEDIA",
|
||||
"name": "Media Network",
|
||||
"contractAddress": "ETAtLmCmsoiEEKfNrHKJ2kYy3MoABhU6NQvpSfij5tDs",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "ROPE",
|
||||
"name": "Rope Token",
|
||||
"contractAddress": "8PMHT4swUMtBzgHnh5U564N5sjPSiUz2cjEQzFnnP1Fo",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 8,
|
||||
"symbol": "POLIS",
|
||||
"name": "Star Atlas DAO",
|
||||
"contractAddress": "poLisWXnNRwC6oBu1vHiuKQzFjGL4XDSu4g9qjz9qVk",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "RAY",
|
||||
"name": "Raydium",
|
||||
"contractAddress": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "Wrapped FRONT",
|
||||
"name": "Wrapped FRONT",
|
||||
"contractAddress": "9S4t2NEAiJVMvPdRYKVrfJpBafPBLtvbvyS3DecojQHw",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "USDC",
|
||||
"name": "Wrapped USDC",
|
||||
"contractAddress": "BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "USDT",
|
||||
"name": "USDT",
|
||||
"contractAddress": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "ORCA",
|
||||
"name": "Orca",
|
||||
"contractAddress": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "LIQ",
|
||||
"name": "LIQ Protocol",
|
||||
"contractAddress": "4wjPQJ6PrkC4dHhYghwJzGBVP78DkBzA2U3kHoFNBuhj",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "WOOF",
|
||||
"name": "WOOFENOMICS",
|
||||
"contractAddress": "9nEqaUcb16sQ3Tn1psbkWqyhPdLmfHWjKGymREjsAgTE",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "STEP",
|
||||
"name": "Step Finance",
|
||||
"contractAddress": "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "FOUR",
|
||||
"name": "The 4th Pillar Token",
|
||||
"contractAddress": "DAtU322C23YpoZyWBm8szk12QyqHa9rUQe1EYXzbm1JE",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "OXY",
|
||||
"name": "Oxygen Protocol",
|
||||
"contractAddress": "z3dn17yLaGMKffVogeFHQ9zWVcXgqgf3PQnDsNs2g6M",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 7,
|
||||
"symbol": "ISLAMI",
|
||||
"name": "ISLAMICOIN",
|
||||
"contractAddress": "HG2FnifX1WPY6UvnkXnMx2kAt4E4Ssu6DZ2aiB5RmLHo",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "AURY",
|
||||
"name": "Aurory",
|
||||
"contractAddress": "AURYydfxJib1ZkTir1Jn1J9ECYUtjb6rKQVmtYaixWPP",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "COPE",
|
||||
"name": "COPE",
|
||||
"contractAddress": "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "FIDA",
|
||||
"name": "Bonfida",
|
||||
"contractAddress": "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "wCCX",
|
||||
"name": "Wrapped Conceal",
|
||||
"contractAddress": "CzqJTjTp3R9opDHmzF5Gy1G2NKDHEyMufa4Keiju4PLu",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 0,
|
||||
"symbol": "XCOPE",
|
||||
"name": "XCOPE",
|
||||
"contractAddress": "3K6rftdAaQYMPunrtNRHgnK2UAtjm2JwyT2oCiTDouYE",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 6,
|
||||
"symbol": "MNGO",
|
||||
"name": "Mango Markets",
|
||||
"contractAddress": "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": false
|
||||
}
|
||||
}
|
||||
]
|
||||
24
app/src/main/assets/solana_tokens_testnet.json
Normal file
24
app/src/main/assets/solana_tokens_testnet.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "TCA",
|
||||
"name": "Tangem Coin A",
|
||||
"contractAddress": "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"decimalCount": 9,
|
||||
"symbol": "TCB",
|
||||
"name": "Tangem Coin B",
|
||||
"contractAddress": "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4",
|
||||
"blockchain": {
|
||||
"key": "solana",
|
||||
"curve": "ed25519",
|
||||
"testnet": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit e80938f7d9ba378a91ac544a9170597e64afa696
|
||||
Subproject commit 42803296690ad6914ec2c59c5fdc3f0fb30f8afc
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
|
||||
import com.tangem.Log
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.images.PicassoHelper
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
|
|
@ -28,9 +30,9 @@ import org.rekotlin.Store
|
|||
import timber.log.Timber
|
||||
|
||||
val store = Store(
|
||||
reducer = ::appReducer,
|
||||
middleware = AppState.getMiddleware(),
|
||||
state = AppState()
|
||||
reducer = ::appReducer,
|
||||
middleware = AppState.getMiddleware(),
|
||||
state = AppState()
|
||||
)
|
||||
lateinit var preferencesStorage: PreferencesStorage
|
||||
lateinit var currenciesRepository: CurrenciesRepository
|
||||
|
|
@ -61,6 +63,8 @@ class TapApplication : Application() {
|
|||
loadConfigs()
|
||||
|
||||
BlockchainSdkRetrofitBuilder.enableNetworkLogging = BuildConfig.DEBUG
|
||||
|
||||
initAppsFlyer()
|
||||
}
|
||||
|
||||
private fun loadConfigs() {
|
||||
|
|
@ -82,4 +86,12 @@ class TapApplication : Application() {
|
|||
|
||||
store.dispatch(GlobalAction.SetFeedbackManager(FeedbackManager(infoHolder, logWriter)))
|
||||
}
|
||||
|
||||
private fun initAppsFlyer() {
|
||||
val devKey = store.state.globalState.configManager?.config?.appsFlyerDevKey ?: return
|
||||
AppsFlyerLib.getInstance().init(devKey, null, this)
|
||||
AppsFlyerLib.getInstance().start(this)
|
||||
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(this)
|
||||
store.dispatch(GlobalAction.SetAnanlyticHandlers(analyticsHandler))
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +79,15 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
TransactionDialog.create(state.dialog.dialogData, context)
|
||||
is WalletConnectDialog.PersonalSign ->
|
||||
PersonalSignDialog.create(state.dialog.data, context)
|
||||
is WalletConnectDialog.BnbTransactionDialog ->
|
||||
BnbTransactionDialog.create(
|
||||
data = state.dialog.data,
|
||||
session = state.dialog.session,
|
||||
sessionId = state.dialog.sessionId,
|
||||
cardId = state.dialog.cardId,
|
||||
dAppName = state.dialog.dAppName,
|
||||
context = context
|
||||
)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,66 @@ enum class AnalyticsEvent(val event: String) {
|
|||
CARD_IS_SCANNED("card_is_scanned"),
|
||||
TRANSACTION_IS_SENT("transaction_is_sent"),
|
||||
READY_TO_SCAN("ready_to_scan"),
|
||||
|
||||
APP_RATING_DISPLAYED("rate_app_warning_displayed"),
|
||||
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"),
|
||||
|
||||
GET_CARD("get_card"),
|
||||
}
|
||||
|
||||
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"),
|
||||
|
||||
SOURCE("source"),
|
||||
}
|
||||
|
||||
enum class GetCardSourceParams(val param: String) {
|
||||
WELCOME("welcome"),
|
||||
ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"),
|
||||
ONBOARDING("wallet_onboarding")
|
||||
}
|
||||
|
||||
object Analytics {
|
||||
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 }
|
||||
|
||||
enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,75 @@ package com.tangem.tap.common.analytics
|
|||
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
|
||||
|
||||
interface AnalyticsHandler {
|
||||
fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null)
|
||||
fun logCardSdkError(
|
||||
abstract class AnalyticsHandler {
|
||||
abstract fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card? = null,
|
||||
blockchain: String? = null,
|
||||
params: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
abstract fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: FirebaseAnalyticsHandler.ActionToLog,
|
||||
parameters: Map<FirebaseAnalyticsHandler.AnalyticsParam, String>? = null,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>? = null,
|
||||
card: Card? = null
|
||||
)
|
||||
|
||||
abstract fun logError(
|
||||
error: Throwable,
|
||||
params: Map<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
protected fun prepareParams(card: Card?, blockchain: String? = null): Map<String, String> {
|
||||
return mapOf(
|
||||
AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue,
|
||||
AnalyticsParam.BATCH_ID.param to card?.batchId,
|
||||
AnalyticsParam.BLOCKCHAIN.param to blockchain,
|
||||
).filterNotNull()
|
||||
}
|
||||
|
||||
fun logWcEvent(event: Analytics.WcAnalyticsEvent) {
|
||||
when (event) {
|
||||
is Analytics.WcAnalyticsEvent.Action -> {
|
||||
triggerEvent(
|
||||
event = AnalyticsEvent.WC_SUCCESS_RESPONSE,
|
||||
params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name
|
||||
)
|
||||
)
|
||||
}
|
||||
is Analytics.WcAnalyticsEvent.Error -> {
|
||||
val params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action?.name,
|
||||
AnalyticsParam.ERROR_DESCRIPTION.param to event.error.message
|
||||
)
|
||||
.filterNotNull()
|
||||
logError(event.error, params)
|
||||
}
|
||||
is Analytics.WcAnalyticsEvent.InvalidRequest ->
|
||||
triggerEvent(
|
||||
event = AnalyticsEvent.WC_INVALID_REQUEST,
|
||||
params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json
|
||||
).filterNotNull()
|
||||
)
|
||||
is Analytics.WcAnalyticsEvent.Session -> {
|
||||
val analyticsEvent = when (event.event) {
|
||||
Analytics.WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED
|
||||
Analytics.WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION
|
||||
}
|
||||
triggerEvent(
|
||||
event = analyticsEvent,
|
||||
params = mapOf(
|
||||
AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url
|
||||
).filterNotNull()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import android.app.Application
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
|
||||
|
||||
override fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
AppsFlyerLib.getInstance().logEvent(context,
|
||||
event.event, prepareParams(card, blockchain))
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>?,
|
||||
card: Card?
|
||||
) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
override fun logError(error: Throwable, params: Map<String, String>) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,11 +7,16 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.google.firebase.ktx.Firebase
|
||||
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?, blockchain: String?) {
|
||||
Firebase.analytics.logEvent(event.event, setData(card, blockchain))
|
||||
object FirebaseAnalyticsHandler : AnalyticsHandler() {
|
||||
override fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
val fullParams = prepareParams(card, blockchain) + params
|
||||
Firebase.analytics.logEvent(event.event, fullParams.toBundle())
|
||||
}
|
||||
|
||||
fun logException(name: String, throwable: Throwable) {
|
||||
|
|
@ -22,16 +27,20 @@ object FirebaseAnalyticsHandler : AnalyticsHandler {
|
|||
))
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toBundle(): Bundle {
|
||||
return bundleOf(*this.toList().toTypedArray())
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: ActionToLog,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>?,
|
||||
card: Card?,
|
||||
) {
|
||||
if (error is TangemSdkError.UserCancelled) return
|
||||
|
||||
val params = parameters?.toMutableMap() ?: mutableMapOf()
|
||||
if (card != null) params + getParamsFromCard(card)
|
||||
if (card != null) params + prepareParams(card)
|
||||
params[AnalyticsParam.ACTION] = actionToLog.key
|
||||
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
|
||||
params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName
|
||||
|
|
@ -44,100 +53,8 @@ object FirebaseAnalyticsHandler : AnalyticsHandler {
|
|||
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()
|
||||
override fun logError(error: Throwable, params: Map<String, String>) {
|
||||
params.forEach { (key, value) -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) }
|
||||
FirebaseCrashlytics.getInstance().recordException(error)
|
||||
}
|
||||
|
||||
private fun setData(card: Card?, blockchain: String?): Bundle {
|
||||
if (card == null) return bundleOf()
|
||||
return bundleOf(
|
||||
AnalyticsParam.BLOCKCHAIN.param to (blockchain),
|
||||
AnalyticsParam.BATCH_ID.param to card.batchId,
|
||||
AnalyticsParam.FIRMWARE.param to card.firmwareVersion.stringValue
|
||||
)
|
||||
}
|
||||
|
||||
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,41 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import android.app.Application
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
|
||||
AnalyticsHandler() {
|
||||
override fun triggerEvent(
|
||||
event: AnalyticsEvent,
|
||||
card: Card?,
|
||||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain) }
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
parameters: Map<AnalyticsParam, String>?,
|
||||
card: Card?
|
||||
) {
|
||||
analyticsHandlers.forEach { it.logCardSdkError(error, actionToLog, parameters, card) }
|
||||
}
|
||||
|
||||
override fun logError(error: Throwable, params: Map<String, String>) {
|
||||
analyticsHandlers.forEach { it.logError(error, params) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler {
|
||||
return GlobalAnalyticsHandler(
|
||||
listOf(
|
||||
FirebaseAnalyticsHandler,
|
||||
AppsFlyerAnalyticsHandler(context)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +105,7 @@ object TangemSdk {
|
|||
is TangemSdkError.WrongInteractionMode -> TangemSdkError.WrongInteractionMode()
|
||||
is TangemSdkError.IssuerSignatureLoadingFailed -> TangemSdkError.IssuerSignatureLoadingFailed()
|
||||
is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware()
|
||||
is TangemSdkError.UserForgotTheCode -> TangemSdkError.UserForgotTheCode()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
data class ByteArrayKey(val bytes: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || other is ByteArrayKey && this.bytes.contentEquals(other.bytes)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = bytes.contentHashCode()
|
||||
}
|
||||
|
||||
fun ByteArray.toMapKey(): ByteArrayKey = ByteArrayKey(this)
|
||||
|
||||
fun ByteArrayKey.bytes(): ByteArray = this.bytes
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.net.Uri
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -9,6 +10,7 @@ import com.squareup.picasso.Picasso
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.IconsUtil
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.wallet.R
|
||||
|
||||
fun Picasso.loadCurrenciesIcon(
|
||||
|
|
@ -19,7 +21,7 @@ fun Picasso.loadCurrenciesIcon(
|
|||
) {
|
||||
|
||||
val url = if (token != null) {
|
||||
IconsUtil.getTokenIconUri(blockchain, token)
|
||||
token.getCustomIconUrl()?.let { Uri.parse(it) } ?: IconsUtil.getTokenIconUri(blockchain, token)
|
||||
} else {
|
||||
IconsUtil.getBlockchainIconUri(blockchain)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ fun Store<*>.dispatchOnMain(action: Action) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse, addAnalytics: Boolean = true) {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(scanResponse, addAnalytics)
|
||||
suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(scanResponse)
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchOpenUrl(url: String) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
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
|
||||
|
|
@ -26,7 +26,7 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
|||
val blockchain = wallet.blockchain
|
||||
val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken())
|
||||
|
||||
if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) {
|
||||
if (exception is BlockchainSdkError.AccountNotFound && amountToCreateAccount != null) {
|
||||
Result.Failure(TapError.WalletManagerUpdate.NoAccountError(amountToCreateAccount.toString()))
|
||||
} else {
|
||||
val message = exception.localizedMessage ?: "An error has occurred. Try later"
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
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,
|
||||
)
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
package com.tangem.tap.common.leapfrogWidget
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.view.children
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class LeapfrogWidget(
|
||||
private val parentContainer: FrameLayout,
|
||||
private val calculator: PropertyCalculator = PropertyCalculator(),
|
||||
) {
|
||||
private val foldAnimationDuration = 300L
|
||||
private val leapAnimationDuration = 500L
|
||||
private val leapBackAnimationDuration = 500L
|
||||
|
||||
private val leapViews = mutableListOf<LeapView>()
|
||||
|
||||
private var foldUnfoldProgress: Int = 100
|
||||
private var leapProgress: Int = 100
|
||||
private var leapBackProgress: Int = 100
|
||||
|
||||
init {
|
||||
calculator.setTranslationConverter { parentContainer.dpToPx(it) }
|
||||
if (viewIsFullFledged()) {
|
||||
val maxPosition = parentContainer.childCount - 1
|
||||
parentContainer.children.forEachIndexed { index, view ->
|
||||
val initialPosition = maxPosition - index
|
||||
leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply initial properties to the views
|
||||
*/
|
||||
fun initViews() {
|
||||
leapViews.forEach { it.initView() }
|
||||
}
|
||||
|
||||
fun fold(animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
val animator = foldAnimator(animate)
|
||||
if (animator == null) {
|
||||
onEnd()
|
||||
} else {
|
||||
animator.doOnEnd { onEnd() }
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun unfold(animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
val animator = unfoldAnimator(animate)
|
||||
if (animator == null) {
|
||||
onEnd()
|
||||
} else {
|
||||
animator.doOnEnd { onEnd() }
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun foldAnimator(animate: Boolean = true): Animator? {
|
||||
if (!canFoldUnfold()) return null
|
||||
|
||||
return createFoldUnfoldAnimator(true, animate, foldAnimationDuration)
|
||||
}
|
||||
|
||||
fun unfoldAnimator(animate: Boolean = true): Animator? {
|
||||
if (!canFoldUnfold()) return null
|
||||
|
||||
return createFoldUnfoldAnimator(false, animate, foldAnimationDuration)
|
||||
}
|
||||
|
||||
private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, animate: Boolean, duration: Long): Animator {
|
||||
val animatorsList = mutableListOf<Animator>()
|
||||
val animateDuration = if (animate) duration else 0
|
||||
leapViews.forEach {
|
||||
if (isFoldAnimation) {
|
||||
it.fold()
|
||||
animatorsList.add(it.view.foldAnimation(animateDuration, it.state.properties))
|
||||
} else {
|
||||
it.unfold()
|
||||
animatorsList.add(it.view.unfoldAnimation(animateDuration, it.state.properties))
|
||||
}
|
||||
}
|
||||
animatorsList.add(createProgressListener(animateDuration) { foldUnfoldProgress = it })
|
||||
|
||||
val animator = AnimatorSet()
|
||||
animator.interpolator = AccelerateDecelerateInterpolator()
|
||||
animator.playTogether(animatorsList.toList())
|
||||
return animator
|
||||
}
|
||||
|
||||
fun leap(animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
if (!canLeap()) {
|
||||
onEnd()
|
||||
return
|
||||
}
|
||||
|
||||
val duration = if (animate) leapAnimationDuration else 0
|
||||
val animatorsList = mutableListOf<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(animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
if (!canLeapBack()) {
|
||||
onEnd()
|
||||
return
|
||||
}
|
||||
|
||||
val duration = if (animate) leapBackAnimationDuration else 0
|
||||
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(leapViews.map { it.state })
|
||||
|
||||
fun applyState(state: LeapfrogWidgetState) {
|
||||
state.leapViewStates.forEach { viewState ->
|
||||
leapViews.firstOrNull { it.index == viewState.index }?.applyState(viewState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1
|
||||
|
||||
private fun canFoldUnfold(): Boolean {
|
||||
return when {
|
||||
!viewIsFullFledged() -> false
|
||||
foldUnfoldInProgress() -> false
|
||||
leapInProgress() || leapBackInProgress() -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun canLeap(): Boolean {
|
||||
return when {
|
||||
!viewIsFullFledged() -> false
|
||||
leapBackInProgress() -> false
|
||||
leapProgress < 70 -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun canLeapBack(): Boolean {
|
||||
return when {
|
||||
!viewIsFullFledged() -> false
|
||||
leapInProgress() -> false
|
||||
leapBackProgress < 70 -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun foldUnfoldInProgress(): Boolean = foldUnfoldProgress != 100
|
||||
private fun leapInProgress(): Boolean = leapProgress != 100
|
||||
private fun leapBackInProgress(): Boolean = leapBackProgress != 100
|
||||
}
|
||||
|
||||
data class LeapfrogWidgetState(
|
||||
val leapViewStates: List<LeapViewState>
|
||||
)
|
||||
|
||||
class PropertyCalculator(
|
||||
val elevationFactor: Float = 1f,
|
||||
val decreaseScaleFactor: Float = 0.15f,
|
||||
val yTranslationFactor: Float = 35f,
|
||||
val decreaseOverLiftingFactor: Float = 0.85f,
|
||||
) {
|
||||
// 0 view it is view on the top of stack, but it is last in parent.children
|
||||
|
||||
private var translationValueConverter: ((Float) -> Float)? = null
|
||||
fun setTranslationConverter(converter: (Float) -> Float) {
|
||||
translationValueConverter = converter
|
||||
}
|
||||
|
||||
fun scale(position: Int): Float {
|
||||
return 1f - decreaseScaleFactor * position
|
||||
}
|
||||
|
||||
fun elevation(position: Int, count: Int): Float {
|
||||
return elevationFactor * (count - 1 - position)
|
||||
}
|
||||
|
||||
fun yTranslation(position: Int): Float {
|
||||
val yTranslation = yTranslationFactor * position
|
||||
return convertTranslation(yTranslation)
|
||||
}
|
||||
|
||||
fun hasForeground(position: Int): Boolean {
|
||||
return position != 0
|
||||
}
|
||||
|
||||
fun overLift(viewCount: Int): Float {
|
||||
val initialOverLift = when {
|
||||
yTranslationFactor < 0f -> yTranslationFactor * decreaseOverLiftingFactor * viewCount * -1
|
||||
else -> 10f
|
||||
}
|
||||
val scaleOverLift = when {
|
||||
decreaseScaleFactor == 0f -> initialOverLift
|
||||
else -> initialOverLift * decreaseOverLiftingFactor - initialOverLift * decreaseScaleFactor
|
||||
}
|
||||
return convertTranslation(scaleOverLift)
|
||||
}
|
||||
|
||||
private fun convertTranslation(value: Float): Float {
|
||||
return translationValueConverter?.invoke(value) ?: value
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import android.view.View
|
|||
import android.widget.FrameLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidgetState
|
||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.test_leapfrog_fragment.*
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.redux.*
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
|
|
@ -69,6 +70,7 @@ sealed class GlobalAction : Action {
|
|||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
|
||||
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
|
||||
data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction()
|
||||
|
||||
data class SendFeedback(val emailData: EmailData) : GlobalAction()
|
||||
data class UpdateFeedbackInfo(val walletManagers: List<WalletManager>) : GlobalAction()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.*
|
||||
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
|
||||
|
|
@ -89,9 +88,8 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
is GlobalAction.ScanCard -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
FirebaseAnalyticsHandler,
|
||||
store.state.globalState.analyticsHandlers,
|
||||
currenciesRepository,
|
||||
action.shouldDeriveWC,
|
||||
action.messageResId
|
||||
)
|
||||
withMainContext {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState.copy(configManager = action.configManager)
|
||||
}
|
||||
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
|
||||
is GlobalAction.SetAnanlyticHandlers ->
|
||||
globalState.copy(analyticsHandlers = action.analyticsHandlers)
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanResponse?.card ?: return globalState
|
||||
val wallet = card.wallet(action.walletPublicKey) ?: return globalState
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
|
|
@ -28,6 +29,7 @@ data class GlobalState(
|
|||
val dialog: StateDialog? = null,
|
||||
val moonpayStatus: MoonpayStatus? = null,
|
||||
val resources: AndroidResources = AndroidResources(),
|
||||
val analyticsHandlers: AnalyticsHandler? = null,
|
||||
) : StateType
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,18 +14,19 @@ import com.tangem.common.core.CardIdDisplayFormat
|
|||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.pins.CheckUserCodesCommand
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.DerivationTask
|
||||
import com.tangem.tap.domain.tasks.DerivationTaskResponse
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
|
|
@ -39,16 +40,15 @@ import kotlin.coroutines.suspendCoroutine
|
|||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
suspend fun scanProduct(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
analyticsHandler: AnalyticsHandler?,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
shouldDeriveWC: Boolean,
|
||||
messageRes: Int? = null,
|
||||
): CompletionResult<ScanResponse> {
|
||||
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
analyticsHandler?.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository), null, message)
|
||||
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
.also { sendScanResultsToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
suspend fun createProductWallet(
|
||||
|
|
@ -61,14 +61,24 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
)
|
||||
}
|
||||
|
||||
private fun sendScanFailuresToAnalytics(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
private fun sendScanResultsToAnalytics(
|
||||
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)
|
||||
}
|
||||
when (result) {
|
||||
is CompletionResult.Success ->
|
||||
analyticsHandler?.triggerEvent(
|
||||
event = AnalyticsEvent.CARD_IS_SCANNED,
|
||||
card = result.data.card,
|
||||
blockchain = result.data.walletData?.blockchain
|
||||
)
|
||||
is CompletionResult.Failure ->
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
analyticsHandler?.logCardSdkError(
|
||||
error = error,
|
||||
actionToLog = Analytics.ActionToLog.Scan
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +91,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
cardId: String,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>
|
||||
): CompletionResult<DerivationTaskResponse> {
|
||||
return runTaskAsyncReturnOnMain(DerivationTask(derivations), cardId)
|
||||
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
|
||||
}
|
||||
|
||||
suspend fun resetToFactorySettings(card: Card): CompletionResult<Card> {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package com.tangem.tap.domain
|
|||
|
||||
import com.tangem.blockchain.common.*
|
||||
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
|
||||
|
|
@ -71,10 +69,7 @@ class TapWalletManager {
|
|||
handleFiatRatesResult(results)
|
||||
}
|
||||
|
||||
suspend fun onCardScanned(data: ScanResponse, addAnalyticsEvent: Boolean = false) {
|
||||
if (addAnalyticsEvent) {
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain)
|
||||
}
|
||||
suspend fun onCardScanned(data: ScanResponse) {
|
||||
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
|
||||
updateConfigManager(data)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ data class Config(
|
|||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val appsFlyerDevKey: String = "",
|
||||
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
val isTopUpEnabled: Boolean = false,
|
||||
|
|
@ -84,7 +85,8 @@ class ConfigManager(
|
|||
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
|
||||
blockcypherTokens = values.blockcypherTokens,
|
||||
infuraProjectId = values.infuraProjectId
|
||||
)
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
|
|
@ -95,7 +97,8 @@ class ConfigManager(
|
|||
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
|
||||
blockcypherTokens = values.blockcypherTokens,
|
||||
infuraProjectId = values.infuraProjectId
|
||||
)
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class ConfigValueModel(
|
|||
val blockchairAuthorizationToken: String?,
|
||||
val blockcypherTokens: Set<String>?,
|
||||
val infuraProjectId: String?,
|
||||
val appsFlyerDevKey: String,
|
||||
)
|
||||
|
||||
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,6 @@ import com.tangem.common.card.EllipticCurve
|
|||
import java.math.BigDecimal
|
||||
|
||||
|
||||
fun Blockchain.isNoAccountError(exception: Throwable): Boolean {
|
||||
return when (this) {
|
||||
Blockchain.Stellar, Blockchain.XRP ->
|
||||
exception.message?.contains("Account not found") == true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
|
||||
return when (this) {
|
||||
Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE
|
||||
|
|
|
|||
17
app/src/main/java/com/tangem/tap/domain/extensions/Token.kt
Normal file
17
app/src/main/java/com/tangem/tap/domain/extensions/Token.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
private val tokenCustomIconUrls = mutableMapOf<String, String>()
|
||||
|
||||
fun Token.getCustomIconUrl(): String? {
|
||||
return tokenCustomIconUrls[this.contractAddress]
|
||||
}
|
||||
|
||||
fun Token.setCustomIconUrl(url: String) {
|
||||
tokenCustomIconUrls[this.contractAddress] = url
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManagerFactory
|
|||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
|
||||
|
|
@ -33,13 +33,23 @@ fun WalletManagerFactory.makeWalletManagerForApp(
|
|||
}
|
||||
seedKey != null -> {
|
||||
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
|
||||
val derivedKey = derivedKeys?.firstOrNull { it.derivationPath == blockchain.derivationPath() }
|
||||
val derivedKey = derivedKeys?.get(blockchain.derivationPath())
|
||||
?: return null
|
||||
|
||||
makeWalletManager(card.cardId, environmentBlockchain, seedKey, derivedKey)
|
||||
makeWalletManager(
|
||||
cardId = card.cardId,
|
||||
blockchain = environmentBlockchain,
|
||||
seedKey = wallet.publicKey,
|
||||
derivedKey = derivedKey
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
makeWalletManager(card.cardId, environmentBlockchain, wallet.publicKey, wallet.curve)
|
||||
makeWalletManager(
|
||||
cardId = card.cardId,
|
||||
blockchain = environmentBlockchain,
|
||||
walletPublicKey = wallet.publicKey,
|
||||
curve = wallet.curve
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,55 @@
|
|||
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.common.hdWallet.DerivationPath
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeyList
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
|
||||
class DerivationTaskResponse(
|
||||
val entries: Map<ByteArrayKey, ExtendedPublicKeyList>
|
||||
): CommandResponse
|
||||
|
||||
class DerivationTask(
|
||||
private val derivations: Map<ByteArrayKey, List<DerivationPath>>
|
||||
) : CardSessionRunnable<DerivationTaskResponse> {
|
||||
|
||||
val response: MutableMap<ByteArrayKey, ExtendedPublicKeyList> = mutableMapOf()
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<DerivationTaskResponse>) {
|
||||
derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback)
|
||||
}
|
||||
|
||||
private fun derive(
|
||||
keys: List<ByteArrayKey>,
|
||||
index: Int,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<DerivationTaskResponse>
|
||||
) {
|
||||
if (index == keys.count()) {
|
||||
callback(CompletionResult.Success(DerivationTaskResponse(response.toMap())))
|
||||
return
|
||||
}
|
||||
|
||||
val key = keys[index]
|
||||
val paths = derivations[key]!!
|
||||
DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
response[key] = result.data
|
||||
derive(keys = keys, index = index + 1, session = session, callback = callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//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.common.hdWallet.DerivationPath
|
||||
//import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
//import com.tangem.operations.CommandResponse
|
||||
//import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
//import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
//
|
||||
//class ExtendedPublicKeyList(
|
||||
// items: Collection<ExtendedPublicKey>
|
||||
//): ArrayList<ExtendedPublicKey>(items), CommandResponse
|
||||
//
|
||||
//
|
||||
//class DerivationTaskResponse(
|
||||
// val entries: Map<ByteArrayKey, ExtendedPublicKeyList>
|
||||
//): CommandResponse
|
||||
//
|
||||
//class DerivationTask(
|
||||
// private val derivations: Map<ByteArrayKey, List<DerivationPath>>
|
||||
//) : CardSessionRunnable<DerivationTaskResponse> {
|
||||
//
|
||||
// val response: MutableMap<ByteArrayKey, ExtendedPublicKeyList> = mutableMapOf()
|
||||
//
|
||||
// override fun run(session: CardSession, callback: CompletionCallback<DerivationTaskResponse>) {
|
||||
// derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback)
|
||||
// }
|
||||
//
|
||||
// private fun derive(
|
||||
// keys: List<ByteArrayKey>,
|
||||
// index: Int,
|
||||
// session: CardSession,
|
||||
// callback: CompletionCallback<DerivationTaskResponse>
|
||||
// ) {
|
||||
// if (index == keys.count()) {
|
||||
// callback(CompletionResult.Success(DerivationTaskResponse(response.toMap())))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// val key = keys[index]
|
||||
// val paths = derivations[key]!!
|
||||
// DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result ->
|
||||
// when (result) {
|
||||
// is CompletionResult.Success -> {
|
||||
// response[key] = result.data[key]
|
||||
// derive(keys = keys, index = index + 1, session = session, callback = callback)
|
||||
// }
|
||||
// is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -6,16 +6,16 @@ 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.common.hdWallet.ExtendedPublicKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.ProductType
|
||||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.tasks.DerivationTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
|
||||
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
|
||||
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
|
||||
|
|
@ -24,7 +24,7 @@ import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
|
|||
|
||||
data class CreateProductWalletTaskResponse(
|
||||
val card: Card,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf(),
|
||||
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null
|
||||
) : CommandResponse
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
return
|
||||
}
|
||||
|
||||
DerivationTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
|
||||
DeriveMultipleWalletPublicKeysTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
|
||||
.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
|
|||
|
|
@ -12,19 +12,20 @@ import com.tangem.common.core.CardSession
|
|||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.domain.ProductType
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
|
|
@ -33,7 +34,6 @@ import com.tangem.tap.domain.TapWorkarounds.isExcluded
|
|||
import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tasks.DerivationTask
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
|
@ -43,7 +43,7 @@ data class ScanResponse(
|
|||
val productType: ProductType,
|
||||
val walletData: WalletData?,
|
||||
val secondTwinPublicKey: String? = null,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf(),
|
||||
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null
|
||||
) : CommandResponse {
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ private class ScanWalletProcessor(
|
|||
return
|
||||
}
|
||||
|
||||
DerivationTask(derivations).run(session) { result ->
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import com.squareup.moshi.Types
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.extensions.readJsonFileToString
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.extensions.setCustomIconUrl
|
||||
import com.tangem.tap.network.createMoshi
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
|
|
@ -113,24 +116,28 @@ class CurrenciesRepository(val context: Application) {
|
|||
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 avalancheTokensFileName =
|
||||
if (isTestNet) AVALANCHE_TESTNET_TOKENS_FILE_NAME else AVALANCHE_TOKENS_FILE_NAME
|
||||
|
||||
val ethereumTokensJson = context.assets.readJsonFileToString(ethereumTokensFileName)
|
||||
val bscTokensJson = context.assets.readJsonFileToString(bscTokensFileName)
|
||||
val binanceTokensJson = context.assets.readJsonFileToString(binanceTokensFileName)
|
||||
val avalancheTokensJson = context.assets.readJsonFileToString(avalancheTokensFileName)
|
||||
|
||||
return tokensAdapter.fromJson(ethereumTokensJson)!!.map { it.toToken() } +
|
||||
tokensAdapter.fromJson(bscTokensJson)!!.map { it.toToken() } +
|
||||
tokensAdapter.fromJson(binanceTokensJson)!!.mapNotNull {
|
||||
// temporary exclude Binance BEP-8 tokens
|
||||
if (it.type != null && it.type == BINANCE_TOKEN_TYPE_BEP8) null else it.toToken()
|
||||
}
|
||||
tokensAdapter.fromJson(bscTokensJson)!!.map { it.toToken() } +
|
||||
tokensAdapter.fromJson(binanceTokensJson)!!.mapNotNull {
|
||||
// temporary exclude Binance BEP-8 tokens
|
||||
if (it.type != null && it.type == BINANCE_TOKEN_TYPE_BEP8) null else it.toToken()
|
||||
} +
|
||||
tokensAdapter.fromJson(avalancheTokensJson)!!.map { it.toToken() }
|
||||
}
|
||||
|
||||
fun getBlockchains(
|
||||
cardFirmware: FirmwareVersion?,
|
||||
cardFirmware: FirmwareVersion,
|
||||
isTestNet: Boolean = false
|
||||
): List<Blockchain> {
|
||||
return if (cardFirmware == null || cardFirmware < FirmwareVersion.MultiWalletAvailable) {
|
||||
return if (cardFirmware < FirmwareVersion.MultiWalletAvailable) {
|
||||
Blockchain.secp256k1Blockchains(isTestNet)
|
||||
} else {
|
||||
Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet)
|
||||
|
|
@ -144,6 +151,9 @@ class CurrenciesRepository(val context: Application) {
|
|||
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 AVALANCHE_TOKENS_FILE_NAME = "avalanche_tokens"
|
||||
private const val AVALANCHE_TESTNET_TOKENS_FILE_NAME = "avalanche_tokens_testnet"
|
||||
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
|
||||
|
|
@ -152,6 +162,18 @@ class CurrenciesRepository(val context: Application) {
|
|||
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
fun getFileNameForBlockchains(cardId: String): String =
|
||||
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
|
||||
|
||||
fun injectDaoBlockchain(context: Context, tokenFileName: String, blockchain: Blockchain): String? {
|
||||
val tokenJson = context.assets.readJsonFileToString(tokenFileName)
|
||||
val converter = MoshiJsonConverter.default()
|
||||
val rawTokensList = converter.fromJson<List<MutableMap<String, Any>>>(tokenJson) ?: return null
|
||||
|
||||
rawTokensList.forEach {
|
||||
it["blockchain"] = BlockchainDao.fromBlockchain(blockchain)
|
||||
}
|
||||
|
||||
return converter.toJson(rawTokensList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,6 +185,7 @@ data class TokenDao(
|
|||
val decimalCount: Int,
|
||||
@Json(name = "blockchain")
|
||||
val blockchainDao: BlockchainDao,
|
||||
val customIconUrl: String? = null,
|
||||
val type: String? = null
|
||||
) {
|
||||
fun toToken(): Token {
|
||||
|
|
@ -172,7 +195,9 @@ data class TokenDao(
|
|||
contractAddress = contractAddress,
|
||||
decimals = decimalCount,
|
||||
blockchain = blockchainDao.toBlockchain()
|
||||
)
|
||||
).apply {
|
||||
customIconUrl?.let { this.setCustomIconUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
@ -182,7 +207,8 @@ data class TokenDao(
|
|||
symbol = token.symbol,
|
||||
contractAddress = token.contractAddress,
|
||||
decimalCount = token.decimals,
|
||||
blockchainDao = BlockchainDao.fromBlockchain(token.blockchain)
|
||||
blockchainDao = BlockchainDao.fromBlockchain(token.blockchain),
|
||||
customIconUrl = token.getCustomIconUrl()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -205,8 +231,7 @@ data class 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)
|
||||
return BlockchainDao(name, blockchain.isTestnet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,17 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
||||
class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
|
||||
class TwinCardsManager(
|
||||
private val card: Card,
|
||||
assetReader: AssetReader,
|
||||
val analyticsHandler: AnalyticsHandler?
|
||||
) {
|
||||
|
||||
private val currentCardId: String = card.cardId
|
||||
|
||||
|
|
@ -31,9 +36,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
|
|||
is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString()
|
||||
is CompletionResult.Failure -> {
|
||||
(response.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
analyticsHandler?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
|
||||
Analytics.ActionToLog.CreateWallet,
|
||||
card = card
|
||||
)
|
||||
}
|
||||
|
|
@ -61,9 +66,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(response.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
analyticsHandler?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
|
||||
Analytics.ActionToLog.CreateWallet,
|
||||
card = card
|
||||
)
|
||||
}
|
||||
|
|
@ -81,9 +86,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) {
|
|||
is CompletionResult.Success -> Result.Success(response.data)
|
||||
is CompletionResult.Failure -> {
|
||||
(response.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
analyticsHandler?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData,
|
||||
Analytics.ActionToLog.WriteIssuerData,
|
||||
card = card
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import android.animation.PropertyValuesHolder
|
|||
import android.view.View
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapView
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ 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.operations.files.FileHashHelper
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataResponse
|
||||
import com.tangem.operations.issuerAndUserData.WriteIssuerDataCommand
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.github.salomonbrys.kotson.registerTypeAdapter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.TradeData
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
||||
import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer
|
||||
import timber.log.Timber
|
||||
|
||||
class BnbHelper {
|
||||
companion object {
|
||||
|
||||
fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer {
|
||||
val input = order.msgs.first().inputs.first()
|
||||
val output = order.msgs.first().inputs.first()
|
||||
|
||||
val currency =
|
||||
input.coins.firstOrNull()?.denom ?: Blockchain.Binance
|
||||
val amount = input.coins
|
||||
.mapNotNull { if (it.denom == currency) it.amount else null }
|
||||
.sum()
|
||||
.toBigDecimal()
|
||||
.movePointRight(Blockchain.Binance.decimals())
|
||||
.toString()
|
||||
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
|
||||
return BinanceMessageData.Transfer(
|
||||
outputAddress = output.address,
|
||||
amount = amount,
|
||||
address = input.address,
|
||||
data = gson.toJson(order).toByteArray().calculateSha256()
|
||||
)
|
||||
}
|
||||
|
||||
fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
val address = order.msgs.first().sender
|
||||
|
||||
val tradeData = order.msgs.map {
|
||||
val price = it.price.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
val quantity = it.quantity.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
|
||||
val amount = price * quantity
|
||||
val symbol = it.symbol.substringBefore("-")
|
||||
|
||||
TradeData(
|
||||
price = "$price ${Blockchain.Binance.currency}",
|
||||
quantity = "$quantity $symbol",
|
||||
amount = "$amount ${Blockchain.Binance.currency}",
|
||||
symbol = symbol
|
||||
)
|
||||
}
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
val serialized = gson.toJson(order)
|
||||
Timber.d(serialized)
|
||||
|
||||
return BinanceMessageData.Trade(
|
||||
tradeData = tradeData,
|
||||
address = address,
|
||||
data = serialized.toByteArray().calculateSha256()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.trustwallet.walletconnect.JSONRPC_VERSION
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
|
||||
class EthSignHelper {
|
||||
companion object {
|
||||
private val gson: Gson by lazy {
|
||||
GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessage(data: String): WCEthereumSignMessage? {
|
||||
val request =
|
||||
gson.fromJson<CustomJsonRpcRequests>(data)
|
||||
return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) {
|
||||
WCEthereumSignMessage(
|
||||
listOf(
|
||||
request.params[0].asString,
|
||||
request.params[1].asString,
|
||||
),
|
||||
WCEthereumSignMessage.WCSignType.TYPED_MESSAGE
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessageString(message: String): String? {
|
||||
return try {
|
||||
val messageString = message
|
||||
.replace("\\", "")
|
||||
.removePrefix("\"")
|
||||
.removeSuffix("\"")
|
||||
val messageJson = JsonParser().parse(messageString)
|
||||
val filteredMap = gson.fromJson<Map<*,*>>(messageJson)
|
||||
.filterKeys { it == "domain" || it == "message"}
|
||||
|
||||
gson.toJson(filteredMap)
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CustomJsonRpcRequests(
|
||||
val id: Long,
|
||||
val jsonrpc: String = JSONRPC_VERSION,
|
||||
val method: WCMethodExtended?,
|
||||
val params: JsonArray
|
||||
)
|
||||
|
||||
enum class WCMethodExtended {
|
||||
@SerializedName("wc_sessionRequest")
|
||||
SESSION_REQUEST,
|
||||
|
||||
@SerializedName("wc_sessionUpdate")
|
||||
SESSION_UPDATE,
|
||||
|
||||
@SerializedName("eth_sign")
|
||||
ETH_SIGN,
|
||||
|
||||
@SerializedName("personal_sign")
|
||||
ETH_PERSONAL_SIGN,
|
||||
|
||||
@SerializedName("eth_signTypedData")
|
||||
ETH_SIGN_TYPE_DATA,
|
||||
|
||||
@SerializedName("eth_signTypedData_v4")
|
||||
ETH_SIGN_TYPE_DATA_V4,
|
||||
|
||||
@SerializedName("eth_signTransaction")
|
||||
ETH_SIGN_TRANSACTION,
|
||||
|
||||
@SerializedName("eth_sendTransaction")
|
||||
ETH_SEND_TRANSACTION,
|
||||
|
||||
@SerializedName("bnb_sign")
|
||||
BNB_SIGN,
|
||||
|
||||
@SerializedName("bnb_tx_confirmation")
|
||||
BNB_TRANSACTION_CONFIRM,
|
||||
|
||||
@SerializedName("get_accounts")
|
||||
GET_ACCOUNTS,
|
||||
|
||||
@SerializedName("trust_signTransaction")
|
||||
SIGN_TRANSACTION;
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.domain.walletconnect
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.*
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.tap.store
|
|||
import com.tangem.tap.walletConnectRepository
|
||||
import com.trustwallet.walletconnect.WCClient
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
import com.trustwallet.walletconnect.models.binance.*
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
|
|
@ -22,6 +23,7 @@ import okhttp3.logging.HttpLoggingInterceptor
|
|||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.set
|
||||
|
||||
class WalletConnectManager {
|
||||
|
||||
|
|
@ -60,6 +62,15 @@ class WalletConnectManager {
|
|||
setupConnectionTimeoutCheck(session)
|
||||
}
|
||||
|
||||
fun updateSession(session: WalletConnectSession) {
|
||||
val updatedSession = sessions[session.session]?.copy(
|
||||
wallet = session.wallet
|
||||
)
|
||||
if (updatedSession != null) {
|
||||
sessions[session.session] = updatedSession
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupConnectionTimeoutCheck(session: WCSession) {
|
||||
scope.launch {
|
||||
delay(20_000)
|
||||
|
|
@ -81,7 +92,7 @@ class WalletConnectManager {
|
|||
client = WCClient(httpClient = okHttpClient),
|
||||
session = session.session,
|
||||
peerMeta = session.peerMeta,
|
||||
wallet = session.wallet
|
||||
wallet = session.wallet,
|
||||
)
|
||||
.also {
|
||||
setListeners(it.client)
|
||||
|
|
@ -96,11 +107,12 @@ class WalletConnectManager {
|
|||
val activeData = sessions[session] ?: return
|
||||
removeSimilarSessions(activeData)
|
||||
|
||||
val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey
|
||||
val accounts = listOf(Blockchain.Ethereum.makeAddresses(key).first().value)
|
||||
val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return
|
||||
val blockchain = activeData.wallet.getBlockchainForSession()
|
||||
val accounts = listOf(blockchain.makeAddresses(key).first().value)
|
||||
val approved = activeData.client.approveSession(
|
||||
accounts = accounts,
|
||||
chainId = activeData.wallet.chainId
|
||||
chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!!
|
||||
)
|
||||
if (approved) {
|
||||
val walletConnectSession = WalletConnectSession(
|
||||
|
|
@ -111,15 +123,18 @@ class WalletConnectManager {
|
|||
peerMeta = activeData.peerMeta!!
|
||||
)
|
||||
walletConnectRepository.saveSession(walletConnectSession)
|
||||
store.dispatchOnMain(WalletConnectAction.ApproveSession.Success(
|
||||
walletConnectSession))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.ApproveSession.Success(
|
||||
walletConnectSession
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun removeSimilarSessions(activeData: WalletConnectActiveData) {
|
||||
val sessionsToRemove = sessions.filter {
|
||||
it.value.wallet.walletPublicKey == activeData.wallet.walletPublicKey
|
||||
it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true
|
||||
&& it.value.peerMeta?.url == activeData.peerMeta?.url
|
||||
&& it.value.session != activeData.session
|
||||
}
|
||||
|
|
@ -146,9 +161,10 @@ class WalletConnectManager {
|
|||
}
|
||||
|
||||
private fun onSessionClosed(session: WCSession) {
|
||||
FirebaseAnalyticsHandler.logWcEvent(
|
||||
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
|
||||
FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url)
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Session(
|
||||
Analytics.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url
|
||||
)
|
||||
)
|
||||
sessions.remove(session)
|
||||
walletConnectRepository.removeSession(session)
|
||||
|
|
@ -170,17 +186,23 @@ class WalletConnectManager {
|
|||
type = type
|
||||
).guard {
|
||||
sessions[session.session] = activeData.copy(transactionData = null)
|
||||
store.dispatchOnMain(WalletConnectAction.RejectRequest(
|
||||
session.session,
|
||||
id
|
||||
))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
session.session,
|
||||
id
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
sessions[session.session] = activeData.copy(transactionData = data)
|
||||
|
||||
store.dispatchOnMain(WalletConnectAction.SetDataToSend(data))
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.RequestTransaction(
|
||||
data.dialogData)))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.RequestTransaction(
|
||||
data.dialogData
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,13 +212,34 @@ class WalletConnectManager {
|
|||
scope.launch {
|
||||
val hash = WalletConnectSdkHelper().completeTransaction(data).guard {
|
||||
sessions[data.session.session] = activeData.copy(transactionData = null)
|
||||
store.dispatchOnMain(WalletConnectAction.RejectRequest(data.session.session,
|
||||
data.id
|
||||
))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
data.session.session,
|
||||
data.id
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
acceptRequest(data.session.session, data.id, hash)
|
||||
sessions[data.session.session] = activeData.copy(transactionData = null)
|
||||
acceptRequest(session, data.id, hash)
|
||||
sessions[session] = activeData.copy(transactionData = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun signBnb(
|
||||
id: Long, data: ByteArray, sessionData: WCSession,
|
||||
) {
|
||||
val activeData = sessions[sessionData] ?: return
|
||||
scope.launch {
|
||||
val hash = WalletConnectSdkHelper().signBnbTransaction(data, activeData).guard {
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
sessionData,
|
||||
id
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
acceptRequest(sessionData, id, hash)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -211,9 +254,13 @@ class WalletConnectManager {
|
|||
message = message, session = session, id = id
|
||||
)
|
||||
sessions[session.session] = activeData.copy(personalSignData = data)
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.PersonalSign(
|
||||
data.dialogData
|
||||
)))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.PersonalSign(
|
||||
data.dialogData
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,9 +271,12 @@ class WalletConnectManager {
|
|||
val hash = WalletConnectSdkHelper().signPersonalMessage(data.hash, activeData.wallet)
|
||||
.guard {
|
||||
sessions[data.session.session] = activeData.copy(transactionData = null)
|
||||
store.dispatchOnMain(WalletConnectAction.RejectRequest(data.session.session,
|
||||
data.id
|
||||
))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.RejectRequest(
|
||||
data.session.session,
|
||||
data.id
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
sessions[session] = activeData.copy(personalSignData = data)
|
||||
|
|
@ -245,12 +295,17 @@ class WalletConnectManager {
|
|||
sessions[session] = data
|
||||
val sessionData = data.toWalletConnectSession()
|
||||
sessionData?.let {
|
||||
store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession(
|
||||
sessionData))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.AcceptOpeningSession(
|
||||
session = sessionData,
|
||||
chainId = client.chainId?.toIntOrNull()
|
||||
)
|
||||
)
|
||||
}
|
||||
FirebaseAnalyticsHandler.logWcEvent(
|
||||
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(
|
||||
FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url)
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Session(
|
||||
Analytics.WcSessionEvent.Connect, peer.url
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -261,56 +316,113 @@ class WalletConnectManager {
|
|||
}
|
||||
client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction ->
|
||||
Timber.d("onEthSendTransaction: $transaction")
|
||||
FirebaseAnalyticsHandler.logWcEvent(
|
||||
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
|
||||
FirebaseAnalyticsHandler.WcAction.SendTransaction
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.SendTransaction
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest(
|
||||
transaction = transaction,
|
||||
session = sessionData,
|
||||
id = id,
|
||||
type = WcTransactionType.EthSendTransaction
|
||||
))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandleTransactionRequest(
|
||||
transaction = transaction,
|
||||
session = sessionData,
|
||||
id = id,
|
||||
type = WcTransactionType.EthSendTransaction
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction ->
|
||||
Timber.d("onEthSignTransaction: $transaction")
|
||||
FirebaseAnalyticsHandler.logWcEvent(
|
||||
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
|
||||
FirebaseAnalyticsHandler.WcAction.SignTransaction
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.SignTransaction
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest(
|
||||
transaction = transaction,
|
||||
session = sessionData,
|
||||
id = id,
|
||||
type = WcTransactionType.EthSignTransaction
|
||||
))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandleTransactionRequest(
|
||||
transaction = transaction,
|
||||
session = sessionData,
|
||||
id = id,
|
||||
type = WcTransactionType.EthSignTransaction
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
client.onEthSign = { id: Long, message: WCEthereumSignMessage ->
|
||||
Timber.d("onEthSign: $message")
|
||||
FirebaseAnalyticsHandler.logWcEvent(
|
||||
FirebaseAnalyticsHandler.WcAnalyticsEvent.Action(
|
||||
FirebaseAnalyticsHandler.WcAction.PersonalSign
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.PersonalSign
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(WalletConnectAction.HandlePersonalSignRequest(
|
||||
message,
|
||||
sessionData,
|
||||
id))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandlePersonalSignRequest(
|
||||
message,
|
||||
sessionData,
|
||||
id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder ->
|
||||
|
||||
}
|
||||
client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder ->
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.BinanceTransaction.Trade(
|
||||
id = id, order = order, sessionData = sessionData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder ->
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.BinanceTransaction.Transfer(
|
||||
id = id, order = order, sessionData = sessionData
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
client.onBnbTxConfirm = { id: Long, order: WCBinanceTxConfirmParam ->
|
||||
// send empty approve request if status is OK
|
||||
if (order.ok) client.approveRequest(id, "")
|
||||
}
|
||||
client.onDisconnect = { code: Int, reason: String ->
|
||||
val session = client.session
|
||||
if (session != null) {
|
||||
onSessionClosed(session)
|
||||
}
|
||||
}
|
||||
client.onCustomRequest = { id: Long, data: String ->
|
||||
Timber.d("Custom Request")
|
||||
Timber.d(data)
|
||||
|
||||
val message = EthSignHelper.tryToParseEthTypedMessage(data)
|
||||
if (message != null) {
|
||||
Timber.d("onEthSign_v4: $message")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.PersonalSign
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandlePersonalSignRequest(
|
||||
message,
|
||||
sessionData,
|
||||
id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
|
||||
class WalletConnectNetworkUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
fun parseBlockchain(
|
||||
chainId: Int?,
|
||||
peer: WCPeerMeta,
|
||||
isTestNet: Boolean? = null
|
||||
): Blockchain? {
|
||||
return when {
|
||||
chainId != null -> {
|
||||
Blockchain.fromChainId(chainId)
|
||||
}
|
||||
peer.url.contains("matic.network") || peer.name == "Polygon" -> {
|
||||
Blockchain.Polygon
|
||||
}
|
||||
peer.url.contains("binance.org") || peer.name.contains("Binance") -> {
|
||||
if (peer.icons.firstOrNull()?.contains("testnet") == true) {
|
||||
Blockchain.BinanceTestnet
|
||||
} else {
|
||||
Blockchain.Binance
|
||||
}
|
||||
}
|
||||
peer.name.contains("BSC") -> {
|
||||
Blockchain.BSC
|
||||
}
|
||||
else -> {
|
||||
if (isTestNet == true) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +88,6 @@ data class SessionDao(
|
|||
session = session.session,
|
||||
peerMeta = session.peerMeta
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,9 +10,10 @@ import com.tangem.blockchain.extensions.*
|
|||
import com.tangem.common.CompletionResult
|
||||
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.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
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
|
||||
|
|
@ -33,20 +34,8 @@ class WalletConnectSdkHelper {
|
|||
id: Long,
|
||||
type: WcTransactionType,
|
||||
): WcTransactionData? {
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val publicKey = Wallet.PublicKey(
|
||||
session.wallet.walletPublicKey,
|
||||
session.wallet.derivedPublicKey,
|
||||
session.wallet.derivationPath,
|
||||
)
|
||||
val walletManager = factory.makeEthereumWalletManager(
|
||||
session.wallet.cardId,
|
||||
publicKey,
|
||||
emptyList(),
|
||||
isTestNet = session.wallet.isTestNet
|
||||
) ?: return null
|
||||
|
||||
|
||||
val walletManager = getWalletManager(session) ?: return null
|
||||
try {
|
||||
walletManager.update()
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -54,15 +43,14 @@ class WalletConnectSdkHelper {
|
|||
return null
|
||||
}
|
||||
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
val balance =
|
||||
walletManager.wallet.amounts[AmountType.Coin]?.value ?: return null
|
||||
wallet.amounts[AmountType.Coin]?.value ?: return null
|
||||
|
||||
val gas = transaction.gas?.hexToBigDecimal()
|
||||
?: transaction.gasLimit?.hexToBigDecimal() ?: return null
|
||||
|
||||
val decimals = blockchain.decimals()
|
||||
val decimals = wallet.blockchain.decimals()
|
||||
|
||||
val value = (transaction.value ?: "0").hexToBigDecimal()
|
||||
?.movePointLeft(decimals) ?: return null
|
||||
|
|
@ -82,8 +70,8 @@ class WalletConnectSdkHelper {
|
|||
val total = value + fee
|
||||
|
||||
val transactionData = TransactionData(
|
||||
amount = Amount(value, blockchain),
|
||||
fee = Amount(fee, blockchain),
|
||||
amount = Amount(value, wallet.blockchain),
|
||||
fee = Amount(fee, wallet.blockchain),
|
||||
sourceAddress = transaction.from,
|
||||
destinationAddress = transaction.to!!,
|
||||
extras = EthereumTransactionExtras(
|
||||
|
|
@ -115,6 +103,21 @@ class WalletConnectSdkHelper {
|
|||
)
|
||||
}
|
||||
|
||||
private fun getWalletManager(session: WalletConnectSession): WalletManager? {
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val publicKey = Wallet.PublicKey(
|
||||
session.wallet.walletPublicKey ?: return null,
|
||||
session.wallet.derivedPublicKey,
|
||||
session.wallet.derivationPath,
|
||||
)
|
||||
val blockchain = session.wallet.getBlockchainForSession()
|
||||
return factory.makeWalletManager(
|
||||
session.wallet.cardId,
|
||||
blockchain,
|
||||
publicKey
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun completeTransaction(data: WcTransactionData): String? {
|
||||
return when (data.type) {
|
||||
WcTransactionType.EthSendTransaction -> sendTransaction(data)
|
||||
|
|
@ -133,9 +136,9 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
is SimpleResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction,
|
||||
Analytics.ActionToLog.WalletConnectTransaction,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error)
|
||||
|
|
@ -164,9 +167,38 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
|
||||
Analytics.ActionToLog.WalletConnectSign,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error.customMessage)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun signBnbTransaction(data: ByteArray, session: WalletConnectActiveData): String? {
|
||||
val command = SignHashCommand(
|
||||
hash = data,
|
||||
walletPublicKey = session.wallet.walletPublicKey ?: return null,
|
||||
derivationPath = session.wallet.derivationPath
|
||||
)
|
||||
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message())
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val key = session.wallet.derivedPublicKey
|
||||
?: session.wallet.walletPublicKey
|
||||
getBnbResultString(
|
||||
key.toHexString(),
|
||||
result.data.signature.toHexString()
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
Analytics.ActionToLog.WalletConnectTransaction,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error.customMessage)
|
||||
|
|
@ -180,8 +212,15 @@ class WalletConnectSdkHelper {
|
|||
session: WalletConnectSession,
|
||||
id: Long,
|
||||
): WcPersonalSignData {
|
||||
val messageData = message.data.removePrefix(HEX_PREFIX).hexToBytes()
|
||||
val messageString = message.data.hexToAscii() ?: message.data
|
||||
val messageData =
|
||||
try {
|
||||
message.data.removePrefix(HEX_PREFIX).hexToBytes()
|
||||
} catch (exception: Exception) {
|
||||
message.data.asciiToHex()!!.hexToBytes()
|
||||
}
|
||||
val messageString = message.data.hexToAscii()
|
||||
?: EthSignHelper.tryToParseEthTypedMessageString(message.data)
|
||||
?: message.data
|
||||
|
||||
val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray()
|
||||
val hashToSign = (prefixData + messageData).toKeccak()
|
||||
|
|
@ -203,29 +242,40 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
|
||||
private fun String.hexToAscii(): String? {
|
||||
return removePrefix(HEX_PREFIX).hexToBytes()
|
||||
.map {
|
||||
val char = it.toInt().toChar()
|
||||
if (char.isAscii()) char else return null
|
||||
}
|
||||
.joinToString("")
|
||||
return try {
|
||||
removePrefix(HEX_PREFIX).hexToBytes()
|
||||
.map {
|
||||
val char = it.toInt().toChar()
|
||||
if (char.isAscii()) char else return null
|
||||
}
|
||||
.joinToString("")
|
||||
} catch (exception: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.asciiToHex(): String? {
|
||||
return map {
|
||||
if (!it.isAscii()) return null
|
||||
Integer.toHexString(it.code)
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? {
|
||||
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey
|
||||
val command = SignHashCommand(hashToSign, wallet.walletPublicKey, wallet.derivationPath)
|
||||
val command = SignHashCommand(hashToSign, wallet.walletPublicKey!!, wallet.derivationPath)
|
||||
return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) {
|
||||
is CompletionResult.Success -> {
|
||||
val hash = result.data.signature
|
||||
return EthereumUtils.prepareSignedMessageData(
|
||||
hash, hashToSign, CryptoUtils.decompressPublicKey(key)
|
||||
hash, hashToSign, CryptoUtils.decompressPublicKey(key!!)
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign,
|
||||
Analytics.ActionToLog.WalletConnectSign,
|
||||
)
|
||||
}
|
||||
Timber.e(result.error.customMessage)
|
||||
|
|
@ -237,5 +287,8 @@ class WalletConnectSdkHelper {
|
|||
companion object {
|
||||
private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n"
|
||||
private const val HEX_PREFIX = "0x"
|
||||
fun getBnbResultString(publicKey: String, signature: String): String {
|
||||
return "{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ import com.tangem.common.card.FirmwareVersion
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -118,9 +119,9 @@ class DetailsMiddleware {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.PurgeWallet,
|
||||
Analytics.ActionToLog.PurgeWallet,
|
||||
card = store.state.detailsState.scanResponse?.card
|
||||
)
|
||||
}
|
||||
|
|
@ -206,11 +207,11 @@ class DetailsMiddleware {
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error = error,
|
||||
actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions,
|
||||
actionToLog = Analytics.ActionToLog.ChangeSecOptions,
|
||||
parameters = mapOf(
|
||||
FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to
|
||||
AnalyticsParam.NEW_SECURITY_OPTION to
|
||||
(selectedOption?.name ?: "")
|
||||
),
|
||||
card = store.state.detailsState.scanResponse?.card
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package com.tangem.tap.features.details.redux.walletconnect
|
|||
|
||||
import android.app.Activity
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
|
|
@ -27,13 +30,16 @@ sealed class WalletConnectAction : Action {
|
|||
data class OpenSession(
|
||||
val wcUri: String,
|
||||
val wallet: WalletForSession,
|
||||
val scanResponse: ScanResponse
|
||||
) : WalletConnectAction()
|
||||
|
||||
object RefuseOpeningSession : WalletConnectAction()
|
||||
|
||||
data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction()
|
||||
|
||||
data class AcceptOpeningSession(val session: WalletConnectSession) : WalletConnectAction()
|
||||
data class AcceptOpeningSession(
|
||||
val session: WalletConnectSession, val chainId: Int?,
|
||||
) : WalletConnectAction()
|
||||
|
||||
data class ApproveSession(
|
||||
val session: WCSession,
|
||||
|
|
@ -79,4 +85,20 @@ sealed class WalletConnectAction : Action {
|
|||
object NotifyCameraPermissionIsRequired : WalletConnectAction(), NotificationAction {
|
||||
override val messageResource = R.string.common_camera_denied_alert_message
|
||||
}
|
||||
|
||||
object BinanceTransaction : WalletConnectAction() {
|
||||
data class Trade(
|
||||
val id: Long, val order: WCBinanceTradeOrder,
|
||||
val sessionData: WalletConnectSession,
|
||||
) : WalletConnectAction()
|
||||
|
||||
data class Transfer(
|
||||
val id: Long, val order: WCBinanceTransferOrder,
|
||||
val sessionData: WalletConnectSession,
|
||||
) : WalletConnectAction()
|
||||
|
||||
data class Sign(
|
||||
val id: Long, val data: ByteArray, val sessionData: WCSession,
|
||||
) : WalletConnectAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
|
@ -13,10 +12,14 @@ import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
|||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
class WalletConnectMiddleware {
|
||||
private val walletConnectManager = WalletConnectManager()
|
||||
|
|
@ -48,8 +51,13 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ClipboardOrScanQr(
|
||||
action.wcUri)))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.ClipboardOrScanQr(
|
||||
action.wcUri
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is WalletConnectAction.ScanCard -> {
|
||||
|
|
@ -78,14 +86,23 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
is WalletConnectAction.RefuseOpeningSession -> {
|
||||
store.dispatch(GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.OpeningSessionRejected
|
||||
))
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.OpeningSessionRejected
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is WalletConnectAction.AcceptOpeningSession -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ApproveWcSession(
|
||||
action.session)))
|
||||
generateWalletKeys(action.session, action.chainId)
|
||||
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.ApproveWcSession(
|
||||
action.session
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is WalletConnectAction.ApproveSession -> {
|
||||
|
|
@ -120,6 +137,39 @@ class WalletConnectMiddleware {
|
|||
is WalletConnectAction.SignMessage -> {
|
||||
walletConnectManager.sendSignedMessage(action.session)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Trade -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
cardId = action.sessionData.wallet.cardId,
|
||||
dAppName = action.sessionData.peerMeta.name
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Transfer -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
cardId = action.sessionData.wallet.cardId,
|
||||
dAppName = action.sessionData.peerMeta.name
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Sign -> {
|
||||
walletConnectManager.signBnb(
|
||||
action.id, action.data, action.sessionData
|
||||
)
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
|
@ -134,60 +184,79 @@ class WalletConnectMiddleware {
|
|||
return@ScanCard
|
||||
}
|
||||
|
||||
val walletManager = getWalletManager(scanResponse).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
|
||||
return@ScanCard
|
||||
}
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
|
||||
null
|
||||
} else {
|
||||
walletManager.wallet.publicKey.blockchainKey
|
||||
}
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(
|
||||
wcUri = wcUri,
|
||||
wallet = WalletForSession(
|
||||
card.cardId,
|
||||
wallet.publicKey.seedKey,
|
||||
derivedKey,
|
||||
wallet.publicKey.derivationPath,
|
||||
card.isTestCard
|
||||
),
|
||||
))
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.OpenSession(
|
||||
wcUri = wcUri,
|
||||
wallet = WalletForSession(
|
||||
scanResponse.card.cardId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
scanResponse.card.isTestCard
|
||||
),
|
||||
scanResponse = scanResponse
|
||||
)
|
||||
)
|
||||
}, {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
|
||||
}, R.string.wallet_connect_scan_card_message))
|
||||
}
|
||||
|
||||
private fun getWalletManager(scanResponse: ScanResponse): WalletManager? {
|
||||
private fun generateWalletKeys(session: WalletConnectSession, chainId: Int?) {
|
||||
val scanResponse = store.state.walletConnectState.scanResponse ?: return
|
||||
val walletManager = getWalletManager(scanResponse, session.peerMeta, chainId) ?: return
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
val derivedKey =
|
||||
if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
|
||||
null
|
||||
} else {
|
||||
walletManager.wallet.publicKey.blockchainKey
|
||||
}
|
||||
val updatedWallet = session.wallet.copy(
|
||||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivedPublicKey = derivedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
blockchain = wallet.blockchain
|
||||
)
|
||||
val updatedSession = session.copy(wallet = updatedWallet)
|
||||
walletConnectManager.updateSession(updatedSession)
|
||||
}
|
||||
|
||||
private fun getWalletManager(
|
||||
scanResponse: ScanResponse, peer: WCPeerMeta, chainId: Int?
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
|
||||
val wcBlockchain = if (scanResponse.card.isTestCard) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
Blockchain.Ethereum
|
||||
}
|
||||
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
|
||||
chainId = chainId, peer = peer, isTestNet = scanResponse.card.isTestCard
|
||||
) ?: if (scanResponse.card.isTestCard) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||
|
||||
Timber.d(blockchain.fullName)
|
||||
|
||||
|
||||
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
|
||||
store.state.walletState.getWalletManager(wcBlockchain)
|
||||
?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
|
||||
store.state.walletState.getWalletManager(blockchain)
|
||||
?: factory.makeWalletManagerForApp(scanResponse, blockchain)
|
||||
?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
}
|
||||
} else {
|
||||
val walletManager = factory.makeWalletManagerForApp(scanResponse, blockchain)
|
||||
if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains(
|
||||
wcBlockchain) == true
|
||||
blockchain
|
||||
) != true
|
||||
) {
|
||||
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
|
||||
} else {
|
||||
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
|
||||
?.also {
|
||||
currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain)
|
||||
}
|
||||
walletManager?.let {
|
||||
currenciesRepository.saveAddedBlockchain(
|
||||
card.cardId,
|
||||
blockchain
|
||||
)
|
||||
}
|
||||
}
|
||||
return walletManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,9 @@ class WalletConnectReducer {
|
|||
sessions = state.sessions + action.session
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
state.copy(scanResponse = action.scanResponse)
|
||||
}
|
||||
is WalletConnectAction.SetSessionsRestored ->
|
||||
WalletConnectState(sessions = action.sessions)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
|
|
@ -13,6 +15,7 @@ import com.trustwallet.walletconnect.models.session.WCSession
|
|||
data class WalletConnectState(
|
||||
val loading: Boolean = false,
|
||||
val sessions: List<WalletConnectSession> = listOf(),
|
||||
val scanResponse: ScanResponse? = null
|
||||
)
|
||||
|
||||
data class WalletConnectSession(
|
||||
|
|
@ -21,18 +24,58 @@ data class WalletConnectSession(
|
|||
val wallet: WalletForSession,
|
||||
val session: WCSession,
|
||||
val peerMeta: WCPeerMeta,
|
||||
)
|
||||
) {
|
||||
fun getAddress(): String? {
|
||||
val key = wallet.derivedPublicKey ?: wallet.walletPublicKey ?: return null
|
||||
return wallet.blockchain?.makeAddresses(key)?.first()?.value
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletForSession(
|
||||
val cardId: String,
|
||||
val walletPublicKey: ByteArray,
|
||||
val walletPublicKey: ByteArray?,
|
||||
val derivedPublicKey: ByteArray?,
|
||||
val derivationPath: DerivationPath?,
|
||||
val isTestNet: Boolean = false,
|
||||
val blockchain: Blockchain? = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||
) {
|
||||
val chainId
|
||||
get() = if (isTestNet) 4 else 1
|
||||
|
||||
fun getBlockchainForSession(): Blockchain {
|
||||
return blockchain ?: if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as WalletForSession
|
||||
|
||||
if (cardId != other.cardId) return false
|
||||
if (walletPublicKey != null) {
|
||||
if (other.walletPublicKey == null) return false
|
||||
if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false
|
||||
} else if (other.walletPublicKey != null) return false
|
||||
if (derivedPublicKey != null) {
|
||||
if (other.derivedPublicKey == null) return false
|
||||
if (!derivedPublicKey.contentEquals(other.derivedPublicKey)) return false
|
||||
} else if (other.derivedPublicKey != null) return false
|
||||
if (derivationPath != other.derivationPath) return false
|
||||
if (isTestNet != other.isTestNet) return false
|
||||
if (blockchain != other.blockchain) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = cardId.hashCode()
|
||||
result = 31 * result + (walletPublicKey?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (derivationPath?.hashCode() ?: 0)
|
||||
result = 31 * result + isTestNet.hashCode()
|
||||
result = 31 * result + (blockchain?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -46,6 +89,12 @@ sealed class WalletConnectDialog : StateDialog {
|
|||
WalletConnectDialog()
|
||||
|
||||
data class PersonalSign(val data: PersonalSignDialogData) : WalletConnectDialog()
|
||||
data class BnbTransactionDialog(val data: BinanceMessageData,
|
||||
val session: WCSession,
|
||||
val sessionId: Long,
|
||||
val cardId: String,
|
||||
val dAppName: String
|
||||
) : WalletConnectDialog()
|
||||
}
|
||||
|
||||
data class WcTransactionData(
|
||||
|
|
@ -59,7 +108,7 @@ data class WcTransactionData(
|
|||
|
||||
enum class WcTransactionType {
|
||||
EthSignTransaction,
|
||||
EthSendTransaction,
|
||||
EthSendTransaction
|
||||
}
|
||||
|
||||
data class WcPersonalSignData(
|
||||
|
|
@ -68,4 +117,29 @@ data class WcPersonalSignData(
|
|||
val id: Long,
|
||||
val dialogData: PersonalSignDialogData,
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
sealed class BinanceMessageData(
|
||||
val address: String,
|
||||
val data: ByteArray,
|
||||
) {
|
||||
class Trade(
|
||||
val tradeData: List<TradeData>,
|
||||
address: String,
|
||||
data: ByteArray,
|
||||
) : BinanceMessageData(address, data)
|
||||
|
||||
class Transfer(
|
||||
val outputAddress: String,
|
||||
val amount: String,
|
||||
address: String,
|
||||
data: ByteArray,
|
||||
) : BinanceMessageData(address, data)
|
||||
}
|
||||
|
||||
data class TradeData(
|
||||
val price: String,
|
||||
val quantity: String,
|
||||
val amount: String,
|
||||
val symbol: String
|
||||
)
|
||||
|
|
@ -73,7 +73,10 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
}
|
||||
tv_card_id.text = cardId
|
||||
tv_issuer.text = state.cardInfo.issuer
|
||||
tv_signed_hashes.text = state.cardInfo.signedHashes.toString()
|
||||
tv_signed_hashes.text = getString(
|
||||
R.string.details_row_subtitle_signed_hashes_format,
|
||||
state.cardInfo.signedHashes.toString()
|
||||
)
|
||||
}
|
||||
|
||||
tv_signed_hashes.show(!state.isTangemTwins)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
|
||||
class BnbTransactionDialog {
|
||||
companion object {
|
||||
fun create(
|
||||
data: BinanceMessageData,
|
||||
session: WCSession,
|
||||
sessionId: Long,
|
||||
cardId: String,
|
||||
dAppName: String,
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
|
||||
val message = when (data) {
|
||||
is BinanceMessageData.Trade -> data.tradeData.map {
|
||||
context.getString(
|
||||
R.string.wallet_connect_bnb_trade_order_message,
|
||||
it.symbol,
|
||||
it.price,
|
||||
it.quantity,
|
||||
it.amount
|
||||
)
|
||||
}.joinToString ( "\n\n" )
|
||||
is BinanceMessageData.Transfer -> context.getString(
|
||||
R.string.wallet_connect_bnb_transaction_message,
|
||||
data.address,
|
||||
data.outputAddress,
|
||||
data.amount
|
||||
)
|
||||
}
|
||||
|
||||
val fullMessage = context.getString(
|
||||
R.string.wallet_connect_bnb_sign_message,
|
||||
dAppName, cardId, message
|
||||
)
|
||||
val positiveButtonTitle = context.getText(R.string.common_sign)
|
||||
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect))
|
||||
setMessage(fullMessage)
|
||||
setPositiveButton(positiveButtonTitle) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.BinanceTransaction.Sign(
|
||||
id = sessionId,
|
||||
data = data.data,
|
||||
sessionData = session
|
||||
))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
|
||||
store.dispatch(WalletConnectAction.RejectRequest(session, sessionId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.tap.features.home.redux
|
||||
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.GetCardSourceParams
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.onCardScanned
|
||||
|
|
@ -46,7 +49,13 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
}
|
||||
}
|
||||
is HomeAction.ReadCard -> handleReadCard()
|
||||
is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
|
||||
is HomeAction.GoToShop -> {
|
||||
store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.GET_CARD,
|
||||
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param)
|
||||
)
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,25 +37,6 @@ class OnboardingManager(
|
|||
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)
|
||||
}
|
||||
|
||||
// suspend fun loadArtwork(): Artwork {
|
||||
// cardInfo = OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
|
||||
// return loadArtwork(scanResponse.card.getOrLoadCardArtworkUrl(cardInfo))
|
||||
// }
|
||||
//
|
||||
// suspend fun loadArtwork(url: String): Artwork {
|
||||
// return withContext(Dispatchers.Main) {
|
||||
// suspendCoroutine { continuation ->
|
||||
// UrlBitmapLoader().loadBitmap(url) {
|
||||
// val result = when (it) {
|
||||
// is Result.Success -> Artwork(url, it.data)
|
||||
// is Result.Failure -> Artwork(url, null)
|
||||
// }
|
||||
// continuation.resume(result)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {
|
||||
val balance = when (val result = walletManager.safeUpdate()) {
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -130,7 +130,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchFirstStep -> {
|
||||
val manager = TwinCardsManager(getScanResponse().card, action.reader)
|
||||
val manager = TwinCardsManager(
|
||||
card = getScanResponse().card,
|
||||
assetReader = action.reader,
|
||||
analyticsHandler = store.state.globalState.analyticsHandlers
|
||||
)
|
||||
store.dispatch(TwinCardsAction.CardsManager.Set(manager))
|
||||
|
||||
scope.launch {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import com.squareup.picasso.Picasso
|
|||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.common.redux.navigation.ShareElement
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
|
|
@ -14,7 +13,7 @@ sealed class OnboardingWalletAction : Action {
|
|||
object ProceedBackup : OnboardingWalletAction()
|
||||
|
||||
object LoadArtwork : OnboardingWalletAction()
|
||||
class SetArtworkUrl(val artwork: Artwork) : OnboardingWalletAction()
|
||||
class SetArtworkUrl(val artworkUrl: String?) : OnboardingWalletAction()
|
||||
|
||||
object OnBackPressed : OnboardingWalletAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.card.Card
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.GetCardSourceParams
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
|
|
@ -14,8 +17,8 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.domain.extensions.hasWallets
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -61,11 +64,16 @@ private fun handleWalletAction(action: Action) {
|
|||
}
|
||||
store.dispatch(action)
|
||||
}
|
||||
is OnboardingNoteAction.LoadCardArtwork -> {
|
||||
// scope.launch {
|
||||
// val artwork = onboardingManager.loadArtwork()
|
||||
// store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(artwork))
|
||||
// }
|
||||
is OnboardingWalletAction.LoadArtwork -> {
|
||||
scope.launch {
|
||||
val artwork = onboardingManager?.loadArtworkUrl()
|
||||
val cardArtwork = if (artwork == Artwork.DEFAULT_IMG_URL) {
|
||||
null
|
||||
} else {
|
||||
artwork
|
||||
}
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(cardArtwork))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
|
|
@ -207,7 +215,13 @@ private fun handleBackupAction(action: BackupAction) {
|
|||
}
|
||||
}
|
||||
is BackupAction.GoToShop -> {
|
||||
store.dispatchOpenUrl(BUY_WALLET_URL)
|
||||
backupState.buyAdditionalCardsUrl?.let { url ->
|
||||
store.dispatchOpenUrl(url)
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.GET_CARD,
|
||||
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
|
||||
)
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishAddingBackupCards -> {
|
||||
if (backupService.addedBackupCardsCount == 1) {
|
||||
|
|
@ -223,11 +237,13 @@ private fun handleBackupAction(action: BackupAction) {
|
|||
AccessCodeError.CodeTooShort
|
||||
))
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
|
||||
}
|
||||
}
|
||||
is BackupAction.SaveAccessCodeConfirmation -> {
|
||||
if (action.accessCodeConfirmation == backupState.accessCode) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
backupService.setAccessCode(action.accessCodeConfirmation)
|
||||
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet
|
|||
OnboardingWalletState(step = OnboardingWalletStep.CreateWallet)
|
||||
is OnboardingWalletAction.ProceedBackup -> state.copy(step = OnboardingWalletStep.Backup)
|
||||
is OnboardingWalletAction.SetArtworkUrl -> {
|
||||
state.copy(artwork = action.artwork)
|
||||
state.copy(cardArtworkUrl = action.artworkUrl)
|
||||
}
|
||||
is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done)
|
||||
else -> state
|
||||
|
|
@ -60,7 +60,7 @@ class BackupReducer {
|
|||
state.copy(backupStep = BackupStep.SetAccessCode)
|
||||
}
|
||||
BackupAction.ShowEnterAccessCodeScreen -> {
|
||||
state.copy(backupStep = BackupStep.EnterAccessCode)
|
||||
state.copy(backupStep = BackupStep.EnterAccessCode, accessCodeError = null)
|
||||
}
|
||||
is BackupAction.SaveFirstAccessCode -> {
|
||||
state.copy(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
|
|
@ -11,7 +10,7 @@ import org.rekotlin.StateType
|
|||
data class OnboardingWalletState(
|
||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||
val backupState: BackupState = BackupState(),
|
||||
val artwork: Artwork? = null,
|
||||
val cardArtworkUrl: String? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
) : StateType {
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import android.animation.PropertyValuesHolder
|
|||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
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
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class BackupCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
|
|
|
|||
|
|
@ -2,23 +2,22 @@ package com.tangem.tap.features.onboarding.products.wallet.ui
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -62,6 +61,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
store.dispatch(OnboardingWalletAction.Init)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
store.dispatch(OnboardingWalletAction.LoadArtwork)
|
||||
|
||||
addBackPressHandler(this)
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +85,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
|
||||
state.artwork?.artwork?.let { imv_front_card.setImageBitmap(it) }
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imv_front_card)
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imv_first_backup_card)
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imv_second_backup_card)
|
||||
|
||||
pb_state.max = 6
|
||||
pb_state.progress = state.getProgressStep()
|
||||
|
||||
|
|
@ -99,6 +103,14 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadImageIntoImageView(url: String?, view: ImageView) {
|
||||
Picasso.get()
|
||||
.load(url)
|
||||
.error(R.drawable.card_placeholder_black)
|
||||
.placeholder(R.drawable.card_placeholder_black)
|
||||
?.into(view)
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() {
|
||||
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
|
||||
|
|
@ -240,15 +252,11 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
private fun showEnterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showEnterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) }
|
||||
|
||||
}
|
||||
|
||||
private fun showReenterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showReenterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) }
|
||||
|
||||
}
|
||||
|
||||
private fun showWritePrimaryCard(state: BackupState) {
|
||||
|
|
@ -312,7 +320,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
tv_header.text =
|
||||
getString(R.string.onboarding_title_backup_card_format, cardNumber)
|
||||
tv_body.text = getString(
|
||||
R.string.onboarding_subtitle_scan_primary_card_format,
|
||||
R.string.onboarding_subtitle_scan_backup_card_format,
|
||||
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1])
|
||||
)
|
||||
btn_main_action.text = getString(
|
||||
|
|
@ -337,7 +345,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
tab_layout_backup_info.hide()
|
||||
|
||||
tv_body.text = text
|
||||
btn_main_action.text = getText(R.string.common_continue)
|
||||
btn_main_action.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
btn_alternative_action.hide()
|
||||
btn_main_action.setOnClickListener {
|
||||
lav_confetti.cancelAnimation()
|
||||
|
|
@ -362,7 +370,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.shop_menu -> {
|
||||
store.dispatchOpenUrl(BUY_WALLET_URL)
|
||||
store.dispatch(BackupAction.GoToShop)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -180,7 +181,7 @@ private fun sendTransaction(
|
|||
tangemSdk.config.linkedTerminal = isLinkedTerminal
|
||||
when (sendResult) {
|
||||
is SimpleResult.Success -> {
|
||||
FirebaseAnalyticsHandler.triggerEvent(
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.TRANSACTION_IS_SENT,
|
||||
card = card,
|
||||
blockchain = walletManager.wallet.blockchain.currency
|
||||
|
|
@ -204,13 +205,13 @@ private fun sendTransaction(
|
|||
}
|
||||
is SimpleResult.Failure -> {
|
||||
when (sendResult.error) {
|
||||
is CreateAccountUnderfunded -> {
|
||||
val error = sendResult.error as CreateAccountUnderfunded
|
||||
is BlockchainSdkError.CreateAccountUnderfunded -> {
|
||||
val error = sendResult.error as BlockchainSdkError.CreateAccountUnderfunded
|
||||
val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0"
|
||||
val symbol = error.minReserve.currencySymbol
|
||||
dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol))))
|
||||
}
|
||||
is SendException -> {
|
||||
is BlockchainSdkError.SendException -> {
|
||||
sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) }
|
||||
}
|
||||
is Throwable -> {
|
||||
|
|
@ -239,11 +240,11 @@ private fun sendTransaction(
|
|||
}
|
||||
else -> {
|
||||
(sendResult.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
|
||||
Analytics.ActionToLog.SendTransaction,
|
||||
mapOf(
|
||||
FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
|
||||
AnalyticsParam.BLOCKCHAIN
|
||||
to walletManager.wallet.blockchain.currency),
|
||||
card = card,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -137,6 +137,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
|
||||
adapter.submitList(state.sendWarningsList)
|
||||
rv.show(state.sendWarningsList.isNotEmpty())
|
||||
|
||||
fg.toolbar.title = fg.getString(
|
||||
R.string.send_title_currency_format,
|
||||
state.amountState.mainCurrency.currencySymbol
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {
|
||||
|
|
|
|||
|
|
@ -23,29 +23,35 @@ sealed class CurrencyListItem {
|
|||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>,
|
||||
): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens
|
||||
val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens
|
||||
val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens
|
||||
return createBlockchainList(blockchains) +
|
||||
createTokensList(R.string.add_tokens_subtitle_ethereum_tokens, Blockchain.Ethereum, tokens) +
|
||||
createTokensList(R.string.add_tokens_subtitle_bsc_tokens, Blockchain.BSC, tokens) +
|
||||
createTokensList(R.string.add_tokens_subtitle_binance_tokens, Blockchain.Binance, tokens) +
|
||||
createTokensList(R.string.add_tokens_subtitle_avalanche_tokens, Blockchain.Avalanche, tokens)
|
||||
}
|
||||
|
||||
val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum }
|
||||
val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC }
|
||||
val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance }
|
||||
return listOf(TitleListItem(blockchainsTitle)) +
|
||||
blockchains.map { BlockchainListItem(it) } +
|
||||
listOf(TitleListItem(ethereumTokensTitle, blockchain = Blockchain.Ethereum)) +
|
||||
ethereumTokens.map { TokenListItem(it) } +
|
||||
listOf(TitleListItem(bscTokensTitle, blockchain = Blockchain.BSC)) +
|
||||
bscTokens.map { TokenListItem(it) } +
|
||||
listOf(TitleListItem(binanceTokensTitle, blockchain = Blockchain.Binance)) +
|
||||
binanceTokens.map { TokenListItem(it) }
|
||||
private fun createBlockchainList(blockchains: List<Blockchain>): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
return listOf(TitleListItem(blockchainsTitle)) + blockchains.map { BlockchainListItem(it) }
|
||||
}
|
||||
|
||||
private fun createTokensList(
|
||||
@StringRes titleResId: Int,
|
||||
blockchain: Blockchain,
|
||||
tokens: List<Token>
|
||||
): List<CurrencyListItem> {
|
||||
val filteredTokens = tokens.filter {
|
||||
it.blockchain == blockchain || it.blockchain == blockchain.getTestnetVersion()
|
||||
}
|
||||
val tokensListItem = filteredTokens.map { TokenListItem(it) }
|
||||
return listOf(TitleListItem(titleResId, blockchain = blockchain)) + tokensListItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.removeTokensForBlockchain(blockchain: Blockchain): List<CurrencyListItem> {
|
||||
return filterNot {
|
||||
it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain
|
||||
it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,18 +59,31 @@ fun List<CurrencyListItem>.addTokensForBlockchain(
|
|||
blockchain: Blockchain, fullCurrenciesList: List<CurrencyListItem>,
|
||||
): List<CurrencyListItem> {
|
||||
val tokensToAdd = fullCurrenciesList.filter {
|
||||
it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain
|
||||
it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
val indexToInsert = indexOfFirst {
|
||||
it is CurrencyListItem.TitleListItem && it.blockchain == blockchain
|
||||
it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
} + 1
|
||||
return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) }
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.toggleHeaderContentShownValue(blockchain: Blockchain) {
|
||||
map {
|
||||
if (it is CurrencyListItem.TitleListItem && it.blockchain == blockchain) {
|
||||
if (it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)) {
|
||||
it.isContentShown = !it.isContentShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if you do not check the testNet blockchains, it will not collapse
|
||||
private fun CurrencyListItem.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean {
|
||||
return when (this) {
|
||||
is CurrencyListItem.TitleListItem -> this.blockchain?.isTheSameOrTestnetBlockchain(blockchain) ?: false
|
||||
is CurrencyListItem.BlockchainListItem -> this.blockchain.isTheSameOrTestnetBlockchain(blockchain)
|
||||
is CurrencyListItem.TokenListItem -> this.token.blockchain.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean {
|
||||
return this == blockchain || this == blockchain.getTestnetVersion()
|
||||
}
|
||||
|
|
@ -4,11 +4,11 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.toMapKey
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -32,7 +32,7 @@ class TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies()
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
}
|
||||
next(action)
|
||||
|
|
@ -40,7 +40,7 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
|
||||
private fun handleLoadCurrencies() {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
val isTestcard = scanResponse.card.isTestCard
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ class TokensMiddleware {
|
|||
val blockchains = currenciesRepository.getBlockchains(
|
||||
cardFirmware = scanResponse.card.firmwareVersion,
|
||||
isTestNet = isTestcard
|
||||
)
|
||||
).sortedBy { it.fullName }
|
||||
val currencies =
|
||||
CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList()
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
|
|
@ -94,14 +94,14 @@ class TokensMiddleware {
|
|||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data.entries
|
||||
val updatedDerivedKeys =
|
||||
mutableMapOf<KeyWalletPublicKey, List<ExtendedPublicKey>>()
|
||||
mutableMapOf<KeyWalletPublicKey, ExtendedPublicKeysMap>()
|
||||
|
||||
newDerivedKeys.forEach { entry ->
|
||||
val derivationData = derivationDataList.find {
|
||||
it.mapKeyOfWalletPublicKey == entry.key
|
||||
} ?: return@forEach
|
||||
updatedDerivedKeys[entry.key] =
|
||||
derivationData.alreadyDerivedKeys + entry.value.toList()
|
||||
ExtendedPublicKeysMap(derivationData.alreadyDerivedKeys + entry.value)
|
||||
}
|
||||
|
||||
val updatedScanResponse = scanResponse.copy(
|
||||
|
|
@ -133,9 +133,9 @@ class TokensMiddleware {
|
|||
.mapNotNull { it.derivationPath() }
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf()
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath }
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
return DerivationData(
|
||||
|
|
@ -147,7 +147,7 @@ class TokensMiddleware {
|
|||
|
||||
private class DerivationData(
|
||||
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
||||
val alreadyDerivedKeys: List<ExtendedPublicKey>,
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap,
|
||||
val mapKeyOfWalletPublicKey: ByteArrayKey
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -146,4 +146,5 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String): WalletAction()
|
||||
}
|
||||
|
|
@ -246,7 +246,8 @@ data class WalletData(
|
|||
val fiatRateString: String? = null,
|
||||
val fiatRate: BigDecimal? = null,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val currency: Currency
|
||||
val currency: Currency,
|
||||
val walletRent: WalletRent? = null,
|
||||
) {
|
||||
fun shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list ?: return false
|
||||
|
|
@ -254,6 +255,11 @@ data class WalletData(
|
|||
}
|
||||
}
|
||||
|
||||
data class WalletRent(
|
||||
val minRentValue: String,
|
||||
val rentExemptValue: String
|
||||
)
|
||||
|
||||
sealed interface Currency {
|
||||
|
||||
val blockchain: com.tangem.blockchain.common.Blockchain
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -31,6 +33,7 @@ class MultiWalletMiddleware {
|
|||
when (action) {
|
||||
is WalletAction.MultiWallet.AddWalletManagers -> {
|
||||
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
|
||||
action.walletManagers.forEach { checkForRentWarning(it) }
|
||||
}
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.walletData != null) {
|
||||
|
|
@ -132,7 +135,7 @@ class MultiWalletMiddleware {
|
|||
val walletManager = walletState?.getWalletManager(Blockchain.Ethereum)
|
||||
?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum)
|
||||
|
||||
val tokenFinder = walletManager as TokenFinder
|
||||
val tokenFinder = walletManager as? TokenFinder ?: return
|
||||
scope.launch {
|
||||
val result = tokenFinder.findTokens()
|
||||
withContext(Dispatchers.Main) {
|
||||
|
|
@ -197,6 +200,26 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkForRentWarning(walletManager: WalletManager) {
|
||||
val rentProvider = walletManager as? RentProvider ?: return
|
||||
|
||||
scope.launch {
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is Result.Success -> {
|
||||
val currency = walletManager.wallet.blockchain.currency
|
||||
val minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency")
|
||||
val rentExempt = ("${result.data.stripZeroPlainString()} $currency")
|
||||
store.dispatchOnMain(WalletAction.SetWalletRent(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
minRent = minRent,
|
||||
rentExempt = rentExempt
|
||||
))
|
||||
}
|
||||
is Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTokens(
|
||||
tokens: List<Token>,
|
||||
walletState: WalletState?,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.common.extensions.isZero
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -104,13 +104,13 @@ class WalletMiddleware {
|
|||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val scanNoteResponse = globalState.scanResponse?.copy(card = result.data)
|
||||
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse, false) }
|
||||
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) }
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
FirebaseAnalyticsHandler.logCardSdkError(
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
error,
|
||||
FirebaseAnalyticsHandler.ActionToLog.CreateWallet,
|
||||
Analytics.ActionToLog.CreateWallet,
|
||||
card = store.state.detailsState.scanResponse?.card
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.blockchain.extensions.SimpleResult
|
|||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.isGreaterThan
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
|
|
@ -75,7 +74,7 @@ class WarningsMiddleware {
|
|||
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
|
||||
}
|
||||
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
|
||||
addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,6 +245,17 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
)
|
||||
newState = newState.copy(wallets = wallets)
|
||||
}
|
||||
is WalletAction.SetWalletRent -> {
|
||||
var walletData = newState.getWalletData(action.blockchain)
|
||||
if (walletData == null) {
|
||||
newState
|
||||
} else {
|
||||
walletData = walletData.copy(walletRent = WalletRent(action.minRent, action.rentExempt))
|
||||
newState = newState.copy(
|
||||
wallets = newState.replaceSomeWallets(listOf(walletData))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
|
|
@ -27,6 +28,8 @@ import kotlinx.android.synthetic.main.item_popular_token.view.*
|
|||
import kotlinx.android.synthetic.main.layout_balance_error.*
|
||||
import kotlinx.android.synthetic.main.layout_balance_wallet_details.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_details.*
|
||||
import kotlinx.android.synthetic.main.layout_wallet_details.card_balance
|
||||
import kotlinx.android.synthetic.main.layout_wallet_details_rent.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber<WalletState> {
|
||||
|
|
@ -124,11 +127,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
|
|||
|
||||
handleDialogs(state.walletDialog)
|
||||
handleCurrencyIcon(selectedWallet)
|
||||
handleWalletRent(selectedWallet.walletRent)
|
||||
|
||||
srl_wallet_details.setOnRefreshListener {
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
blockchain = selectedWallet.currency?.blockchain
|
||||
blockchain = selectedWallet.currency.blockchain
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -141,6 +145,18 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
|
|||
btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed)
|
||||
}
|
||||
|
||||
private fun handleWalletRent(rent: WalletRent?) {
|
||||
val rent = rent.guard {
|
||||
l_rent_warning.hide()
|
||||
return
|
||||
}
|
||||
|
||||
val warningMessage = requireContext().getString(
|
||||
R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue)
|
||||
tv_rent_warning_message.text = warningMessage
|
||||
l_rent_warning.show()
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(wallet: WalletData) {
|
||||
Picasso.get().loadCurrenciesIcon(
|
||||
imageView = iv_currency,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
|
||||
import com.google.android.play.core.review.ReviewManagerFactory
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.getActivity
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
|
|
@ -102,13 +101,14 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
WarningMessage.Type.AppRating -> {
|
||||
view.group_controls_temporary.hide()
|
||||
view.group_controls_rating.show()
|
||||
val analyticsHandler = store.state.globalState.analyticsHandlers
|
||||
view.btn_close.setOnClickListener {
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
|
||||
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
|
||||
}
|
||||
view.btn_can_be_better.setOnClickListener {
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
|
||||
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
|
||||
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
|
||||
|
|
@ -116,7 +116,7 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
view.btn_really_cool.setOnClickListener {
|
||||
val activity = view.context.getActivity() ?: return@setOnClickListener
|
||||
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
|
||||
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
|
||||
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
val reviewManager = ReviewManagerFactory.create(activity)
|
||||
val task = reviewManager.requestReviewFlow()
|
||||
|
|
|
|||
|
|
@ -78,28 +78,38 @@
|
|||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_second_backup_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:visibility="gone"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:scaleType="fitCenter"
|
||||
|
||||
/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_first_backup_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_front_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/card_placeholder_new"
|
||||
android:transitionName="imv_front_card"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -182,6 +192,7 @@
|
|||
app:tabBackground="@drawable/selector_tabs_dots"
|
||||
app:tabIndicator="@null"
|
||||
app:tabPaddingStart="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:tabPaddingEnd="8dp"
|
||||
app:tabGravity="center"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@
|
|||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.MyTheme.Toolbar"
|
||||
app:menu="@menu/wallet_details"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
android:theme="@style/ThemeOverlay.MyTheme.Toolbar"
|
||||
app:title=" " />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
|
@ -46,7 +46,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_currency_title"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -79,7 +78,6 @@
|
|||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="tv_currency_subtitle,tv_currency_title" />
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_pending_transaction"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -100,6 +98,18 @@
|
|||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_rent_warning"
|
||||
layout="@layout/layout_wallet_details_rent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_pending_transaction_warning"
|
||||
|
|
@ -131,22 +141,6 @@
|
|||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="l_wallet_details,card_pending_transaction_warning" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_trade"
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:text="@string/wallet_button_buy"
|
||||
app:icon="@drawable/ic_arrow_up"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_sell"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_left_button"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -161,6 +155,22 @@
|
|||
app:barrierDirection="start"
|
||||
app:constraint_referenced_ids="btn_sell, btn_confirm" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_trade"
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:text="@string/wallet_button_buy"
|
||||
app:icon="@drawable/ic_arrow_up"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_sell"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/l_rent_warning"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sell"
|
||||
style="@style/TapButtonWithIcon"
|
||||
|
|
@ -172,10 +182,10 @@
|
|||
android:text="@string/wallet_button_sell"
|
||||
app:icon="@drawable/ic_arrow_down"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_confirm"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_trade"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_confirm"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintTop_toBottomOf="@+id/l_rent_warning"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
|
@ -187,12 +197,11 @@
|
|||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:text="@string/wallet_button_send"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_sell"
|
||||
app:icon="@drawable/ic_send"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_sell"
|
||||
app:layout_constraintTop_toBottomOf="@+id/l_rent_warning"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@
|
|||
android:layout_marginStart="78dp"
|
||||
android:layout_marginEnd="78dp"
|
||||
android:layout_marginBottom="92dp"
|
||||
android:text="@string/common_create"
|
||||
android:text="@string/common_continue"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
android:layout_marginTop="44dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:hint="Access code"
|
||||
android:hint="@string/onboarding_wallet_info_title_third"
|
||||
app:boxStrokeColor="@color/selector_edit_text"
|
||||
app:boxStrokeWidth="1dp"
|
||||
app:endIconDrawable="@drawable/selector_password_toggle"
|
||||
|
|
@ -53,6 +53,9 @@
|
|||
android:layout_marginStart="78dp"
|
||||
android:layout_marginEnd="78dp"
|
||||
android:layout_marginBottom="92dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toTopOf="@id/til_access_code"
|
||||
app:layout_constraintVertical_bias="1"
|
||||
android:text="@string/common_create"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
|
|
|||
47
app/src/main/res/layout/layout_wallet_details_rent.xml
Normal file
47
app/src/main/res/layout/layout_wallet_details_rent.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card_balance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="3dp"
|
||||
app:cardBackgroundColor="@color/darkGray2">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_rent_warning_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/common_warning"
|
||||
android:textColor="@color/lightGray0"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_rent_warning_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:textColor="@color/lightGray0"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_rent_warning_title"
|
||||
tools:text="@string/solana_rent_warning" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<string name="twins_recreate_title_preparing" translatable="false">Preparing card</string>
|
||||
<string name="twins_recreate_title_creating_wallet" translatable="false">Creating wallet</string>
|
||||
<string name="twins_recreate_subtitle" translatable="false">The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning.</string>
|
||||
<string name="twins_recreate_button_format" translatable="false">Tap the card #%s</string>
|
||||
<string name="twins_recreate_button_format" translatable="false">Scan the card #%s</string>
|
||||
<string name="twins_recreate_alert" translatable="false">You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet.</string>
|
||||
<string name="twins_wrong_card_error" translatable="false">You\'ve tapped the same card. To create twin wallet you need to tap a second twin card.</string>
|
||||
|
||||
|
|
@ -101,6 +101,7 @@
|
|||
<string name="add_tokens_subtitle_ethereum_tokens" translatable="false">Ethereum tokens</string>
|
||||
<string name="add_tokens_subtitle_bsc_tokens" translatable="false">Binance Smart Chain tokens</string>
|
||||
<string name="add_tokens_subtitle_binance_tokens" translatable="false">Binance Chain tokens</string>
|
||||
<string name="add_tokens_subtitle_avalanche_tokens" translatable="false">Avalanche C-Chain tokens</string>
|
||||
<string name="wallet_address_button_copy" translatable="false">Copy</string>
|
||||
<string name="wallet_address_button_share" translatable="false">Share</string>
|
||||
<string name="wallet_button_add_tokens" translatable="false">+ Add tokens</string>
|
||||
|
|
@ -294,4 +295,23 @@
|
|||
|
||||
<string name="details_manage_security_access_code_disclaimer" translatable="false">Access code is available only for cards with a backup.</string>
|
||||
|
||||
|
||||
|
||||
<string name="wallet_connect_bnb_transaction_signed" translatable="false">The BNB transaction has been successfully signed and sent to the Dapp</string>
|
||||
<string name="wallet_connect_bnb_sign_message" translatable="false">Dapp %s, requesting to\n
|
||||
sign BNB transaction with\n
|
||||
Card: %s\n
|
||||
\n
|
||||
%s</string>
|
||||
<string name="wallet_connect_bnb_transaction_message" translatable="false">Transaction details:\n
|
||||
From: %s\n
|
||||
To: %s\n
|
||||
Amount: %s</string>
|
||||
<string name="wallet_connect_bnb_trade_order_message" translatable="false">Trade order for %s\n
|
||||
Price: %s\n
|
||||
Amount to receive: %s\n
|
||||
Amount to pay: %s</string>
|
||||
|
||||
<string name="solana_rent_warning" translatable="false">Solana network charges a rent of %s every 2 days. Accounts that can’t afford the rent are purged from the network. Deposit your account with more than %s to use it for free.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.5.30',
|
||||
build_gradle: '7.0.2',
|
||||
kotlin : '1.6.10',
|
||||
build_gradle: '7.0.4',
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue