Updated on 2026-08-14

This commit is contained in:
Tangem 2022-02-14 14:53:40 +03:00
commit f823a41b4b
104 changed files with 19896 additions and 9976 deletions

View file

@ -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-64'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-131'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-131'
// 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'

View file

@ -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

View file

@ -0,0 +1,863 @@
[
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/ZRX/logo.png",
"symbol": "ZRX",
"name": "0x",
"contractAddress": "0x596fa47043f99a4e0f122243b841e55375cde0d2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/1INCH/logo.png",
"symbol": "1INCH",
"name": "1inch",
"contractAddress": "0xd501281565bf7789224523144fe5d98e8b28f267"
},
{
"decimalCount": 8,
"customIconUrl": "https://github.com/blocknetdx/documentation/blob/master/docs/img/icons/Blocknet_symbol_dark_RGB.png",
"symbol": "ABLOCK",
"name": "ANY Blocknet",
"contractAddress": "0xc931f61b1534eb21d8c11b24f3f5ab2471d4ab50"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0x1ECd47FF4d9598f89721A2866BFEb99505a413Ed/logo.png",
"symbol": "AVME",
"name": "AVME",
"contractAddress": "0x1ecd47ff4d9598f89721a2866bfeb99505a413ed"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/AAVE/logo.png",
"symbol": "AAVE",
"name": "Aave",
"contractAddress": "0x63a72806098bd3d9520cc43356dd78afe5d386d9"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x9E037dE681CaFA6E661e6108eD9c2bd1AA567Ecd/logo.png",
"symbol": "ALBT",
"name": "AllianceBlock",
"contractAddress": "0x9e037de681cafa6e661e6108ed9c2bd1aa567ecd"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x027dbcA046ca156De9622cD1e2D907d375e53aa7/logo.png",
"symbol": "AMPL",
"name": "Ampleforth",
"contractAddress": "0x027dbca046ca156de9622cd1e2d907d375e53aa7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x6C6f910A79639dcC94b4feEF59Ff507c2E843929/logo.png",
"symbol": "AAVAXB",
"name": "Ankr Avalanche Reward Earning Bond",
"contractAddress": "0x6c6f910a79639dcc94b4feef59ff507c2e843929"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x938FE3788222A74924E062120E7BFac829c719Fb/logo.png",
"symbol": "APEIN",
"name": "Ape In",
"contractAddress": "0x938fe3788222a74924e062120e7bfac829c719fb"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/avalaunch-app/xava-protocol/master/logo.png",
"symbol": "XAVA",
"name": "Avalaunch",
"contractAddress": "0xd1c3f94de7e5b45fa4edbba472491a9f4b166fc4"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x9F87C6c30F4b23C7B51Aa7465A9e0A836514700D/logo.png",
"symbol": "TERRA",
"name": "Avaterra",
"contractAddress": "0x9f87c6c30f4b23c7b51aa7465a9e0a836514700d"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x78ea17559B3D2CF85a7F9C2C704eda119Db5E6dE/logo.png",
"symbol": "AVE",
"name": "Avaware",
"contractAddress": "0x78ea17559b3d2cf85a7f9c2c704eda119db5e6de"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x783C08b5F26E3daf8C4681F3bf49844e425b6393/logo.png",
"symbol": "AUSD",
"name": "Avaware USD",
"contractAddress": "0x783c08b5f26e3daf8c4681f3bf49844e425b6393"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x397bBd6A0E41bdF4C3F971731E180Db8Ad06eBc1/logo.png",
"symbol": "AVXT",
"name": "Avaxtars",
"contractAddress": "0x397bbd6a0e41bdf4c3f971731e180db8ad06ebc1"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x8729438EB15e2C8B576fCc6AeCdA6A148776C0F5/logo.png",
"symbol": "QI",
"name": "BENQI",
"contractAddress": "0x8729438eb15e2c8b576fcc6aecda6a148776c0f5"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/BAT/logo.png",
"symbol": "BAT",
"name": "Basic Attention Token",
"contractAddress": "0x98443b96ea4b0858fdf3219cd13e98c7a4690588"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0xd6070ae98b8069de6B494332d1A1a81B6179D960/logo.png",
"symbol": "BIFI",
"name": "Beefy.Finance",
"contractAddress": "0xd6070ae98b8069de6b494332d1a1a81b6179d960"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x564A341Df6C126f90cf3ECB92120FD7190ACb401/logo.png",
"symbol": "TRYB",
"name": "BiLira",
"contractAddress": "0x564a341df6c126f90cf3ecb92120fd7190acb401"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/BUSD/logo.png",
"symbol": "BUSD",
"name": "Binance USD",
"contractAddress": "0x19860ccb0a68fd4213ab9d8266f7bbf05a8dde98"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xbca7f1998Dc9FFB70b086543a808960a460aBcA7/logo.png",
"symbol": "KITTY",
"name": "Binary Cat",
"contractAddress": "0xbca7f1998dc9ffb70b086543a808960a460abca7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x0f34919404a290e71fc6A510cB4a6aCb8D764b24/logo.png",
"symbol": "BLZZ",
"name": "Blizz Finance",
"contractAddress": "0x0f34919404a290e71fc6a510cb4a6acb8d764b24"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/BooFinance/assets/main/logo.png",
"symbol": "BOOFI",
"name": "Boo Finance",
"contractAddress": "0xb00f1ad977a949a3ccc389ca1d1282a2946963b0"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xF2CfC11093edB5A2DC7F49e70A3a3a9cd4F4FeE4/logo.png",
"symbol": "BRIG",
"name": "Brig Finance",
"contractAddress": "0xf2cfc11093edb5a2dc7f49e70a3a3a9cd4f4fee4"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0x8D88e48465F30Acfb8daC0b3E35c9D6D7d36abaf/logo.png",
"symbol": "CNR",
"name": "Canary",
"contractAddress": "0x8d88e48465f30acfb8dac0b3e35c9d6d7d36abaf"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/LINK/logo.png",
"symbol": "LINK",
"name": "Chainlink",
"contractAddress": "0x5947bb275c521040051d82396192181b413227a3"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x7761E2338B35bCEB6BdA6ce477EF012bde7aE611/logo.png",
"symbol": "EGG",
"name": "Chikn Egg",
"contractAddress": "0x7761e2338b35bceb6bda6ce477ef012bde7ae611"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xec3492a2508DDf4FDc0cD76F31f340b30d1793e6/logo.png",
"symbol": "CLY",
"name": "Colony",
"contractAddress": "0xec3492a2508ddf4fdc0cd76f31f340b30d1793e6"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/COMP/logo.png",
"symbol": "COMP",
"name": "Compound",
"contractAddress": "0xc3048e19e76cb9a3aa9d77d8c03c29fc906e2437"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xA32608e873F9DdEF944B24798db69d80Bbb4d1ed/logo.png",
"symbol": "CRA",
"name": "Crabada",
"contractAddress": "0xa32608e873f9ddef944b24798db69d80bbb4d1ed"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/CycleProtocol/assets/master/cycle-logo-round-small.png",
"symbol": "CYCLE",
"name": "Cycle Token",
"contractAddress": "0x81440c939f2c1e34fc7048e518a637205a632a74"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/DAI/logo.png",
"symbol": "DAI",
"name": "Dai",
"contractAddress": "0xd586e7f844cea2f87f50152665bcbc2c279d8d70"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0x961C8c0B1aaD0c0b10a51FeF6a867E3091BCef17/logo.png",
"symbol": "DYP",
"name": "DeFi Yield Protocol",
"contractAddress": "0x961c8c0b1aad0c0b10a51fef6a867e3091bcef17"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x47EB6F7525C1aA999FBC9ee92715F5231eB1241D/logo.png",
"symbol": "MELT",
"name": "Defrost Finance",
"contractAddress": "0x47eb6f7525c1aa999fbc9ee92715f5231eb1241d"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x1DB749847C4abB991d8B6032102383e6BfD9B1c7/logo.png",
"symbol": "DON",
"name": "Dogeon",
"contractAddress": "0x1db749847c4abb991d8b6032102383e6bfd9b1c7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x100Cc3a819Dd3e8573fD2E46D1E66ee866068f30/logo.png",
"symbol": "DCAU",
"name": "Dragon Crypto Aurum",
"contractAddress": "0x100cc3a819dd3e8573fd2e46d1e66ee866068f30"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xAcD7B3D9c10e97d0efA418903C0c7669E702E4C0/logo.png",
"symbol": "ELE",
"name": "Eleven Finance",
"contractAddress": "0xacd7b3d9c10e97d0efa418903c0c7669e702e4c0"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xE1C110E1B1b4A1deD0cAf3E42BfBdbB7b5d7cE1C/logo.png",
"symbol": "ELK",
"name": "Elk Finance",
"contractAddress": "0xe1c110e1b1b4a1ded0caf3e42bfbdbb7b5d7ce1c"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xf6d46849DB378AE01D93732585BEc2C4480D1fD5/logo.png",
"symbol": "FORT",
"name": "Fortress",
"contractAddress": "0xf6d46849db378ae01d93732585bec2c4480d1fd5"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xD24C2Ad096400B6FBcd2ad8B24E7acBc21A1da64/logo.png",
"symbol": "FRAX",
"name": "Frax",
"contractAddress": "0xd24c2ad096400b6fbcd2ad8b24e7acbc21a1da64"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x214DB107654fF987AD859F34125307783fC8e387/logo.png",
"symbol": "FXS",
"name": "Frax Share",
"contractAddress": "0x214db107654ff987ad859f34125307783fc8e387"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x595c8481c48894771CE8FaDE54ac6Bf59093F9E8/logo.png",
"symbol": "GAJ",
"name": "Gaj Finance",
"contractAddress": "0x595c8481c48894771ce8fade54ac6bf59093f9e8"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x90842eb834cFD2A1DB0b1512B254a18E4D396215/logo.png",
"symbol": "GB",
"name": "Good Bridging",
"contractAddress": "0x90842eb834cfd2a1db0b1512b254a18e4d396215"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x321E7092a180BB43555132ec53AaA65a5bF84251/logo.png",
"symbol": "GOHM",
"name": "Governance OHM",
"contractAddress": "0x321e7092a180bb43555132ec53aaa65a5bf84251"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xB15f02F9Da8CD1f99E9dd375F21dc96D25ddd82C/logo.png",
"symbol": "HERMES",
"name": "HERMES",
"contractAddress": "0xb15f02f9da8cd1f99e9dd375f21dc96d25ddd82c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xfa4B6db72A650601E7Bd50a0A9f537c9E98311B2/logo.png",
"symbol": "HSHARES",
"name": "Hermes Shares",
"contractAddress": "0xfa4b6db72a650601e7bd50a0a9f537c9e98311b2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xC7f4debC8072e23fe9259A5C0398326d8EfB7f5c/logo.png",
"symbol": "HEC",
"name": "HeroesChained",
"contractAddress": "0xc7f4debc8072e23fe9259a5c0398326d8efb7f5c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xce0052745A182fff07aCB9dd834041E5F29319b2/logo.png",
"symbol": "HIGGS",
"name": "Higgs",
"contractAddress": "0xce0052745a182fff07acb9dd834041e5f29319b2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x45C13620B55C35A5f539d26E88247011Eb10fDbd/logo.png",
"symbol": "HCT",
"name": "HurricaneSwap Token",
"contractAddress": "0x45c13620b55c35a5f539d26e88247011eb10fdbd"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xF891214fdcF9cDaa5fdC42369eE4F27F226AdaD6/logo.png",
"symbol": "IME",
"name": "Imperium Empires",
"contractAddress": "0xf891214fdcf9cdaa5fdc42369ee4f27f226adad6"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xeA6887e4a9CdA1B77E70129E5Fba830CdB5cdDef/logo.png",
"symbol": "IMX",
"name": "Impermax",
"contractAddress": "0xea6887e4a9cda1b77e70129e5fba830cdb5cddef"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x544c42fBB96B39B21DF61cf322b5EDC285EE7429/logo.png",
"symbol": "INSUR",
"name": "InsurAce",
"contractAddress": "0x544c42fbb96b39b21df61cf322b5edc285ee7429"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd/logo.png",
"symbol": "JOE",
"name": "JOE",
"contractAddress": "0x6e84a6216ea6dacc71ee8e6b0a5b7322eebc0fdd"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xb27c8941a7Df8958A1778c0259f76D1F8B711C35/logo.png",
"symbol": "KLO",
"name": "Kalao",
"contractAddress": "0xb27c8941a7df8958a1778c0259f76d1f8b711c35"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xf32398dae246C5f672B52A54e9B413dFFcAe1A44/logo.png",
"symbol": "KACY",
"name": "Kassandra",
"contractAddress": "0xf32398dae246c5f672b52a54e9b413dffcae1a44"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x5684a087C739A2e845F4AaAaBf4FBd261edc2bE8/logo.png",
"symbol": "LF",
"name": "Life DAO",
"contractAddress": "0x5684a087c739a2e845f4aaaabf4fbd261edc2be8"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0x4C9B4E1AC6F24CdE3660D5E4Ef1eBF77C710C084/logo.png",
"symbol": "LYD",
"name": "Lydia Finance",
"contractAddress": "0x4c9b4e1ac6f24cde3660d5e4ef1ebf77c710c084"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x130966628846BFd36ff31a822705796e8cb8C18D/logo.png",
"symbol": "MIM",
"name": "Magic Internet Money",
"contractAddress": "0x130966628846bfd36ff31a822705796e8cb8c18d"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/MKR/logo.png",
"symbol": "MKR",
"name": "Maker",
"contractAddress": "0x88128fd4b259552a9a1d457f435a6527aab72d42"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x9Fda7cEeC4c18008096C2fE2B85F05dc300F94d0/logo.png",
"symbol": "MFI",
"name": "Marginswap",
"contractAddress": "0x9fda7ceec4c18008096c2fe2b85f05dc300f94d0"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x7C08413cbf02202a1c13643dB173f2694e0F73f0/logo.png",
"symbol": "MAXI",
"name": "Maximizer",
"contractAddress": "0x7c08413cbf02202a1c13643db173f2694e0f73f0"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x885d748C00A279B67A7749EC6b03301700dd0455/logo.png",
"symbol": "MAXI",
"name": "Maximus",
"contractAddress": "0x885d748c00a279b67a7749ec6b03301700dd0455"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x921f99719Eb6C01b4B8f0BA7973A7C24891e740A/logo.png",
"symbol": "MAGE",
"name": "MetaBrands",
"contractAddress": "0x921f99719eb6c01b4b8f0ba7973a7c24891e740a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xd9D90f882CDdD6063959A9d837B05Cb748718A05/logo.png",
"symbol": "MORE",
"name": "More Token",
"contractAddress": "0xd9d90f882cddd6063959a9d837b05cb748718a05"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x9E3Ca00f2d4A9E5d4f0add0900de5f15050812cF/logo.png",
"symbol": "NFTD",
"name": "NFTrade",
"contractAddress": "0x9e3ca00f2d4a9e5d4f0add0900de5f15050812cf"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x937E077aBaEA52d3abf879c9b9d3f2eBd15BAA21/logo.png",
"symbol": "OH",
"name": "Oh! Finance",
"contractAddress": "0x937e077abaea52d3abf879c9b9d3f2ebd15baa21"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x0ebd9537A25f56713E34c45b38F421A1e7191469/logo.png",
"symbol": "OOE",
"name": "OpenOcean",
"contractAddress": "0x0ebd9537a25f56713e34c45b38f421a1e7191469"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x346A59146b9b4a77100D369a3d18E8007A9F46a6/logo.png",
"symbol": "AVAI",
"name": "Orca AVAI",
"contractAddress": "0x346a59146b9b4a77100d369a3d18e8007a9f46a6"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x8B1d98A91F853218ddbb066F20b8c63E782e2430/logo.png",
"symbol": "ORCA",
"name": "Orca DAO",
"contractAddress": "0x8b1d98a91f853218ddbb066f20b8c63e782e2430"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/assets/0x60781C2586D68229fde47564546784ab3fACA982/logo.png",
"symbol": "PNG",
"name": "Pangolin",
"contractAddress": "0x60781c2586d68229fde47564546784ab3faca982"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x69A61f38Df59CBB51962E69C54D39184E21C27Ec/logo.png",
"symbol": "PARTY",
"name": "PartySwap",
"contractAddress": "0x69a61f38df59cbb51962e69c54d39184e21c27ec"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xfB98B335551a418cD0737375a2ea0ded62Ea213b/logo.png",
"symbol": "PENDLE",
"name": "Pendle",
"contractAddress": "0xfb98b335551a418cd0737375a2ea0ded62ea213b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/Penguin-Finance/png-files/main/pefiv2_250x250.png",
"symbol": "PEFI",
"name": "Penguin Finance",
"contractAddress": "0xe896cdeaac9615145c0ca09c8cd5c25bced6384c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x22d4002028f537599bE9f666d1c4Fa138522f9c8/logo.png",
"symbol": "PTP",
"name": "Platypus Finance",
"contractAddress": "0x22d4002028f537599be9f666d1c4fa138522f9c8"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x7b2B702706D9b361dfE3f00bD138C0CFDA7FB2Cf/logo.png",
"symbol": "PLN",
"name": "Pollen",
"contractAddress": "0x7b2b702706d9b361dfe3f00bd138c0cfda7fb2cf"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x97Cd1CFE2ed5712660bb6c14053C0EcB031Bff7d/logo.png",
"symbol": "RAI",
"name": "Rai Reflex Index",
"contractAddress": "0x97cd1cfe2ed5712660bb6c14053c0ecb031bff7d"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xb2a85C5ECea99187A977aC34303b80AcbDdFa208/logo.png",
"symbol": "ROCO",
"name": "Roco Finance",
"contractAddress": "0xb2a85c5ecea99187a977ac34303b80acbddfa208"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0xa5E59761eBD4436fa4d20E1A27cBa29FB2471Fc6/logo.png",
"symbol": "SHERPA",
"name": "Sherpa",
"contractAddress": "0xa5e59761ebd4436fa4d20e1a27cba29fb2471fc6"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/Snowball-Finance/Assets/main/Uphill%20snowball.png",
"symbol": "SNOB",
"name": "Snowball",
"contractAddress": "0xc38f41a296a4493ff429f1238e030924a1542e50"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x7d1232B90D3F809A54eeaeeBC639C62dF8a8942f/logo.png",
"symbol": "SB",
"name": "Snowbank",
"contractAddress": "0x7d1232b90d3f809a54eeaeebc639c62df8a8942f"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x721C299E6BF7D6a430d9bEA3364Ea197314bcE09/logo.png",
"symbol": "MILK2",
"name": "Spaceswap MILK2",
"contractAddress": "0x721c299e6bf7d6a430d9bea3364ea197314bce09"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xC1d02E488a9Ce2481BFdcd797d5373Dd2E70a9C2/logo.png",
"symbol": "SHAKE",
"name": "Spaceswap SHAKE",
"contractAddress": "0xc1d02e488a9ce2481bfdcd797d5373dd2e70a9c2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xCE1bFFBD5374Dac86a2893119683F4911a2F7814/logo.png",
"symbol": "SPELL",
"name": "Spell Token",
"contractAddress": "0xce1bffbd5374dac86a2893119683f4911a2f7814"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xbD83010eB60F12112908774998F65761cf9f6f9a/logo.png",
"symbol": "BOO",
"name": "Spookyswap",
"contractAddress": "0xbd83010eb60f12112908774998f65761cf9f6f9a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x37d87e316CB4e35163881fDb6c6Bc0CdBa91dc0A/logo.png",
"symbol": "SET",
"name": "Stabilize Token",
"contractAddress": "0x37d87e316cb4e35163881fdb6c6bc0cdba91dc0a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xAafd2577Fb67366d3C89DB0d627C49D769ee2e5D/logo.png",
"symbol": "SUSD",
"name": "Stabilize USD",
"contractAddress": "0xaafd2577fb67366d3c89db0d627c49d769ee2e5d"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xF44Fb887334Fa17d2c5c0F970B5D320ab53eD557/logo.png",
"symbol": "START",
"name": "Starter.xyz",
"contractAddress": "0xf44fb887334fa17d2c5c0f970b5d320ab53ed557"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x6AFD5A1ea4b793CC1526d6Dc7e99A608b356eF7b/logo.png",
"symbol": "STORM",
"name": "Storm Token",
"contractAddress": "0x6afd5a1ea4b793cc1526d6dc7e99a608b356ef7b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/SUSHI/logo.png",
"symbol": "SUSHI",
"name": "Sushi",
"contractAddress": "0x37b608519f91f70f2eeb0e5ed9af4061722e4f76"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x1f1E7c893855525b303f99bDF5c3c05Be09ca251/logo.png",
"symbol": "SYN",
"name": "Synapse",
"contractAddress": "0x1f1e7c893855525b303f99bdf5c3c05be09ca251"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/SNX/logo.png",
"symbol": "SNX",
"name": "Synthetix Network Token",
"contractAddress": "0xbec243c995409e6520d7c41e404da5deba4b209b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x8aE8be25C23833e0A01Aa200403e826F611f9CD2/logo.png",
"symbol": "CRAFT",
"name": "TaleCraft",
"contractAddress": "0x8ae8be25c23833e0a01aa200403e826f611f9cd2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x094bd7B2D99711A1486FB94d4395801C6d0fdDcC/logo.png",
"symbol": "TEDDY",
"name": "Teddy",
"contractAddress": "0x094bd7b2d99711a1486fb94d4395801c6d0fddcc"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x4fbf0429599460D327BD5F55625E30E4fC066095/logo.png",
"symbol": "TSD",
"name": "Teddy Dollar",
"contractAddress": "0x4fbf0429599460d327bd5f55625e30e4fc066095"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/USDT/logo.png",
"symbol": "USDT",
"name": "Tether",
"contractAddress": "0xc7198437980c041c805a1edcba50c1ce5db95118"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/GRT/logo.png",
"symbol": "GRT",
"name": "The Graph",
"contractAddress": "0x8a0cac13c7da965a312f08ea4229c37869e85cb9"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xfcc6CE74f4cd7eDEF0C5429bB99d38A3608043a5/logo.png",
"symbol": "FIRE",
"name": "The Phoenix",
"contractAddress": "0xfcc6ce74f4cd7edef0c5429bb99d38a3608043a5"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x8F47416CaE600bccF9530E9F3aeaA06bdD1Caa79/logo.png",
"symbol": "THOR",
"name": "Thor",
"contractAddress": "0x8f47416cae600bccf9530e9f3aeaa06bdd1caa79"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xc12e249FaBe1c5Eb7C558E5F50D187687a244E31/logo.png",
"symbol": "BLUE",
"name": "Tip.Blue",
"contractAddress": "0xc12e249fabe1c5eb7c558e5f50d187687a244e31"
},
{
"decimalCount": 8,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xae9d2385Ff2E2951Dd4fA061e74c4d3deDD24347/logo.png",
"symbol": "TOK",
"name": "Tokenplace",
"contractAddress": "0xae9d2385ff2e2951dd4fa061e74c4d3dedd24347"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xb84527D59b6Ecb96F433029ECc890D4492C5dCe1/logo.png",
"symbol": "TOMB",
"name": "Tomb",
"contractAddress": "0xb84527d59b6ecb96f433029ecc890d4492c5dce1"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xf693248F96Fe03422FEa95aC0aFbBBc4a8FdD172/logo.png",
"symbol": "TUS",
"name": "Treasure Under Sea",
"contractAddress": "0xf693248f96fe03422fea95ac0afbbbc4a8fdd172"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x1C20E891Bab6b1727d14Da358FAe2984Ed9B59EB/logo.png",
"symbol": "TUSD",
"name": "TrueUSD",
"contractAddress": "0x1c20e891bab6b1727d14da358fae2984ed9b59eb"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xc7B5D72C836e718cDA8888eaf03707fAef675079/logo.png",
"symbol": "SWAP",
"name": "Trustswap",
"contractAddress": "0xc7b5d72c836e718cda8888eaf03707faef675079"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x21c5402C3B7d40C89Cc472C9dF5dD7E51BbAb1b1/logo.png",
"symbol": "TUNDRA",
"name": "Tundra Token",
"contractAddress": "0x21c5402c3b7d40c89cc472c9df5dd7e51bbab1b1"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/UMA/logo.png",
"symbol": "UMA",
"name": "UMA",
"contractAddress": "0x3bd2b1c7ed8d396dbb98ded3aebb41350a5b2339"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E/logo.png",
"symbol": "USDC",
"name": "USD Coin",
"contractAddress": "0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/USDC/logo.png",
"symbol": "USDC",
"name": "USD Coin Avalanche Bridged (USDC.e)",
"contractAddress": "0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/UNI/logo.png",
"symbol": "UNI",
"name": "Uniswap",
"contractAddress": "0x8ebaf22b6f053dffeaf46f4dd9efa95d89ba8580"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/VersoOfficial/pr/master/icon_blue.png",
"symbol": "VSO",
"name": "Verso",
"contractAddress": "0x846d50248baf8b7ceaa9d9b53bfd12d7d7fbb25a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/WETH/logo.png",
"symbol": "WETH",
"name": "WETH",
"contractAddress": "0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/WOO/logo.png",
"symbol": "WOO",
"name": "WOO Network",
"contractAddress": "0xabc9547b534519ff73921b1fba6e672b5f58d083"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xA384Bc7Cdc0A93e686da9E7B8C0807cD040F4E0b/logo.png",
"symbol": "WOW",
"name": "WOWswap",
"contractAddress": "0xa384bc7cdc0a93e686da9e7b8c0807cd040f4e0b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xB1466d4cf0DCfC0bCdDcf3500F473cdACb88b56D/logo.png",
"symbol": "WET",
"name": "Weble Ecosystem Token",
"contractAddress": "0xb1466d4cf0dcfc0bcddcf3500f473cdacb88b56d"
},
{
"decimalCount": 9,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xb54f16fB19478766A268F172C9480f8da1a7c9C3/logo.png",
"symbol": "TIME",
"name": "Wonderland",
"contractAddress": "0xb54f16fb19478766a268f172c9480f8da1a7c9c3"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7/logo.png",
"symbol": "WAVAX",
"name": "Wrapped AVAX",
"contractAddress": "0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/bridge-tokens/main/avalanche-tokens/0x264c1383EA520f73dd837F915ef3a732e204a493/logo.png",
"symbol": "WBNB",
"name": "Wrapped BNB",
"contractAddress": "0x264c1383ea520f73dd837f915ef3a732e204a493"
},
{
"decimalCount": 8,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/WBTC/logo.png",
"symbol": "WBTC",
"name": "Wrapped Bitcoin",
"contractAddress": "0x50b7545627a5162f82a992c33b87adc75187b218"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x01C2086faCFD7aA38f69A6Bd8C91BEF3BB5adFCa/logo.png",
"symbol": "YAY",
"name": "YAY Games",
"contractAddress": "0x01c2086facfd7aa38f69a6bd8c91bef3bb5adfca"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/YetiSwap/yetiswap.app/master/src/assets/image/YTSCoin.png",
"symbol": "YETI",
"name": "Yearn Ecosystem Token Index",
"contractAddress": "0x488f73cddda1de3664775ffd91623637383d6404"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0x59414b3089ce2AF0010e7523Dea7E2b35d776ec7/logo.png",
"symbol": "YAK",
"name": "Yield Yak",
"contractAddress": "0x59414b3089ce2af0010e7523dea7e2b35d776ec7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xab592d197ACc575D16C3346f4EB70C703F308D1E/logo.png",
"symbol": "FEED",
"name": "chikn feed",
"contractAddress": "0xab592d197acc575d16c3346f4eb70c703f308d1e"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/pangolindex/tokens/main/assets/0xBD100d061E120b2c67A24453CF6368E63f1Be056/logo.png",
"symbol": "IDYP",
"name": "iDeFiYieldProtocol",
"contractAddress": "0xbd100d061e120b2c67a24453cf6368e63f1be056"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/tokens/YFI/logo.png",
"symbol": "YFI",
"name": "yearn.finance",
"contractAddress": "0x9eaac1b23d935365bd7b542fe22ceee2922f52dc"
}
]

View file

@ -0,0 +1,16 @@
[
{
"decimalCount": 6,
"customIconUrl": "https://i.imgur.com\/LPZDXoT.png",
"symbol": "FUJISTABLE",
"name": "The Fuji stablecoin",
"contractAddress": "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22"
},
{
"decimalCount": 9,
"customIconUrl": "https://i.imgur.com\/jNTqyQA.png",
"symbol": "FUJIMOON",
"name": "To the Moon",
"contractAddress": "0x97132C109c6816525F7f338DCb7435E1412A7668"
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,9 @@
[
{
"symbol" : "HEM",
"decimalCount" : 8,
"contractAddress" : "HEM-452",
"blockchain" : {
"key" : "binance",
"curve" : "secp256k1",
"testnet" : false
},
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/binance/assets/MATIC-84A/logo.png",
"name" : "Hemster - 452"
"decimalCount": 8,
"customIconUrl": "https:\/\/assets.trustwalletapp.com\/blockchains\/binance\/assets\/MATIC-84A\/logo.png",
"symbol": "HEM",
"name": "Hemster - 452",
"contractAddress": "HEM-452"
}
]
]

File diff suppressed because it is too large Load diff

View file

@ -1,99 +1,65 @@
[
{
"decimalCount" : 18,
"symbol" : "BUSD",
"name" : "Binance-Peg BUSD Token",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56/logo.png",
"contractAddress" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "ETH",
"name" : "Ethereum Token",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x2170Ed0880ac9A755fd29B2688956BD959F933F8/logo.png",
"contractAddress" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "BTCB",
"name" : "Binance-Peg BTCB Token",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c/logo.png",
"contractAddress" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "USDT",
"name" : "Binance-Peg Tether USD",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
"contractAddress" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "USDT",
"name" : "Tether USD",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
"contractAddress" : "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "XRP",
"name" : "Binance-Peg XRP",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE/logo.png",
"contractAddress" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "DAI",
"name" : "Binance-Peg Dai Token",
"customIconUrl" : "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
"contractAddress" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "USDC",
"name" : "Binance-Peg USDC Token",
"customIconUrl" : "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"contractAddress" : "0x64544969ed7ebf5f083679233325356ebe738930",
"blockchain" : {
"key" : "bsc",
"curve" : "secp256k1",
"testnet" : true
}
}
]
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c/logo.png",
"symbol": "BTCB",
"name": "Binance-Peg BTCB Token",
"contractAddress": "0x6ce8da28e2f864420840cf74474eff5fd80e65b8"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56/logo.png",
"symbol": "BUSD",
"name": "Binance-Peg BUSD Token",
"contractAddress": "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
"symbol": "DAI",
"name": "Binance-Peg Dai Token",
"contractAddress": "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
"symbol": "USDT",
"name": "Binance-Peg Tether USD",
"contractAddress": "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"symbol": "USDC",
"name": "Binance-Peg USDC Token",
"contractAddress": "0x64544969ed7ebf5f083679233325356ebe738930"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBE/logo.png",
"symbol": "XRP",
"name": "Binance-Peg XRP",
"contractAddress": "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3/logo.png",
"symbol": "DAI",
"name": "Dai Token",
"contractAddress": "0x8a9424745056eb399fd19a0ec26a14316684e274"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x2170Ed0880ac9A755fd29B2688956BD959F933F8/logo.png",
"symbol": "ETH",
"name": "Ethereum Token",
"contractAddress": "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378"
},
{
"decimalCount": 18,
"customIconUrl": "https://assets.trustwalletapp.com/blockchains/smartchain/assets/0x55d398326f99059fF775485246999027B3197955/logo.png",
"symbol": "USDT",
"name": "Tether USD",
"contractAddress": "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684"
}
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
[
{
"decimalCount": 18,
"symbol": "WEENUS",
"name": "Weenus",
"contractAddress": "0xaFF4481D10270F50f203E0763e2597776068CBc5"
},
{
"decimalCount": 18,
"symbol": "XEENUS",
"name": "Xeenus",
"contractAddress": "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c"
},
{
"decimalCount": 8,
"symbol": "YEENUS",
"name": "Yeenus",
"contractAddress": "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7"
},
{
"decimalCount": 0,
"symbol": "ZEENUS",
"name": "Zeenus",
"contractAddress": "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e"
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,46 +0,0 @@
[
{
"decimalCount" : 18,
"symbol" : "WEENUS",
"name" : "Weenus",
"contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5",
"blockchain" : {
"key" : "ethereum",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 18,
"symbol" : "XEENUS",
"name" : "Xeenus",
"contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
"blockchain" : {
"key" : "ethereum",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 8,
"symbol" : "YEENUS",
"name" : "Yeenus",
"contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
"blockchain" : {
"key" : "ethereum",
"curve" : "secp256k1",
"testnet" : true
}
},
{
"decimalCount" : 0,
"symbol" : "ZEENUS",
"name" : "Zeenus",
"contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
"blockchain" : {
"key" : "ethereum",
"curve" : "secp256k1",
"testnet" : true
}
}
]

View file

@ -0,0 +1,868 @@
[
{
"decimalCount": 8,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x71B821aa52a49F32EEd535fCA6Eb5aa130085978/logo.png",
"symbol": "0XBTC",
"name": "0xBitcoin",
"contractAddress": "0x71b821aa52a49f32eed535fca6eb5aa130085978"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x111111111117dc0aa78b770fa6a738034120c302.png",
"symbol": "1INCH",
"name": "1inch",
"contractAddress": "0x9c2c5fd7b07e95ee044ddeba0e97a665f142394f"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x48cBc913dE09317dF2365e6827Df50dA083701D5/logo.png",
"symbol": "FOUR",
"name": "4thpillar technologies",
"contractAddress": "0x48cbc913de09317df2365e6827df50da083701d5"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x034b2090b579228482520c589dbD397c53Fc51cC.png",
"symbol": "VISION",
"name": "APY.vision",
"contractAddress": "0x034b2090b579228482520c589dbd397c53fc51cc"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/aave.svg",
"symbol": "AAVE",
"name": "Aave",
"contractAddress": "0xd6df932a45c0f255f85145f286ea0b292b21c90b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0.png",
"symbol": "AMWMATIC",
"name": "Aave Polygon WMATIC",
"contractAddress": "0x8df3aad3a84da6b69a4da8aec3ea40d9091b2ac4"
},
{
"decimalCount": 18,
"customIconUrl": "https://polygonscan.com/token/images/aavegotchighst_32.png",
"symbol": "GHST",
"name": "Aavegotchi",
"contractAddress": "0x385eeac5cb85a38a9a07a70c73e0a3271cfb54a7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xc3FdbadC7c795EF1D6Ba111e06fF8F16A20Ea539/logo.png",
"symbol": "ADDY",
"name": "Adamant",
"contractAddress": "0xc3fdbadc7c795ef1d6ba111e06ff8f16a20ea539"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xcae72a7a0fd9046cf6b165ca54c9e3a3872109e0.png",
"symbol": "$ANRX",
"name": "AnRKey X",
"contractAddress": "0x554f074d9ccda8f483d1812d4874cbebd682644e"
},
{
"decimalCount": 18,
"symbol": "ANY",
"name": "Anyswap",
"contractAddress": "0x6ab6d61428fde76768d7b45d8bfeec19c6ef91a8"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x5d47bAbA0d66083C52009271faF3F50DCc01023C/logo.png",
"symbol": "BANANA",
"name": "ApeSwap Finance",
"contractAddress": "0x5d47baba0d66083c52009271faf3f50dcc01023c"
},
{
"decimalCount": 0,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xdacd69347de42babfaecd09dc88958378780fb62.png",
"symbol": "ATRI",
"name": "Atari",
"contractAddress": "0xb140665dde25c644c6b418e417c930de8a8a6ac9"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x76e63a3E7Ba1e2E61D3DA86a87479f983dE89a7E/logo.png",
"symbol": "OMEN",
"name": "Augury Finance",
"contractAddress": "0x76e63a3e7ba1e2e61d3da86a87479f983de89a7e"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x7f426F6Dc648e50464a0392E60E1BB465a67E9cf.png",
"symbol": "AUTO",
"name": "Auto",
"contractAddress": "0x7f426f6dc648e50464a0392e60e1bb465a67e9cf"
},
{
"decimalCount": 4,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xc813ea5e3b48bebeedb796ab42a30c5599b01740.png",
"symbol": "NIOX",
"name": "Autonio",
"contractAddress": "0xad684e79ce4b6d464f2ff7c3fd51646892e24b96"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x340d2bde5eb28c1eed91b2f790723e3b160613b7.png",
"symbol": "VEE",
"name": "BLOCKv",
"contractAddress": "0xf1c1a3c2481a3a8a3f173a9ab5ade275292a6fa3"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xb683d83a532e2cb7dfa5275eed3698436371cc9f.png",
"symbol": "BTU",
"name": "BTU Protocol",
"contractAddress": "0xfdc26cda2d2440d0e83cd1dee8e8be48405806dc"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/bal.svg",
"symbol": "BAL",
"name": "Balancer",
"contractAddress": "0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x2d94AA3e47d9D5024503Ca8491fcE9A2fB4DA198.png",
"symbol": "BANK",
"name": "Bankless DAO",
"contractAddress": "0xdb7cb471dd0b49b29cab4a1c14d070f27216a0ab"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x0d8775f648430679a709e98d2b0cb6250d2887ef.png",
"symbol": "BAT",
"name": "Basic Attention Token",
"contractAddress": "0x3cef98bb43d732e2f285ee605a8158cde967d219"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xFbdd194376de19a88118e84E279b977f165d01b8.png",
"symbol": "BIFI",
"name": "Beefy.Finance",
"contractAddress": "0xfbdd194376de19a88118e84e279b977f165d01b8"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/busd.svg",
"symbol": "BUSD",
"name": "Binance USD",
"contractAddress": "0xdab529f40e671a1d4bf91361c21bf9f0c9712ab7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x229b1b6C23ff8953D663C4cBB519717e323a0a84/logo.png",
"symbol": "BLOK",
"name": "Bloktopia",
"contractAddress": "0x229b1b6c23ff8953d663c4cbb519717e323a0a84"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x491604c0fdf08347dd1fa4ee062a822a5dd06b5d.png",
"symbol": "CTSI",
"name": "Cartesi",
"contractAddress": "0x2727ab1c2d22170abc9b595177b2d5c6e1ab7b7b"
},
{
"decimalCount": 4,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xaaaebe6fe48e54f431b0c390cfaf0b017d09d42d.png",
"symbol": "CEL",
"name": "Celsius Network",
"contractAddress": "0xd85d1e945766fea5eda9103f918bd915fbca63e6"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/link.svg",
"symbol": "LINK",
"name": "Chainlink",
"contractAddress": "0x53e0bca35ec356bd5dddfebbd1fc0fd03fabad39"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xE33dC9d2D7D64c1e23467aC7eD7F8eF56d0a8d66/logo.png",
"symbol": "CHERRY",
"name": "CherryPick",
"contractAddress": "0xe33dc9d2d7d64c1e23467ac7ed7f8ef56d0a8d66"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x9b31bb425D8263fA1b8B9d090b83CF0C31665355.png",
"symbol": "CPD",
"name": "CoinsPaid",
"contractAddress": "0x1ce4a2c355f0dcc24e32a9af19f1836d6f4f98ae"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xc00e94cb662c3520282e6f5717214004a7f26888.png",
"symbol": "COMP",
"name": "Compound",
"contractAddress": "0x8505b9d2254a7ae468c0e9dd10ccea3a837aef5c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x172370d5Cd63279eFa6d502DAB29171933a610AF/logo.png",
"symbol": "CRV",
"name": "Curve DAO Token",
"contractAddress": "0x172370d5cd63279efa6d502dab29171933a610af"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x63b4f3e3fa4e438698ce330e365e831f7ccd1ef4.png",
"symbol": "CFI",
"name": "CyberFi",
"contractAddress": "0xecf8f2fa183b1c4d2a269bf98a54fce86c812d3e"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xfa6de2697d59e88ed7fc4dfe5a33dac43565ea41.png",
"symbol": "DEFI5",
"name": "DEFI Top 5 Index",
"contractAddress": "0x42435f467d33e5c4146a4e8893976ef12bbce762"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xDE12c7959E1a72bbe8a5f7A1dc8f8EeF9Ab011B3/logo.png",
"symbol": "DEI",
"name": "DEI Token",
"contractAddress": "0xde12c7959e1a72bbe8a5f7a1dc8f8eef9ab011b3"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xDE5ed76E7c05eC5e4572CfC88d1ACEA165109E44/logo.png",
"symbol": "DEUS",
"name": "DEUS Finance",
"contractAddress": "0xde5ed76e7c05ec5e4572cfc88d1acea165109e44"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x43dfc4159d86f3a37a5a4b3d4580b888ad7d4ddd.png",
"symbol": "DODO",
"name": "DODO",
"contractAddress": "0xe4bf2864ebec7b7fdf6eeca9bacae7cdfdaffe78"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/dai.svg",
"symbol": "DAI",
"name": "Dai",
"contractAddress": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b.png",
"symbol": "DPI",
"name": "DeFi Pulse Index",
"contractAddress": "0x85955046df4668e1dd369d2de9f3aeb98dd2a369"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xef938b6da8576a896f6E0321ef80996F4890f9c4/logo.png",
"symbol": "DG",
"name": "Decentral Games",
"contractAddress": "0xef938b6da8576a896f6e0321ef80996f4890f9c4"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x0f5d2fb29fb7d3cfee444a200298f468908cc942.png",
"symbol": "MANA",
"name": "Decentraland",
"contractAddress": "0xa1c57f48f0deb89f569dfbe6e2b7f46d33606fd4"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xC168E40227E4ebD8C1caE80F7a55a4F0e6D66C97/logo.png",
"symbol": "DFYN",
"name": "Dfyn Network",
"contractAddress": "0xc168e40227e4ebd8c1cae80f7a55a4f0e6d66c97"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xB9638272aD6998708de56BBC0A290a1dE534a578.png",
"symbol": "IQ",
"name": "Everipedia",
"contractAddress": "0xb9638272ad6998708de56bbc0a290a1de534a578"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xee573a945b01b788b9287ce062a0cfc15be9fd86.png",
"symbol": "XED",
"name": "Exeedme",
"contractAddress": "0x2fe8733dcb25bfbba79292294347415417510067"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xfffffffff15abf397da76f1dcc1a1604f45126db.png",
"symbol": "FSW",
"name": "Falconswap",
"contractAddress": "0xad5dc12e88c6534eea8cfe2265851d9d4a1472ad"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x853d955aCEf822Db058eb8505911ED77F175b99e.png",
"symbol": "FRAX",
"name": "Frax",
"contractAddress": "0x45c32fa6df82ead1e2ef74d17b76547eddfaff89"
},
{
"decimalCount": 18,
"symbol": "FXS",
"name": "Frax Share",
"contractAddress": "0x1a3acf6d19267e2d3e7f898f42803e90c9219062"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xf8c3527cc04340b208c854e985240c02f7b7793f.png",
"symbol": "FRONT",
"name": "Frontier",
"contractAddress": "0xa3ed22eee92a3872709823a6970069e12a4540eb"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xcf32822ff397Ef82425153a9dcb726E5fF61DCA7/logo.png",
"symbol": "GMEE",
"name": "GAMEE",
"contractAddress": "0xcf32822ff397ef82425153a9dcb726e5ff61dca7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x7075cAB6bCCA06613e2d071bd918D1a0241379E2.png",
"symbol": "GFARM2",
"name": "Gains Farm",
"contractAddress": "0x7075cab6bcca06613e2d071bd918d1a0241379e2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x8d1566569d5b695d44a9a234540f68D393cDC40D.png",
"symbol": "GAME",
"name": "GameCredits",
"contractAddress": "0x8d1566569d5b695d44a9a234540f68d393cdc40d"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x3f6b3595ecF70735D3f48D69b09C4E4506DB3F47/logo.png",
"symbol": "GAMER",
"name": "GameStation",
"contractAddress": "0x3f6b3595ecf70735d3f48d69b09c4e4506db3f47"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xbea98c05eeae2f3bc8c3565db7551eb738c8ccab.png",
"symbol": "GYSR",
"name": "Geyser",
"contractAddress": "0xc48f61a288a08f1b80c2edd74652e1276b6a168c"
},
{
"decimalCount": 8,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x2b591e99afe9f32eaa6214f7b7629768c40eeb39.png",
"symbol": "HEX",
"name": "HEX",
"contractAddress": "0x23d29d30e35c5e8d321e1dc9a8a61bfd846d4c5c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x0954906da0bf32d5479e25f46056d22f08464cab.png",
"symbol": "INDEX",
"name": "Index Cooperative",
"contractAddress": "0xfbd8a3b908e764dbcd51e27992464b4432a1132b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xD86b5923F3AD7b585eD81B448170ae026c65ae9a/logo.png",
"symbol": "IRON",
"name": "Iron",
"contractAddress": "0xd86b5923f3ad7b585ed81b448170ae026c65ae9a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x6AE7Dfc73E0dDE2aa99ac063DcF7e8A63265108c.png",
"symbol": "JPYC",
"name": "JPY Coin",
"contractAddress": "0x6ae7dfc73e0dde2aa99ac063dcf7e8a63265108c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x8a9c67fee641579deba04928c4bc45f66e26343a.png",
"symbol": "JRT",
"name": "Jarvis Reward Token",
"contractAddress": "0x596ebe76e2db4470966ea395b0d063ac6197a8c5"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xdd974d5c2e2928dea5f71b9825b8b646686bd200.png",
"symbol": "KNC",
"name": "Kyber Network Crystal",
"contractAddress": "0x1c954e8fe737f99f68fa1ccda3e51ebdb291948c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x5f98805a4e8be255a32880fdec7f6728c6568ba0.png",
"symbol": "LUSD",
"name": "Liquity USD",
"contractAddress": "0x23001f892c0c82b79303edc9b9033cd190bb21c7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xa3Fa99A148fA48D14Ed51d610c367C61876997F1/logo.png",
"symbol": "MIMATIC",
"name": "MAI",
"contractAddress": "0xa3fa99a148fa48d14ed51d610c367c61876997f1"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x3593d125a4f7849a1b059e64f4517a86dd60c95d.png",
"symbol": "OM",
"name": "MANTRA DAO",
"contractAddress": "0xc3ec80343d2bae2f8e680fdadde7c17e71e114ea"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xb4d930279552397bba2ee473229f89ec245bc365.png",
"symbol": "MAHA",
"name": "MahaDAO",
"contractAddress": "0xedd6ca8a4202d4a36611e2fff109648c4863ae19"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x69af81e73a73b40adf4f3d4223cd9b1ece623074.png",
"symbol": "MASK",
"name": "Mask Network",
"contractAddress": "0x2b9e7ccdf0f4e5b24757c1e1a80e311e34cb10c7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xffc97d72e13e01096502cb8eb52dee56f74dad7b.png",
"symbol": "MAAAVE",
"name": "Matic Aave Interest Bearing AAVE",
"contractAddress": "0x823cd4264c1b951c9209ad0deaea9988fe8429bf"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x028171bca77440897b824ca71d1c56cac55b68a3.png",
"symbol": "MADAI",
"name": "Matic Aave Interest Bearing DAI",
"contractAddress": "0xe0b22e0037b130a9f56bbb537684e6fa18192341"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xa06bc25b5805d5f8d82847d191cb4af5a3e873e0.png",
"symbol": "MALINK",
"name": "Matic Aave Interest Bearing LINK",
"contractAddress": "0x98ea609569bd25119707451ef982b90e3eb719cd"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x9719d867A500Ef117cC201206B8ab51e794d3F82/logo.png",
"symbol": "MAUSDC",
"name": "Matic Aave Interest Bearing USDC",
"contractAddress": "0x9719d867a500ef117cc201206b8ab51e794d3f82"
},
{
"decimalCount": 6,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x3ed3b47dd13ec9a98b44e6204a523e766b225811.png",
"symbol": "MAUSDT",
"name": "Matic Aave Interest Bearing USDT",
"contractAddress": "0xdae5f1590db13e3b40423b5b5c5fbf175515910b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x030ba81f1c18d280636f32af80b9aad02cf0854e.png",
"symbol": "MAWETH",
"name": "Matic Aave Interest Bearing WETH",
"contractAddress": "0x20d3922b4a1a8560e1ac99fba4fade0c849e2142"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x5165d24277cd063f5ac44efd447b27025e888f37.png",
"symbol": "MAYFI",
"name": "Matic Aave Interest Bearing YFI",
"contractAddress": "0xe20f7d1f0ec39c4d5db01f53554f2ef54c71f613"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x101cc05f4a51c0319f570d5e146a8c625198e636.png",
"symbol": "MATUSD",
"name": "Matic Aave interest bearing TUSD",
"contractAddress": "0xf4b8888427b00d7caf21654408b7cba2ecf4ebd9"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x72e364F2ABdC788b7E918bc238B21f109Cd634D7.png",
"symbol": "MVI",
"name": "Metaverse Index",
"contractAddress": "0xfe712251173a2cd5f5be2b46bb528328ea3565e1"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x7778360F035C589fCE2f4eA5786CbD8B36e5396B.png",
"symbol": "OOE",
"name": "OpenOcean",
"contractAddress": "0x9d5565da88e596730522cbc5a918d2a89dbc16d9"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x1631244689EC1fEcbDD22fb5916E920dFC9b8D30/logo.png",
"symbol": "OVR",
"name": "Ovr",
"contractAddress": "0x1631244689ec1fecbdd22fb5916e920dfc9b8d30"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x9aF3b7DC29D3C4B1A5731408B6A9656fA7aC3b72/logo.png",
"symbol": "PUSD",
"name": "PUSD",
"contractAddress": "0x9af3b7dc29d3c4b1a5731408b6a9656fa7ac3b72"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x4C16f69302CcB511c5Fac682c7626B9eF0Dc126a.png",
"symbol": "POLYBUNNY",
"name": "Pancake Bunny Polygon",
"contractAddress": "0x4c16f69302ccb511c5fac682c7626b9ef0dc126a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xc1c93D475dc82Fe72DBC7074d55f5a734F8cEEAE/logo.png",
"symbol": "PGX",
"name": "Pegaxy Stone",
"contractAddress": "0xc1c93d475dc82fe72dbc7074d55f5a734f8ceeae"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x429881672b9ae42b8eba0e26cd9c73711b891ca5.png",
"symbol": "PICKLE",
"name": "Pickle Finance",
"contractAddress": "0x2b88ad57897a8b496595925f43048301c37615da"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xe3818504c1b32bf1557b16c238b2e01fd3149c17.png",
"symbol": "PLR",
"name": "Pillar",
"contractAddress": "0xa6b37fc85d870711c56fbcb8afe2f8db049ae774"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x054d64b73d3d8a21af3d764efd76bcaa774f3bb2.png",
"symbol": "PPAY",
"name": "Plasma Finance",
"contractAddress": "0x08158a6b5d4018340387d1a302f882e98a8bc5b4"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x72f020f8f3e8fd9382705723cd26380f8d0c66bb.png",
"symbol": "PLOT",
"name": "PlotX",
"contractAddress": "0xe82808eaa78339b06a691fd92e1be79671cad8d3"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xd8912c10681d8b21fd3742244f44658dba12264e.png",
"symbol": "PLU",
"name": "Pluton",
"contractAddress": "0x7dc0cb65ec6019330a6841e9c274f2ee57a6ca6c"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x3a3Df212b7AA91Aa0402B9035b098891d276572B/logo.png",
"symbol": "FISH",
"name": "Polycat Finance",
"contractAddress": "0x3a3df212b7aa91aa0402b9035b098891d276572b"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/matic.svg",
"symbol": "MATIC",
"name": "Polygon",
"contractAddress": "0x0000000000000000000000000000000000001010"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xC68e83a305b0FaD69E264A1769a0A070F190D2d6/logo.png",
"symbol": "ROLL",
"name": "Polyroll",
"contractAddress": "0xc68e83a305b0fad69e264a1769a0a070f190d2d6"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x05089C9EBFFa4F0AcA269e32056b1b36B37ED71b/logo.png",
"symbol": "KRILL",
"name": "Polywhale",
"contractAddress": "0x05089c9ebffa4f0aca269e32056b1b36b37ed71b"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x580A84C73811E1839F75d86d75d88cCa0c241fF4/logo.png",
"symbol": "QI",
"name": "Qi Dao",
"contractAddress": "0x580a84c73811e1839f75d86d75d88cca0c241ff4"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/quick.svg",
"symbol": "QUICK",
"name": "Quickswap",
"contractAddress": "0x831753dd7087cac61ab5644b308642cc1c33dc13"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x33D0568941C0C64ff7e0FB4fbA0B11BD37deEd9f.png",
"symbol": "RAMP",
"name": "RAMP",
"contractAddress": "0xaecebfcf604ad245eaf0d5bd68459c3a7a6399c2"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x03ab458634910aad20ef5f1c8ee96f1d6ac54919.png",
"symbol": "RAI",
"name": "Rai Reflex Index",
"contractAddress": "0x00e5646f60ac6fb446f621d146b6e1886f002905"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xC91c06DB0f7bFFba61e2A5645CC15686F0a8c828.png",
"symbol": "RAZOR",
"name": "Razor Network",
"contractAddress": "0xc91c06db0f7bffba61e2a5645cc15686f0a8c828"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x61299774020dA444Af134c82fa83E3810b309991.png",
"symbol": "RNDR",
"name": "Render Token",
"contractAddress": "0x61299774020da444af134c82fa83e3810b309991"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x16ECCfDbb4eE1A85A33f3A9B21175Cd7Ae753dB4/logo.png",
"symbol": "ROUTE",
"name": "Router Protocol",
"contractAddress": "0x16eccfdbb4ee1a85a33f3a9b21175cd7ae753db4"
},
{
"decimalCount": 2,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xdb25f211ab05b1c97d595516f45794528a807ad8.png",
"symbol": "EURS",
"name": "STASIS EURO",
"contractAddress": "0xe111178a87a3bff0c8d18decba5798827539ae99"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x840195888Db4D6A99ED9F73FcD3B225Bb3cB1A79.png",
"symbol": "SX",
"name": "SX Network",
"contractAddress": "0x840195888db4d6a99ed9f73fcd3b225bb3cb1a79"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xc770eefad204b5180df6a14ee197d99d808ee52d.png",
"symbol": "FOX",
"name": "ShapeShift FOX Token",
"contractAddress": "0x65a05db8322701724c197af82c9cae41195b0aa8"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0xccBe9B810d6574701d324fD6DbE0A1b68f9d5bf7/logo.png",
"symbol": "STACK",
"name": "Stacker Ventures",
"contractAddress": "0xccbe9b810d6574701d324fd6dbe0a1b68f9d5bf7"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x3a9A81d576d83FF21f26f325066054540720fC34/logo.png",
"symbol": "DATA",
"name": "Streamr",
"contractAddress": "0x3a9a81d576d83ff21f26f325066054540720fc34"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xe53ec727dbdeb9e2d5456c3be40cff031ab40a55.png",
"symbol": "SUPER",
"name": "SuperFarm",
"contractAddress": "0xa1428174f516f527fafdd146b883bb4428682737"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xea319e87cf06203dae107dd8e5672175e3ee976c.png",
"symbol": "SURF",
"name": "Surf.Finance",
"contractAddress": "0x1e42edbe5376e717c1b22904c59e406426e8173f"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/sushi.svg",
"symbol": "SUSHI",
"name": "Sushi",
"contractAddress": "0x0b3f868e0be5597d5db7feb59e1cadbb0fdda50a"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f.png",
"symbol": "SNX",
"name": "Synthetix Network Token",
"contractAddress": "0x50b728d8d964fd00c2d0aad81718b71311fef68a"
},
{
"decimalCount": 2,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/tel.svg",
"symbol": "TEL",
"name": "Telcoin",
"contractAddress": "0xdf7837de1f2fa4631d716cf2502f8b230f1dcc32"
},
{
"decimalCount": 6,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/usdt.svg",
"symbol": "USDT",
"name": "Tether",
"contractAddress": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xc944e90c64b2c07662a292be6244bdf05cda44a7.png",
"symbol": "GRT",
"name": "The Graph",
"contractAddress": "0x5fe2b58c013d7601147dcdd68c143a77499f5531"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x0000000000085d4780b73119b644ae5ecd22b376.png",
"symbol": "TUSD",
"name": "TrueUSD",
"contractAddress": "0x2e1ad108ff1d8c782fcbbb89aad783ac49586756"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xcc4304a31d09258b0029ea7fe63d032f52e44efe.png",
"symbol": "SWAP",
"name": "Trustswap",
"contractAddress": "0x3809dcdd5dde24b37abe64a5a339784c3323c44f"
},
{
"decimalCount": 6,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/usdc.svg",
"symbol": "USDC",
"name": "USD Coin",
"contractAddress": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x0202be363b8a4820f3f4de7faf5224ff05943ab1.png",
"symbol": "UFT",
"name": "UniLend Finance",
"contractAddress": "0x5b4cf2c120a9702225814e18543ee658c5f8631e"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xf2f9a7e93f845b3ce154efbeb64fb9346fcce509.png",
"symbol": "POWER",
"name": "UniPower",
"contractAddress": "0x00d5149cdf7cec8725bf50073c51c4fa58ecca12"
},
{
"decimalCount": 8,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x8400d94a5cb0fa0d041a3788e395285d61c9ee5e.png",
"symbol": "UBT",
"name": "Unibright",
"contractAddress": "0x7fbc10850cae055b27039af31bd258430e714c62"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/uni.svg",
"symbol": "UNI",
"name": "Uniswap",
"contractAddress": "0xb33eaad8d922b1083446dc23f610c2567fb5180f"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x1b40183EFB4Dd766f11bDa7A7c3AD8982e998421.png",
"symbol": "VSP",
"name": "Vesper Finance",
"contractAddress": "0x09c5a4bca808bd1ba2b8e6b3aaf7442046b4ca5b"
},
{
"decimalCount": 18,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/eth.svg",
"symbol": "WETH",
"name": "WETH",
"contractAddress": "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x4691937a7508860f876c9c0a2a617e7d9e945d4b.png",
"symbol": "WOO",
"name": "WOO Network",
"contractAddress": "0x1b815d120b3ef02039ee11dc2d33de7aa4a8c603"
},
{
"decimalCount": 8,
"customIconUrl": "https://wallet-asset.matic.network/img/tokens/wbtc.svg",
"symbol": "WBTC",
"name": "Wrapped Bitcoin",
"contractAddress": "0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x6e1A19F235bE7ED8E3369eF73b196C07257494DE.png",
"symbol": "WFIL",
"name": "Wrapped Filecoin",
"contractAddress": "0xede1b77c0ccc45bfa949636757cd2ca7ef30137f"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270/logo.png",
"symbol": "WMATIC",
"name": "Wrapped Matic",
"contractAddress": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xa47c8bf37f92abed4a126bda807a7b7498661acd.png",
"symbol": "UST",
"name": "Wrapped UST",
"contractAddress": "0x692597b009d13c4049a947cab2239b7d6517875f"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xf4cd3d3fda8d7fd6c5a500203e38640a70bf9577.png",
"symbol": "YF-DAI",
"name": "YfDAI.finance",
"contractAddress": "0x7e7ff932fab08a0af569f93ce65e7b8b23698ad8"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x8C92e38eCA8210f4fcBf17F0951b198Dd7668292/logo.png",
"symbol": "DHT",
"name": "dHEDGE DAO",
"contractAddress": "0x8c92e38eca8210f4fcbf17f0951b198dd7668292"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xa0246c9032bc3a600820415ae600c6388619a14d.png",
"symbol": "IFARM",
"name": "iFARM",
"contractAddress": "0xab0b2ddb9c7e440fac8e140a89c0dbcbf2d7bbff"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xa3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2.png",
"symbol": "MTA",
"name": "mStable Governance Token: Meta",
"contractAddress": "0xf501dd45a1198c2e1b5aef5314a68b9006d842e0"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0xfC40a4F89b410a1b855b5e205064a38fC29F5eb5.png",
"symbol": "RUSD",
"name": "rUSD",
"contractAddress": "0xfc40a4f89b410a1b855b5e205064a38fc29f5eb5"
},
{
"decimalCount": 18,
"customIconUrl": "https://raw.githubusercontent.com/plasmadlt/token-list/master/assets/0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e.png",
"symbol": "YFI",
"name": "yearn.finance",
"contractAddress": "0xda537104d6a5edd53c6fbba9a898708e465260b6"
}
]

View file

@ -0,0 +1 @@
[]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
[
{
"decimalCount": 9,
"symbol": "TCA",
"name": "Tangem Coin A",
"contractAddress": "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR"
},
{
"decimalCount": 9,
"symbol": "TCB",
"name": "Tangem Coin B",
"contractAddress": "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4"
}
]

@ -1 +1 @@
Subproject commit e80938f7d9ba378a91ac544a9170597e64afa696
Subproject commit 42803296690ad6914ec2c59c5fdc3f0fb30f8afc

View file

@ -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))
}
}

View file

@ -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)

View file

@ -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()
}
}

View file

@ -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()
)
}
}
}
}

View file

@ -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
}
}

View file

@ -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 }
}

View file

@ -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)
)
)
}
}
}

View file

@ -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()
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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) {

View file

@ -0,0 +1,8 @@
package com.tangem.tap.common.extensions
/**
[REDACTED_AUTHOR]
*/
fun StringBuilder.appendIf(value: String, predicate: (String) -> Boolean) {
if (predicate(value)) this.append(value)
}

View file

@ -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"

View file

@ -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
}

View file

@ -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,
)

View file

@ -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
}
}

View file

@ -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.*

View file

@ -1,8 +1,10 @@
package com.tangem.tap.common.redux.global
import com.tangem.blockchain.common.Blockchain
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
@ -36,7 +38,7 @@ sealed class GlobalAction : Action {
}
data class ScanCard(
val shouldDeriveWC: Boolean,
val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
val onSuccess: ((ScanResponse) -> Unit)? = null,
val onFailure: ((TangemError) -> Unit)? = null,
val messageResId: Int? = null,
@ -69,6 +71,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()

View file

@ -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,9 @@ 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.additionalBlockchainsToDerive,
action.messageResId
)
withMainContext {

View file

@ -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

View file

@ -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

View file

@ -5,6 +5,7 @@ import CreateProductWalletTaskResponse
import android.content.Context
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CardFilter
import com.tangem.common.CompletionResult
import com.tangem.common.SuccessResponse
@ -14,24 +15,25 @@ 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
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -39,16 +41,18 @@ 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,
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
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) }
return runTaskAsyncReturnOnMain(
runnable = ScanProductTask(null, currenciesRepository, additionalBlockchainsToDerive),
cardId = null, initialMessage = message
).also { sendScanResultsToAnalytics(analyticsHandler, it) }
}
suspend fun createProductWallet(
@ -61,14 +65,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 +95,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> {
@ -125,7 +139,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
if (continuation.context.isActive) continuation.resume(result)
}
}
}

View file

@ -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)

View file

@ -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,
)
}

View file

@ -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?) {

View file

@ -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

View file

@ -14,10 +14,11 @@ fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean {
return when (currency) {
is Currency.Blockchain -> {
if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) {
false
} else {
availableToBuy.contains(currency.currencySymbol)
val blockchain = currency.blockchain
when {
blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null
blockchain == Blockchain.Unknown || blockchain == Blockchain.BSC -> false
else -> availableToBuy.contains(currency.currencySymbol)
}
}
is Currency.Token -> false

View 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
}

View file

@ -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
)
}
}
}

View file

@ -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))
// }
// }
// }
//}

View file

@ -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 -> {

View file

@ -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 {
@ -80,7 +80,8 @@ typealias KeyWalletPublicKey = ByteArrayKey
class ScanProductTask(
val card: Card? = null,
private val currenciesRepository: CurrenciesRepository?
private val currenciesRepository: CurrenciesRepository?,
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null
) : CardSessionRunnable<ScanResponse> {
override fun run(
@ -101,7 +102,7 @@ class ScanProductTask(
val commandProcessor = when {
TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
card.isTangemTwins() -> ScanTwinProcessor()
else -> ScanWalletProcessor(currenciesRepository)
else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive)
}
commandProcessor.proceed(card, session) { processorResult ->
when (processorResult) {
@ -153,7 +154,8 @@ private class ScanNoteProcessor : ProductCommandProcessor<ScanResponse> {
}
private class ScanWalletProcessor(
private val currenciesRepository: CurrenciesRepository?
private val currenciesRepository: CurrenciesRepository?,
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null
) : ProductCommandProcessor<ScanResponse> {
var primaryCard: PrimaryCard? = null
@ -253,7 +255,7 @@ private class ScanWalletProcessor(
return
}
DerivationTask(derivations).run(session) { result ->
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
val response = ScanResponse(
@ -291,6 +293,9 @@ private class ScanWalletProcessor(
)
)
}
if (additionalBlockchainsToDerive != null) {
blockchainsToDerive.addAll(additionalBlockchainsToDerive)
}
return blockchainsToDerive.distinct()
}

View file

@ -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.tap.common.extensions.appendIf
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) {
@ -60,6 +63,11 @@ class CurrenciesRepository(val context: Application) {
saveBlockchains(cardId, blockchains)
}
fun removeCurrencies(cardId: String) {
saveTokens(cardId, emptyList())
saveBlockchains(cardId, emptyList())
}
private fun loadSavedTokens(cardId: String): List<Token> {
val json = try {
context.readFileText(getFileNameForTokens(cardId))
@ -70,11 +78,7 @@ class CurrenciesRepository(val context: Application) {
return try {
tokensAdapter.fromJson(json)!!.map { it.toToken() }.distinct()
} catch (exception: Exception) {
try {
obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() }
} catch (exception: Exception) {
emptyList()
}
emptyList()
}
}
@ -106,49 +110,73 @@ class CurrenciesRepository(val context: Application) {
}
}
fun getPopularTokens(isTestNet: Boolean = false): List<Token> {
val ethereumTokensFileName =
if (isTestNet) ETHEREUM_TESTNET_TOKENS_FILE_NAME else ETHEREUM_TOKENS_FILE_NAME
val bscTokensFileName =
if (isTestNet) BSC_TESTNET_TOKENS_FILE_NAME else BSC_TOKENS_FILE_NAME
val binanceTokensFileName =
if (isTestNet) BINANCE_TESTNET_TOKENS_FILE_NAME else BINANCE_TOKENS_FILE_NAME
fun getSupportedTokens(isTestNet: Boolean = false): List<Token> {
val supportedTokens = if (isTestNet) {
getSupportedTokens().mapNotNull { it.getTestnetVersion() }
} else {
getSupportedTokens()
}
val ethereumTokensJson = context.assets.readJsonFileToString(ethereumTokensFileName)
val bscTokensJson = context.assets.readJsonFileToString(bscTokensFileName)
val binanceTokensJson = context.assets.readJsonFileToString(binanceTokensFileName)
val blockchainTokens = supportedTokens.map {
fromJsonToTokensDao(loadTokensJson(it), it)
}
val tokens = blockchainTokens.flatten().map { it.toToken() }
return tokens
}
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()
}
private fun loadTokensJson(blockchain: Blockchain): String {
val fileName = getFileName(blockchain)
return context.assets.readJsonFileToString(fileName)
}
private fun getFileName(blockchain: Blockchain): String {
return StringBuilder().apply {
append(blockchain.id.toLowerCase().replace("/test", ""))
append("_tokens")
appendIf("_testnet") { blockchain.isTestnet() }
}.toString()
}
private fun fromJsonToTokensDao(tokenJson: String, blockchain: Blockchain): List<TokenDao> {
return obsoleteTokensAdapter.fromJson(tokenJson)!!.map { it.toTokenDao(blockchain) }
}
private fun getSupportedTokens(): List<Blockchain> {
return listOf(
Blockchain.Avalanche,
Blockchain.Binance,
Blockchain.BSC,
Blockchain.Ethereum,
Blockchain.Polygon,
Blockchain.Solana,
)
}
fun getBlockchains(
cardFirmware: FirmwareVersion?,
cardFirmware: FirmwareVersion,
isTestNet: Boolean = false
): List<Blockchain> {
return if (cardFirmware == null || cardFirmware < FirmwareVersion.MultiWalletAvailable) {
val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) {
Blockchain.secp256k1Blockchains(isTestNet)
} else {
Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet)
}
return excludeUnsupportedBlockchains(blockchains)
}
//TODO: move to the App settings
private fun excludeUnsupportedBlockchains(blockchains: List<Blockchain>): List<Blockchain> {
return blockchains.toMutableList().apply {
removeAll(listOf(
Blockchain.Fantom, Blockchain.FantomTestnet
))
}
}
companion object {
private const val ETHEREUM_TOKENS_FILE_NAME = "ethereum_tokens"
private const val ETHEREUM_TESTNET_TOKENS_FILE_NAME = "ethereum_tokens_testnet"
private const val BSC_TOKENS_FILE_NAME = "bsc_tokens"
private const val BSC_TESTNET_TOKENS_FILE_NAME = "bsc_tokens_testnet"
private const val BINANCE_TOKENS_FILE_NAME = "binance_tokens"
private const val BINANCE_TESTNET_TOKENS_FILE_NAME = "binance_tokens_testnet"
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
private const val BINANCE_TOKEN_TYPE_BEP8 = "bep8"
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
fun getFileNameForBlockchains(cardId: String): String =
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
@ -163,6 +191,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 +201,9 @@ data class TokenDao(
contractAddress = contractAddress,
decimals = decimalCount,
blockchain = blockchainDao.toBlockchain()
)
).apply {
customIconUrl?.let { this.setCustomIconUrl(it) }
}
}
companion object {
@ -182,7 +213,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 +237,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())
}
}
}
@ -217,13 +248,16 @@ data class ObsoleteTokenDao(
val symbol: String,
val contractAddress: String,
val decimalCount: Int,
val customIconUrl: String?,
) {
fun toToken(): Token {
return Token(
fun toTokenDao(blockchain: Blockchain): TokenDao {
return TokenDao(
name = name,
symbol = symbol,
contractAddress = contractAddress,
decimals = decimalCount
decimalCount = decimalCount,
blockchainDao = BlockchainDao.fromBlockchain(blockchain),
customIconUrl = customIconUrl
)
}
}

View file

@ -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,13 +86,13 @@ 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
)
}
Result.failure(response.error)
Result.fromTangemSdkError(response.error)
}
}
}

View file

@ -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]

View file

@ -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

View file

@ -0,0 +1,80 @@
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.common.extensions.stripZeroPlainString
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()
.movePointLeft(Blockchain.Binance.decimals())
.stripZeroPlainString()
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()
)
}
}
}

View file

@ -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;
}

View file

@ -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 {
@ -40,7 +42,7 @@ class WalletConnectManager {
private var sessions: MutableMap<WCSession, WalletConnectActiveData> = mutableMapOf()
fun connect(wcUri: String, wallet: WalletForSession) {
fun connect(wcUri: String) {
val session = WCSession.from(wcUri) ?: return
if (sessions[session] != null) {
store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession)
@ -55,11 +57,20 @@ class WalletConnectManager {
remotePeerId = null,
session = session,
client = client,
wallet = wallet
wallet = WalletForSession(cardId = "")
)
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
}
@ -139,16 +154,22 @@ class WalletConnectManager {
fun disconnect(session: WCSession) {
val activeData = sessions[session] ?: return
val disconnected = activeData.client.killSession()
val disconnected = if (activeData.client.isConnected) {
activeData.client.killSession()
} else {
true
}
if (disconnected) {
onSessionClosed(session)
}
}
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 +191,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 +217,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 +259,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 +276,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 +300,17 @@ class WalletConnectManager {
sessions[session] = data
val sessionData = data.toWalletConnectSession()
sessionData?.let {
store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession(
sessionData))
store.dispatchOnMain(
WalletConnectAction.ScanCard(
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 +321,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 {

View file

@ -0,0 +1,38 @@
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,
): 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 -> {
Blockchain.Ethereum
}
}
}
}
}

View file

@ -88,7 +88,6 @@ data class SessionDao(
session = session.session,
peerMeta = session.peerMeta
)
}
}
}

View file

@ -10,9 +10,11 @@ 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.toDecompressedPublicKey
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 +35,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 +44,15 @@ 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
?: transaction.gasLimit?.hexToBigDecimal()
?: BigDecimal(300000) //Set high gasLimit if not provided
val decimals = blockchain.decimals()
val decimals = wallet.blockchain.decimals()
val value = (transaction.value ?: "0").hexToBigDecimal()
?.movePointLeft(decimals) ?: return null
@ -82,8 +72,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 +105,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 +138,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 +169,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?.toDecompressedPublicKey()
?: session.wallet.walletPublicKey.toDecompressedPublicKey()
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 +214,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 +244,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 +289,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\"}"
}
}
}

View file

@ -5,7 +5,9 @@ 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.*
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
@ -18,10 +20,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -114,13 +112,14 @@ class DetailsMiddleware {
withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
currenciesRepository.removeCurrencies(card.cardId)
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
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 +205,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

View file

@ -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
@ -20,20 +23,23 @@ sealed class WalletConnectAction : Action {
data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction()
data class ScanCard(val wcUri: String) : WalletConnectAction()
object UnsupportedCard : WalletConnectAction()
data class OpenSession(
val wcUri: String,
val wallet: WalletForSession,
) : WalletConnectAction()
data class AddScanResponse(
val scanResponse: ScanResponse
): WalletConnectAction()
object RefuseOpeningSession : WalletConnectAction()
data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction()
data class AcceptOpeningSession(val session: WalletConnectSession) : WalletConnectAction()
data class ScanCard(
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()
}
}

View file

@ -1,20 +1,24 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
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.tap.store
import com.tangem.wallet.R
import org.rekotlin.Middleware
@ -33,7 +37,7 @@ class WalletConnectMiddleware {
is WalletConnectAction.HandleDeepLink -> {
if (!action.wcUri.isNullOrBlank()) {
if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
store.dispatchOnMain(WalletConnectAction.ScanCard(action.wcUri))
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
}
}
}
@ -48,12 +52,13 @@ class WalletConnectMiddleware {
}
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ClipboardOrScanQr(
action.wcUri)))
}
is WalletConnectAction.ScanCard -> {
handleScanCard(action.wcUri)
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.ClipboardOrScanQr(
action.wcUri
)
)
)
}
is WalletConnectAction.OpeningSessionTimeout -> {
@ -73,19 +78,19 @@ class WalletConnectMiddleware {
is WalletConnectAction.OpenSession -> {
walletConnectManager.connect(
wcUri = action.wcUri,
wallet = action.wallet
)
}
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)))
is WalletConnectAction.ScanCard -> {
scanCard(action.session, action.chainId)
}
is WalletConnectAction.ApproveSession -> {
@ -120,74 +125,135 @@ 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)
}
}
}
private fun handleScanCard(wcUri: String) {
store.dispatch(GlobalAction.ScanCard(true, { scanResponse ->
val card = scanResponse.card
if (!card.isMultiwalletAllowed) {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return@ScanCard
private fun scanCard(session: WalletConnectSession, chainId: Int?) {
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
chainId = chainId, peer = session.peerMeta
) ?: Blockchain.Ethereum
store.dispatch(GlobalAction.ScanCard(additionalBlockchainsToDerive = listOf(blockchain),
onSuccess = { scanResponse ->
handleScanResponse(scanResponse, session, blockchain)
},
onFailure = {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
}, R.string.wallet_connect_scan_card_message
)
)
}
private fun handleScanResponse(
scanResponse: ScanResponse, session: WalletConnectSession, blockchain: Blockchain
) {
val card = scanResponse.card
if (!card.isMultiwalletAllowed) {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return
}
val walletManager = getWalletManager(scanResponse, blockchain).guard {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
return
}
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)) {
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.FailureEstablishingSession(null))
}, R.string.wallet_connect_scan_card_message))
val walletForSession = WalletForSession(
cardId = scanResponse.card.cardId,
walletPublicKey = wallet.publicKey.seedKey,
derivedPublicKey = derivedKey,
derivationPath = wallet.publicKey.derivationPath,
blockchain = wallet.blockchain
)
val updatedSession = session.copy(wallet = walletForSession)
walletConnectManager.updateSession(updatedSession)
store.dispatchOnMain(WalletConnectAction.AddScanResponse(scanResponse))
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.ApproveWcSession(
updatedSession
)
)
)
}
private fun getWalletManager(scanResponse: ScanResponse): WalletManager? {
private fun getWalletManager(
scanResponse: ScanResponse, blockchain: Blockchain
): WalletManager? {
val card = scanResponse.card
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
val wcBlockchain = if (scanResponse.card.isTestCard) {
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
Blockchain.EthereumTestnet
} else {
Blockchain.Ethereum
blockchain
}
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
store.state.walletState.getWalletManager(wcBlockchain)
?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
store.state.walletState.getWalletManager(blockchainToMake)
?: factory.makeWalletManagerForApp(scanResponse, blockchainToMake)
?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
}
} else {
val walletManager = factory.makeWalletManagerForApp(scanResponse, blockchainToMake)
if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains(
wcBlockchain) == true
blockchainToMake
) != true
) {
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
} else {
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
?.also {
currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain)
}
walletManager?.let {
currenciesRepository.saveAddedBlockchain(
card.cardId,
blockchainToMake
)
}
}
return walletManager
}
}
}

View file

@ -16,6 +16,12 @@ class WalletConnectReducer {
sessions = state.sessions + action.session
)
}
is WalletConnectAction.OpenSession -> {
state.copy(loading = true)
}
is WalletConnectAction.AddScanResponse -> {
state.copy(scanResponse = action.scanResponse)
}
is WalletConnectAction.SetSessionsRestored ->
WalletConnectState(sessions = action.sessions)
@ -24,8 +30,6 @@ class WalletConnectReducer {
state.sessions.filterNot { it.session.toUri() == action.session.toUri() }
state.copy(sessions = sessions)
}
is WalletConnectAction.ScanCard -> state.copy(loading = true)
is WalletConnectAction.UnsupportedCard -> state.copy(loading = false)
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)

View file

@ -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 derivedPublicKey: ByteArray?,
val derivationPath: DerivationPath?,
val walletPublicKey: ByteArray? = null,
val derivedPublicKey: ByteArray? = null,
val derivationPath: DerivationPath? = null,
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
)

View file

@ -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)

View file

@ -56,7 +56,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
store.dispatch(NavigationAction.PopBackTo())
if (!result.text.isNullOrBlank()) {
store.dispatch(WalletConnectAction.ScanCard(result.text))
store.dispatch(WalletConnectAction.OpenSession(result.text))
}
}

View file

@ -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()
}
}
}

View file

@ -16,7 +16,7 @@ class ClipboardOrScanQrDialog {
setTitle(context.getString(R.string.wallet_connect))
setMessage(context.getText(R.string.wallet_connect_clipboard_alert))
setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ ->
store.dispatch(WalletConnectAction.ScanCard(wcUri))
store.dispatch(WalletConnectAction.OpenSession(wcUri))
}
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan))

View file

@ -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)
}
@ -58,7 +67,7 @@ private fun handleReadCard() {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
} else {
changeButtonState(ButtonState.PROGRESS)
store.dispatch(GlobalAction.ScanCard(false, { scanResponse ->
store.dispatch(GlobalAction.ScanCard( onSuccess = { scanResponse ->
store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
@ -79,7 +88,7 @@ private fun handleReadCard() {
}
}
}
}, {
}, onFailure = {
changeButtonState(ButtonState.ENABLED)
}))
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.onboarding
import android.content.Context
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchDialogHide
@ -23,6 +24,7 @@ class AddressInfoBottomSheetDialog(
init {
this.setContentView(R.layout.dialog_onboarding_address_info)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
setOnCancelListener { store.dispatchDialogHide() }
}

View file

@ -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 -> {

View file

@ -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 {

View file

@ -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

View file

@ -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()
}

View file

@ -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 {

View file

@ -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(

View file

@ -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 {

View file

@ -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,

View file

@ -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) }
@ -124,7 +136,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
BackupStep.ReenterAccessCode -> showReenterAccessCode(state)
is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state)
is BackupStep.WriteBackupCard -> showWriteBackupCard(state)
BackupStep.Finished -> showSuccess(state)
BackupStep.Finished -> showSuccess()
}
}
@ -182,11 +194,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
layout_buttons_add_cards.show()
layout_buttons_common.hide()
btn_add_card.text = getText(R.string.onboarding_button_add_backup_card)
if (state.backupCardsNumber < state.maxBackupCards) {
btn_add_card.text = getText(R.string.onboarding_button_add_backup_card)
btn_add_card.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
} else {
btn_add_card.text = "You hit the maximum"
btn_add_card.isEnabled = false
}
@ -240,15 +251,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 +319,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(
@ -323,21 +330,16 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) }
}
private fun showSuccess(state: BackupState) {
private fun showSuccess() {
tv_header.text = getText(R.string.onboarding_done_header)
val text = when (state.backupCardsNumber) {
1 -> getText(R.string.onboarding_subtitle_success_backup_one_card)
2 -> getText(R.string.onboarding_subtitle_success_backup)
else -> getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
}
tv_header.show()
tv_body.show()
view_pager_backup_info.hide()
tab_layout_backup_info.hide()
tv_body.text = text
btn_main_action.text = getText(R.string.common_continue)
tv_body.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
btn_main_action.text = getText(R.string.onboarding_button_continue_wallet)
btn_alternative_action.hide()
btn_main_action.setOnClickListener {
lav_confetti.cancelAnimation()
@ -362,7 +364,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)

View file

@ -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,
)

View file

@ -23,7 +23,8 @@ import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
import com.tangem.tap.common.text.truncateMiddleWith
import com.tangem.tap.common.toggleWidget.*
import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
import com.tangem.tap.common.toggleWidget.ViewStateWidget
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.send.redux.*
@ -40,8 +41,6 @@ import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_send.*
import kotlinx.android.synthetic.main.fragment_send.rv_warning_messages
import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
import kotlinx.android.synthetic.main.layout_send_fee.*
@ -298,7 +297,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
@ExperimentalCoroutinesApi
fun EditText.inputtedTextAsFlow(): Flow<String> = callbackFlow {
val watcher = addTextChangedListener { editable -> offer(editable?.toString() ?: "") }
val watcher = addTextChangedListener { editable -> trySend(editable?.toString() ?: "") }
awaitClose { removeTextChangedListener(watcher) }
}

View file

@ -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) {

View file

@ -23,29 +23,36 @@ 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_polygon_tokens, Blockchain.Polygon, 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 +60,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()
}

View file

@ -15,6 +15,7 @@ sealed class TokensAction : Action {
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
object OpenAllTokens : TokensAction()
data class SaveChanges(val addedItems: List<CurrencyListItem>) : TokensAction()
}

View file

@ -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,15 +40,15 @@ class TokensMiddleware {
}
}
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
private fun handleLoadCurrencies() {
val scanResponse = store.state.globalState.scanResponse ?: return
val isTestcard = scanResponse.card.isTestCard
val tokens = currenciesRepository.getPopularTokens(isTestcard)
val tokens = currenciesRepository.getSupportedTokens(isTestcard)
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
)

View file

@ -37,6 +37,11 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
tokensState.copy(shownCurrencies = shownCurrencies)
}
}
is TokensAction.OpenAllTokens -> {
val shownCurrencies = tokensState.currencies
shownCurrencies.forEach { if (it is CurrencyListItem.TitleListItem) it.isContentShown = true}
tokensState.copy(shownCurrencies = tokensState.currencies)
}
else -> tokensState
}
}

View file

@ -121,7 +121,7 @@ fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
}
override fun onQueryTextChange(newText: String?): Boolean {
offer(newText ?: "")
trySend(newText ?: "")
return false
}
})

View file

@ -100,6 +100,7 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
val list = mutableListOf<CurrencyListItem>()
if (!query.isNullOrEmpty()) {
store.dispatch(TokensAction.OpenAllTokens)
val queryNormalized = query.toString().toLowerCase(Locale.US)
list.addAll(
unfilteredList.filter { element ->
@ -181,7 +182,10 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, token, token.blockchain)
view.tv_currency_name.text = token.name
view.tv_currency_symbol.text = token.symbol
view.tv_currency_symbol.text = view.context.getString(
R.string.token_symbol_address_format,
token.symbol, token.contractAddress
)
view.btn_add_token.setOnClickListener {
onItemAddListener.invoke(currency)
currency.isAdded = true
@ -214,18 +218,30 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
if (title.blockchain != null) {
view.cl_subtitle_container.setOnClickListener {
val rotation = if (title.isContentShown) -90f else 0f
val rotation = if (title.isContentShown) -90f else 90f
store.dispatch(TokensAction.ToggleShowTokensForBlockchain(
title.isContentShown, title.blockchain
))
view.iv_toggle_sublist_visibility.animate().rotation(rotation)
view.iv_toggle_sublist_visibility.animate().rotation(rotation).withEndAction {
showArrow(view, title.isContentShown)
}
}
view.iv_toggle_sublist_visibility.show()
view.iv_toggle_sublist_visibility.setImageResource(R.drawable.ic_arrow_angle_down)
showArrow(view, title.isContentShown)
} else {
view.iv_toggle_sublist_visibility.hide()
}
}
private fun showArrow(view: View, isContentShown: Boolean) {
view.iv_toggle_sublist_visibility.show()
val drawable = if (isContentShown) {
R.drawable.ic_arrow_angle_down
} else {
R.drawable.ic_arrow_angle_right
}
view.iv_toggle_sublist_visibility.setImageResource(drawable)
view.iv_toggle_sublist_visibility.rotation = 0f
}
}
}

View file

@ -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()
}

View file

@ -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

View file

@ -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?,

View file

@ -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
)
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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,

View file

@ -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()

View file

@ -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"/>

View file

@ -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>

View file

@ -61,13 +61,16 @@
<TextView
android:id="@+id/tv_currency_symbol"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:singleLine="true"
android:textColor="@color/darkGray6"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_add_token"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/iv_currency"
app:layout_constraintTop_toTopOf="@id/guideline"

View file

@ -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"

Some files were not shown because too many files have changed in this diff Show more