diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index fb5232f19..0dc126afb 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -270,6 +270,7 @@ jobs: echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const alchemyApiKey = '${{ secrets.ALCHEMY_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const blinkApiKey = '${{ secrets.BLINK_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 846264dcc..b56b538c4 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -315,6 +315,7 @@ jobs: echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const blinkApiKey = '${{ secrets.BLINK_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const alchemyApiKey = '${{ secrets.ALCHEMY_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart diff --git a/.github/workflows/reusable-integration-test.yml b/.github/workflows/reusable-integration-test.yml index e5a91aeb9..9c4c24603 100644 --- a/.github/workflows/reusable-integration-test.yml +++ b/.github/workflows/reusable-integration-test.yml @@ -157,6 +157,7 @@ jobs: echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const alchemyApiKey = '${{ secrets.ALCHEMY_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const blinkApiKey = '${{ secrets.BLINK_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index a1d72a66d..11a4822e0 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -117,6 +117,9 @@ + + + diff --git a/assets/images/robinhood_chain_mono.svg b/assets/images/robinhood_chain_mono.svg new file mode 100644 index 000000000..7f56d1bda --- /dev/null +++ b/assets/images/robinhood_chain_mono.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/stocks/amd.png b/assets/images/stocks/amd.png new file mode 100644 index 000000000..d46e2abbe Binary files /dev/null and b/assets/images/stocks/amd.png differ diff --git a/assets/images/stocks/asml.png b/assets/images/stocks/asml.png new file mode 100644 index 000000000..654ec8288 Binary files /dev/null and b/assets/images/stocks/asml.png differ diff --git a/assets/images/stocks/baba.png b/assets/images/stocks/baba.png new file mode 100644 index 000000000..33830abaa Binary files /dev/null and b/assets/images/stocks/baba.png differ diff --git a/assets/images/stocks/cost.png b/assets/images/stocks/cost.png new file mode 100644 index 000000000..412f340ab Binary files /dev/null and b/assets/images/stocks/cost.png differ diff --git a/assets/images/stocks/ddog.png b/assets/images/stocks/ddog.png new file mode 100644 index 000000000..12751fb03 Binary files /dev/null and b/assets/images/stocks/ddog.png differ diff --git a/assets/images/stocks/dell.png b/assets/images/stocks/dell.png new file mode 100644 index 000000000..19572f468 Binary files /dev/null and b/assets/images/stocks/dell.png differ diff --git a/assets/images/stocks/f.png b/assets/images/stocks/f.png new file mode 100644 index 000000000..e928f6a66 Binary files /dev/null and b/assets/images/stocks/f.png differ diff --git a/assets/images/stocks/intu.png b/assets/images/stocks/intu.png new file mode 100644 index 000000000..9c5d3a524 Binary files /dev/null and b/assets/images/stocks/intu.png differ diff --git a/assets/images/stocks/lulu.png b/assets/images/stocks/lulu.png new file mode 100644 index 000000000..22195d62b Binary files /dev/null and b/assets/images/stocks/lulu.png differ diff --git a/assets/images/stocks/mu.png b/assets/images/stocks/mu.png new file mode 100644 index 000000000..012df2660 Binary files /dev/null and b/assets/images/stocks/mu.png differ diff --git a/assets/images/stocks/nu.png b/assets/images/stocks/nu.png new file mode 100644 index 000000000..7cbc3d496 Binary files /dev/null and b/assets/images/stocks/nu.png differ diff --git a/assets/images/stocks/qcom.png b/assets/images/stocks/qcom.png new file mode 100644 index 000000000..41fbe6340 Binary files /dev/null and b/assets/images/stocks/qcom.png differ diff --git a/assets/images/stocks/rivn.png b/assets/images/stocks/rivn.png new file mode 100644 index 000000000..f692f9cf6 Binary files /dev/null and b/assets/images/stocks/rivn.png differ diff --git a/assets/images/stocks/shop.png b/assets/images/stocks/shop.png new file mode 100644 index 000000000..a1d3d81b7 Binary files /dev/null and b/assets/images/stocks/shop.png differ diff --git a/assets/images/stocks/sofi.png b/assets/images/stocks/sofi.png new file mode 100644 index 000000000..80894a1ff Binary files /dev/null and b/assets/images/stocks/sofi.png differ diff --git a/assets/images/stocks/tsm.png b/assets/images/stocks/tsm.png new file mode 100644 index 000000000..ca531cf80 Binary files /dev/null and b/assets/images/stocks/tsm.png differ diff --git a/assets/images/stocks/ttwo.png b/assets/images/stocks/ttwo.png new file mode 100644 index 000000000..9683ab1bb Binary files /dev/null and b/assets/images/stocks/ttwo.png differ diff --git a/assets/images/stocks/ups.png b/assets/images/stocks/ups.png new file mode 100644 index 000000000..188668f22 Binary files /dev/null and b/assets/images/stocks/ups.png differ diff --git a/assets/images/stocks/wday.png b/assets/images/stocks/wday.png new file mode 100644 index 000000000..64eaf939c Binary files /dev/null and b/assets/images/stocks/wday.png differ diff --git a/assets/images/usdg_icon.png b/assets/images/usdg_icon.png new file mode 100644 index 000000000..ef6d8b8d6 Binary files /dev/null and b/assets/images/usdg_icon.png differ diff --git a/assets/robinhood_node_list.yml b/assets/robinhood_node_list.yml new file mode 100644 index 000000000..5fcdcda72 --- /dev/null +++ b/assets/robinhood_node_list.yml @@ -0,0 +1,23 @@ +- + uri: robinhood-rpc.publicnode.com + isDefault: true + useSSL: true + isEnabledForAutoSwitching: true + label: "PublicNode" +- + uri: robinhood.nownodes.io + useSSL: true + isEnabledForAutoSwitching: true + label: "NOWNodes" +- + uri: rpc.mainnet.chain.robinhood.com + useSSL: true + isEnabledForAutoSwitching: true + label: "Robinhood" +- + uri: robinhood-mainnet.g.alchemy.com + path: /v2 + useSSL: true + isDefault: false + isEnabledForAutoSwitching: true + label: "Alchemy" diff --git a/cakewallet.bat b/cakewallet.bat index 5bb41128c..5c64fbd6b 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,5 +1,5 @@ @echo off -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base --arbitrum --bsc +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base --arbitrum --bsc --robinhood set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% diff --git a/cw_core/lib/card_design.dart b/cw_core/lib/card_design.dart index a8e33fd8f..408966ab1 100644 --- a/cw_core/lib/card_design.dart +++ b/cw_core/lib/card_design.dart @@ -168,6 +168,8 @@ class CardDesign { static const bnb = CardDesign(imagePath: "assets/new-ui/balance_card_icons/bnb.svg"); + static const robinhood = CardDesign(imagePath: "assets/new-ui/balance_card_icons/robinhood.svg"); + static const ethSpecial = CardDesign( gradient: const LinearGradient( colors: [Color(0xFF6259FF), Color(0xFF3B20E6)], @@ -323,6 +325,15 @@ class CardDesign { backgroundType: CardDesignBackgroundTypes.svgFull, imagePath: "assets/new-ui/balance_card_backgrounds/bnb.svg"); + static const robinhoodSpecial = CardDesign( + gradient: const LinearGradient( + colors: [Color(0xFF5AA438), Color(0xFF2E6B1E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter), + colors: CardColorCombination.light, + backgroundType: CardDesignBackgroundTypes.svgFull, + imagePath: "assets/new-ui/balance_card_backgrounds/robinhood.svg"); + CardDesign withGradient(Gradient gradient) => CardDesign( gradient: gradient, colors: preferredColorCombinations[gradient] ?? colors, @@ -357,6 +368,7 @@ class CardDesign { CryptoCurrency.arbEth: _CurrencyIconNames(ticker: 'arb', longName: 'arbitrum'), CryptoCurrency.baseEth: _CurrencyIconNames(ticker: 'base', longName: 'base', chainFile: 'base_icon'), + CryptoCurrency.robEth: _CurrencyIconNames(ticker: "rob", longName: "robinhood"), CryptoCurrency.bch: _CurrencyIconNames(ticker: 'bch', longName: 'bitcoin_cash', chainFile: 'bitcoin-cash'), CryptoCurrency.btc: _CurrencyIconNames(ticker: 'btc', longName: 'bitcoin', outlineFile: 'BTC'), @@ -409,6 +421,7 @@ class CardDesign { arbitrum, zec, bnb, + robinhood, ethSpecial, btcSpecial, xmrSpecial, @@ -423,7 +436,8 @@ class CardDesign { zanoSpecial, arbitrumSpecial, zecSpecial, - bnbSpecial + bnbSpecial, + robinhoodSpecial, ]; static CardDesign forCurrencySpecial(CryptoCurrency currency) { @@ -453,6 +467,7 @@ class CardDesign { CryptoCurrency.zec: zec, CryptoCurrency.dcr: dcr, CryptoCurrency.bnb: bnb, + CryptoCurrency.robEth: robinhood, }; static const Map specialDesignsForCurrencies = { @@ -474,6 +489,7 @@ class CardDesign { CryptoCurrency.arbEth: arbitrumSpecial, CryptoCurrency.zec: zecSpecial, CryptoCurrency.bnb: bnbSpecial, + CryptoCurrency.robEth: robinhoodSpecial, }; static Map preferredColorCombinations = { diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 09ead9151..678228916 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -143,6 +143,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.arbEth, CryptoCurrency.usdcArb, CryptoCurrency.usdtArb, + CryptoCurrency.robEth, ]; static const havenCurrencies = [ @@ -974,6 +975,16 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen name: 'ltcmweb', iconPath: 'assets/new-ui/crypto_full_icons/litecoin.svg', decimals: 8); + static const robEth = CryptoCurrency( + title: "ETH", + tag: "ROB", + fullName: "Ethereum", + raw: 111, + name: "robeth", + iconPath: "assets/new-ui/crypto_full_icons/ethereum.svg", + decimals: 18, + flatIconPath: "assets/new-ui/balance_card_icons/robinhood.svg", + chainIconPath: "assets/new-ui/chain_badges/robinhood.svg"); static final Map _rawCurrencyMap = [ ...all, @@ -999,7 +1010,14 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen ...zcashCurrencies ].fold>({}, (acc, item) { if (item.fullName != null) { - acc.addAll({item.fullName!.toLowerCase(): item}); + final fullName = item.fullName!.toLowerCase(); + final existing = acc[fullName]; + + // Mainnet ETH and the L2 ETHs share the "Ethereum" full name, + // so we make the untagged coin keeps the name + if (existing == null || existing.tag != null) { + acc[fullName] = item; + } } return acc; }); @@ -1025,6 +1043,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen 'base': baseEth, 'arbitrum': arbEth, 'bsc': bnb, + "rob": robEth, + "robinhood": robEth, }; static CryptoCurrency deserialize({required int raw}) { diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index c31aa5b26..c4f1f289d 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -26,6 +26,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.arbEth; case WalletType.bsc: return CryptoCurrency.bnb; + case WalletType.robinhood: + return CryptoCurrency.robEth; case WalletType.bitcoinCash: return CryptoCurrency.bch; case WalletType.nano: @@ -66,6 +68,8 @@ CryptoCurrency getCryptoCurrencyByChainId(int chainId) { return CryptoCurrency.arbEth; case 56: return CryptoCurrency.bnb; + case 4663: + return CryptoCurrency.robEth; default: return CryptoCurrency.eth; } @@ -85,6 +89,8 @@ int? getChainIdByCryptoCurrency(CryptoCurrency currency) { return 42161; case CryptoCurrency.bnb: return 56; + case CryptoCurrency.robEth: + return 4663; default: return null; } @@ -109,6 +115,10 @@ String getCryptoCurrencyIconForWalletListItem(WalletType type, return "assets/new-ui/crypto_full_icons/base.svg"; } + if (type == WalletType.robinhood) { + return "assets/new-ui/crypto_full_icons/robinhood.svg"; + } + return walletTypeToCryptoCurrency(type, isTestnet: isTestnet, chainId: chainId).iconPath ?? ""; } @@ -133,6 +143,8 @@ String? symbolIconPathForWalletType(WalletType type) { return "$prefix/bch-symbol.svg"; case WalletType.polygon: return "$prefix/pol-symbol.svg"; + case WalletType.robinhood: + return "$prefix/rob-symbol.svg"; case WalletType.solana: return "$prefix/sol-symbol.svg"; case WalletType.tron: diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index 69c278e7b..0132183b1 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -15,6 +15,7 @@ class Erc20Token extends CryptoCurrency { this.id = 0, this.walletName, this.chainId, + Set groups = const {}, }) : _enabled = enabled, super( name: symbol.toLowerCase(), @@ -24,6 +25,7 @@ class Erc20Token extends CryptoCurrency { iconPath: iconPath, decimals: decimal, isPotentialScam: isPotentialScam, + groups: groups, ); Erc20Token.copyWith( @@ -51,6 +53,7 @@ class Erc20Token extends CryptoCurrency { iconPath: icon, decimals: other.decimal, isPotentialScam: other.isPotentialScam, + groups: other.groups, ); Erc20Token.fromMap(Map map) diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 78faba142..0686e5d4e 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -292,6 +292,7 @@ class Node { case WalletType.polygon: case WalletType.base: case WalletType.bsc: + case WalletType.robinhood: case WalletType.arbitrum: case WalletType.solana: case WalletType.tron: @@ -353,6 +354,7 @@ class Node { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: diff --git a/cw_core/lib/node_legacy.dart b/cw_core/lib/node_legacy.dart index 848b771f5..f81eb0068 100644 --- a/cw_core/lib/node_legacy.dart +++ b/cw_core/lib/node_legacy.dart @@ -183,6 +183,7 @@ class Node extends HiveObject with Keyable { case WalletType.polygon: case WalletType.base: case WalletType.bsc: + case WalletType.robinhood: case WalletType.arbitrum: case WalletType.solana: case WalletType.tron: diff --git a/cw_core/lib/node_list.dart b/cw_core/lib/node_list.dart index 899d1ed98..e9e154d31 100644 --- a/cw_core/lib/node_list.dart +++ b/cw_core/lib/node_list.dart @@ -61,6 +61,9 @@ Future> loadDefaultNodes(WalletType type) async { case WalletType.bsc: path = 'assets/bsc_node_list.yml'; break; + case WalletType.robinhood: + path = "assets/robinhood_node_list.yml"; + break; case WalletType.banano: case WalletType.none: path = ''; @@ -126,6 +129,7 @@ Future> loadAllDefaultNodes() async { final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum); final zcashNodes = await loadDefaultNodes(WalletType.zcash); final bscNodes = await loadDefaultNodes(WalletType.bsc); + final robinhoodNodes = await loadDefaultNodes(WalletType.robinhood); return moneroNodes + bitcoinElectrumServerList + @@ -143,7 +147,8 @@ Future> loadAllDefaultNodes() async { baseNodes + arbitrumNodes + zcashNodes + - bscNodes; + bscNodes + + robinhoodNodes; } Future resetToDefault() async { diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 5ccfbbc23..906a3e73f 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -24,6 +24,7 @@ const walletTypes = [ WalletType.arbitrum, WalletType.zcash, WalletType.bsc, + WalletType.robinhood, ]; const electrumWalletTypes = [ @@ -38,7 +39,8 @@ const evmWalletTypes = [ WalletType.polygon, WalletType.base, WalletType.arbitrum, - WalletType.bsc + WalletType.bsc, + WalletType.robinhood ]; // @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -102,6 +104,9 @@ enum WalletType { // @HiveField(19) bsc, + + // @HiveField(20) + robinhood, } int serializeToInt(WalletType type) { @@ -144,6 +149,8 @@ int serializeToInt(WalletType type) { return 17; case WalletType.bsc: return 18; + case WalletType.robinhood: + return 19; case WalletType.none: return -1; } @@ -189,6 +196,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.zcash; case 18: return WalletType.bsc; + case 19: + return WalletType.robinhood; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -234,6 +243,8 @@ String walletTypeToString(WalletType type) { return 'Zcash'; case WalletType.bsc: return 'BNB Smart Chain'; + case WalletType.robinhood: + return "Robinhood Chain"; case WalletType.none: return ''; } @@ -259,6 +270,7 @@ String walletTypeToDisplayName(WalletType type) => switch (type) { WalletType.arbitrum => 'Arbitrum', WalletType.zcash => 'Zcash', WalletType.bsc => 'BNB Smart Chain', + WalletType.robinhood => "Robinhood Chain", WalletType.none => '' }; @@ -282,6 +294,7 @@ String walletTypeToDisplayTicker(WalletType type) => switch (type) { WalletType.arbitrum => 'ARB', WalletType.zcash => 'ZEC', WalletType.bsc => 'BNB', + WalletType.robinhood => "", WalletType.none => '' }; @@ -327,6 +340,8 @@ WalletType? _cryptoCurrencyToWalletType(CryptoCurrency type) { return WalletType.dogecoin; case CryptoCurrency.zec: return WalletType.zcash; + case CryptoCurrency.robEth: + return WalletType.robinhood; default: return null; } diff --git a/cw_core/lib/wallet_type.part.dart b/cw_core/lib/wallet_type.part.dart index 2451e1cfa..eb89fa1ed 100644 --- a/cw_core/lib/wallet_type.part.dart +++ b/cw_core/lib/wallet_type.part.dart @@ -53,6 +53,8 @@ class WalletTypeAdapter extends TypeAdapter { return WalletType.zcash; case 19: return WalletType.bsc; + case 20: + return WalletType.robinhood; default: return WalletType.monero; } @@ -121,6 +123,9 @@ class WalletTypeAdapter extends TypeAdapter { case WalletType.bsc: writer.writeByte(19); break; + case WalletType.robinhood: + writer.writeByte(20); + break; } } diff --git a/cw_evm/lib/clients/evm_chain_client.dart b/cw_evm/lib/clients/evm_chain_client.dart index de3f9726e..1d5651294 100644 --- a/cw_evm/lib/clients/evm_chain_client.dart +++ b/cw_evm/lib/clients/evm_chain_client.dart @@ -57,7 +57,7 @@ class EVMChainClient { } if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { - final res = (jsonResponse['result'] as List); + final res = jsonResponse["result"] as List; res.removeWhere((e) => e['value'] == '0'); // Filter out spam native transactions below 0.00001 ETH (10000000000000 wei) @@ -175,17 +175,31 @@ class EVMChainClient { try { Uri? rpcUri; bool isModifiedNodeUri = false; + final nodeHost = Uri.parse("https://${node.uriRaw}").host; + final pathSegments = + (node.path ?? "").split("/").where((segment) => segment.isNotEmpty).toList(); - if (node.uriRaw.contains('nownodes.io')) { + if (nodeHost.endsWith(".nownodes.io") && pathSegments.isEmpty) { isModifiedNodeUri = true; String nowNodeApiKey = secrets.nowNodesApiKey; if (nowNodeApiKey.isEmpty) { - printV('NowNodes API key is empty, cannot connect to ${node.uriRaw}'); + printV("NowNodes API key is empty, cannot connect to ${node.uriRaw}"); return false; } rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey'); + } else if (nodeHost.endsWith(".g.alchemy.com") && + (pathSegments.isEmpty || (pathSegments.length == 1 && pathSegments.first == "v2"))) { + isModifiedNodeUri = true; + String alchemyApiKey = secrets.alchemyApiKey; + + if (alchemyApiKey.isEmpty) { + printV("Alchemy API key is empty, cannot connect to ${node.uriRaw}"); + return false; + } + + rpcUri = Uri.https(node.uriRaw, "/v2/$alchemyApiKey"); } _client = Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client); @@ -318,6 +332,7 @@ class EVMChainClient { currency == CryptoCurrency.baseEth || currency == CryptoCurrency.arbEth || currency == CryptoCurrency.bnb || + currency == CryptoCurrency.robEth || contractAddress != null); final isNativeToken = [ @@ -325,7 +340,8 @@ class EVMChainClient { CryptoCurrency.maticpoly, CryptoCurrency.baseEth, CryptoCurrency.arbEth, - CryptoCurrency.bnb + CryptoCurrency.bnb, + CryptoCurrency.robEth ].contains(currency); // Get nonce with "pending" block tag to include pending transactions @@ -540,18 +556,20 @@ class EVMChainClient { } } - Future getErc20Token(String contractAddress, String chainName) async { + Future getErc20Token(String contractAddress, String? chainName) async { try { - final token = await getErc20TokenFromMoralis(contractAddress, chainName); + if (chainName != null) { + final token = await getErc20TokenFromMoralis(contractAddress, chainName); - if (token == null || token.name.isEmpty || token.symbol.isEmpty) { - return await getErcTokenInfoFromNode(contractAddress, chainName); + if (token != null && token.name.isNotEmpty && token.symbol.isNotEmpty) { + return token; + } } - return token; + return await getErcTokenInfoFromNode(contractAddress); } catch (e) { try { - return await getErcTokenInfoFromNode(contractAddress, chainName); + return await getErcTokenInfoFromNode(contractAddress); } catch (e) { return null; } @@ -598,7 +616,7 @@ class EVMChainClient { ); } - Future getErcTokenInfoFromNode(String contractAddress, String chainName) async { + Future getErcTokenInfoFromNode(String contractAddress) async { final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); final name = await erc20.name(); final symbol = await erc20.symbol(); diff --git a/cw_evm/lib/clients/robinhood_client.dart b/cw_evm/lib/clients/robinhood_client.dart new file mode 100644 index 000000000..fe4215198 --- /dev/null +++ b/cw_evm/lib/clients/robinhood_client.dart @@ -0,0 +1,42 @@ +import "package:cw_evm/clients/evm_chain_client.dart"; +import "package:flutter/foundation.dart"; +import "package:web3dart/web3dart.dart"; + +class RobinhoodClient extends EVMChainClient { + RobinhoodClient() : super(chainId: 4663); + + @override + Transaction createTransaction({ + required EthereumAddress from, + required EthereumAddress to, + required EtherAmount amount, + EtherAmount? maxPriorityFeePerGas, + Uint8List? data, + int? maxGas, + EtherAmount? gasPrice, + EtherAmount? maxFeePerGas, + int? nonce, + }) { + EtherAmount? finalGasPrice = gasPrice; + + if (gasPrice == null && maxFeePerGas != null) { + finalGasPrice = maxFeePerGas; + } + + return Transaction( + from: from, + to: to, + value: amount, + data: data, + maxGas: maxGas, + gasPrice: finalGasPrice, + nonce: nonce, + ); + } + + @override + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction; + + @override + int get chainId => 4663; +} diff --git a/cw_evm/lib/evm_chain_client_factory.dart b/cw_evm/lib/evm_chain_client_factory.dart index bd55af384..d20a84adc 100644 --- a/cw_evm/lib/evm_chain_client_factory.dart +++ b/cw_evm/lib/evm_chain_client_factory.dart @@ -1,6 +1,7 @@ import 'package:cw_evm/clients/arbitrum_client.dart'; import 'package:cw_evm/clients/base_client.dart'; import 'package:cw_evm/clients/bsc_client.dart'; +import "package:cw_evm/clients/robinhood_client.dart"; import 'package:cw_evm/clients/ethereum_client.dart'; import 'package:cw_evm/clients/polygon_client.dart'; import 'package:cw_evm/clients/evm_chain_client.dart'; @@ -32,6 +33,8 @@ class EVMChainClientFactory { return ArbitrumClient(); case 56: return BSCClient(); + case 4663: + return RobinhoodClient(); default: return EVMChainClient(chainId: chainId); } diff --git a/cw_evm/lib/evm_chain_default_tokens.dart b/cw_evm/lib/evm_chain_default_tokens.dart index ba5ecb353..abad65627 100644 --- a/cw_evm/lib/evm_chain_default_tokens.dart +++ b/cw_evm/lib/evm_chain_default_tokens.dart @@ -1,28 +1,56 @@ -import 'package:cw_core/erc20_token.dart'; -import 'package:cw_evm/tokens/arbitrum_tokens.dart'; -import 'package:cw_evm/tokens/base_tokens.dart'; -import 'package:cw_evm/tokens/bsc_tokens.dart'; -import 'package:cw_evm/tokens/ethereum_tokens.dart'; -import 'package:cw_evm/tokens/polygon_tokens.dart'; +import "package:cw_core/erc20_token.dart"; +import "package:cw_evm/tokens/arbitrum_tokens.dart"; +import "package:cw_evm/tokens/base_tokens.dart"; +import "package:cw_evm/tokens/bsc_tokens.dart"; +import "package:cw_evm/tokens/ethereum_tokens.dart"; +import "package:cw_evm/tokens/polygon_tokens.dart"; +import "package:cw_evm/tokens/robinhood_tokens.dart"; /// Default ERC20 tokens for each EVM chain and utility methods for interacting with them class EVMChainDefaultTokens { - static List getDefaultTokensByChainId(int chainId) { - return switch (chainId) { - 1 => EthereumTokens.tokens, - 137 => PolygonTokens.tokens, - 8453 => BaseTokens.tokens, - 42161 => ArbitrumTokens.tokens, - 56 => BSCTokens.tokens, - _ => [], - }; + static List getDefaultTokensByChainId(int chainId) => switch (chainId) { + 1 => EthereumTokens.tokens, + 137 => PolygonTokens.tokens, + 8453 => BaseTokens.tokens, + 42161 => ArbitrumTokens.tokens, + 56 => BSCTokens.tokens, + 4663 => RobinhoodTokens.tokens, + _ => [], + }; + + static List getDefaultTokenAddresses(int chainId) => + getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList(); + + static List getDefaultTokenSymbols(int chainId) => + getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList(); + + static final Map> _iconPathsByAddress = {}; + + static String? getDefaultIconPathByAddress(int chainId, String contractAddress) { + final iconPaths = _iconPathsByAddress.putIfAbsent(chainId, () { + final paths = {}; + + for (final token in getDefaultTokensByChainId(chainId)) { + final iconPath = token.iconPath; + + if (iconPath != null && iconPath.isNotEmpty) { + paths[token.contractAddress.toLowerCase()] = iconPath; + } + } + + return paths; + }); + + return iconPaths[contractAddress.toLowerCase()]; } - static List getDefaultTokenAddresses(int chainId) { - return getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList(); - } + static Set getDefaultGroupsByAddress(int chainId, String contractAddress) { + for (final token in getDefaultTokensByChainId(chainId)) { + if (token.contractAddress.toLowerCase() == contractAddress.toLowerCase()) { + return token.groups; + } + } - static List getDefaultTokenSymbols(int chainId) { - return getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList(); + return const {}; } } diff --git a/cw_evm/lib/evm_chain_registry.dart b/cw_evm/lib/evm_chain_registry.dart index e3c660d24..0f6a431f3 100644 --- a/cw_evm/lib/evm_chain_registry.dart +++ b/cw_evm/lib/evm_chain_registry.dart @@ -190,6 +190,36 @@ class EvmChainRegistry { WalletType.bsc, 'BSC', ); + + // Robinhood Chain + _registerChain( + const ChainConfig( + chainId: 4663, + name: "Robinhood Chain", + shortCode: "robinhood", + caip2: "eip155:4663", + nativeCurrency: CryptoCurrency.robEth, + capabilities: ChainCapabilities( + supportsERC20: true, + supportsEIP1559: true, + supportsInternalTx: true, + supportsSubscriptions: false, + supportsENS: false, + ), + defaultRpcEndpoints: [ + "rpc.mainnet.chain.robinhood.com", + ], + explorerUrls: [ + "https://robinhoodchain.blockscout.com", + ], + feeModel: FeeModel( + type: FeeType.eip1559, + defaultGasLimit: 21000, + ), + ), + WalletType.robinhood, + "ROB", + ); } void _registerChain( diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 93a3b9759..ae65a8878 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -323,6 +323,10 @@ abstract class EVMChainWalletBase isPotentialScam: token.isPotentialScam, walletName: walletInfo.name, chainId: selectedChainId, + groups: EVMChainDefaultTokens.getDefaultGroupsByAddress( + selectedChainId, + token.contractAddress, + ), ); } @@ -339,14 +343,16 @@ abstract class EVMChainWalletBase ); } - String _getUSDCContractAddress() { + String? _getUSDCContractAddress() { return switch (selectedChainId) { 1 => "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 137 => "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", 8453 => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", 42161 => "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", 56 => "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", - _ => throw Exception("Unsupported chain ID: $selectedChainId"), + // Robinhood Chain does not have USDC so using USDG in its place + 4663 => "0x5fc5360d0400a0fd4f2af552add042d716f1d168", + _ => null, }; } @@ -355,10 +361,13 @@ abstract class EVMChainWalletBase try { await _client.getBalance(_evmChainPrivateKey.address); - final usdcContractAddress = Erc20Token( - name: "USDC", symbol: "USDC", contractAddress: _getUSDCContractAddress(), decimal: 6); + final usdcAddress = _getUSDCContractAddress(); + if (usdcAddress != null) { + final usdcContractAddress = + Erc20Token(name: "USDC", symbol: "USDC", contractAddress: usdcAddress, decimal: 6); - await _client.fetchERC20Balances(_evmChainPrivateKey.address, usdcContractAddress); + await _client.fetchERC20Balances(_evmChainPrivateKey.address, usdcContractAddress); + } return true; } catch (e) { @@ -544,7 +553,10 @@ abstract class EVMChainWalletBase final address = walletAddresses.address; if (address.isEmpty) return MoralisDiscoveryResult.empty; - final chainName = EVMChainUtils.getDefaultTokenSymbol(selectedChainId).toLowerCase(); + final chainName = EVMChainUtils.getMoralisChainName(selectedChainId); + if (chainName == null) { + return MoralisDiscoveryResult.empty; + } final walletTokens = await _client.fetchWalletTokensFromMoralis(address, chainName); if (walletTokens.isEmpty) return MoralisDiscoveryResult.empty; @@ -988,6 +1000,7 @@ abstract class EVMChainWalletBase 56 => CryptoCurrency.bnb, 8453 => CryptoCurrency.baseEth, 42161 => CryptoCurrency.arbEth, + 4663 => CryptoCurrency.robEth, _ => CryptoCurrency.eth, }; @@ -1403,11 +1416,18 @@ abstract class EVMChainWalletBase String? iconPath; if ((token.iconPath == null || token.iconPath!.isEmpty) && !token.isPotentialScam) { - try { - iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - } catch (_) {} + iconPath = EVMChainDefaultTokens.getDefaultIconPathByAddress( + selectedChainId, + token.contractAddress, + ); + + if (iconPath == null || iconPath.isEmpty) { + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + } } else if (!token.isPotentialScam) { iconPath = token.iconPath; } @@ -1448,7 +1468,7 @@ abstract class EVMChainWalletBase await transactionHistory.save(); } - Future getErc20Token(String contractAddress, String chainName) async { + Future getErc20Token(String contractAddress, String? chainName) async { try { return await _client.getErc20Token(contractAddress, chainName); } catch (e) { diff --git a/cw_evm/lib/tokens/robinhood_tokens.dart b/cw_evm/lib/tokens/robinhood_tokens.dart new file mode 100644 index 000000000..45d543e32 --- /dev/null +++ b/cw_evm/lib/tokens/robinhood_tokens.dart @@ -0,0 +1,407 @@ +import "package:cw_core/crypto_currency.dart"; +import "package:cw_core/currency_groups.dart"; +import "package:cw_core/erc20_token.dart"; + +class RobinhoodTokens { + static List get tokens { + final tokens = [ + Erc20Token( + name: "Global Dollar", + symbol: "USDG", + contractAddress: "0x5fc5360d0400a0fd4f2af552add042d716f1d168", + decimal: 6, + enabled: true, + iconPath: "assets/images/usdg_icon.png", + ), + Erc20Token( + name: "Ethena USDe", + symbol: "USDe", + contractAddress: "0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34", + decimal: 18, + enabled: true, + ), + Erc20Token( + name: "WETH", + symbol: "WETH", + contractAddress: "0x0bd7d308f8e1639fab988df18a8011f41eacad73", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Apple", + symbol: "AAPL", + contractAddress: "0xaf3d76f1834a1d425780943c99ea8a608f8a93f9", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/apple.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "AMD", + symbol: "AMD", + contractAddress: "0x86923f96303d656e4aa86d9d42d1e57ad2023fdc", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/amd.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Amazon", + symbol: "AMZN", + contractAddress: "0x12f190a9f9d7d37a250758b26824b97ce941bf54", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/amznx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "ASML Holding NV", + symbol: "ASML", + contractAddress: "0x47f93d52cbec7c6d2cfc080e154002370a60daea", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/asml.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Broadcom", + symbol: "AVGO", + contractAddress: "0x156e175dd063a8ce274c50654ef40e0032b3fbcf", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/avgox.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Alibaba", + symbol: "BABA", + contractAddress: "0xad25ac6c84d497db898fa1e8387bf6af3532a1c4", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/baba.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Coinbase", + symbol: "COIN", + contractAddress: "0x6330d8c3178a418788df01a47479c0ce7ccf450b", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/coinx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Costco", + symbol: "COST", + contractAddress: "0x4ea005168d7f09a7a0ba9d1def21a479950e44c2", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/cost.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Circle Internet Group", + symbol: "CRCL", + contractAddress: "0xdf0992e440dd0be65bd8439b609d6d4366bf1cb5", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/crclx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Datadog", + symbol: "DDOG", + contractAddress: "0x27c99fbde9d0d2aa4f4bfb4943f237843ddf6958", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/ddog.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Dell", + symbol: "DELL", + contractAddress: "0x941ae714ec6d8130c7b75d67160ca08f1e7d11dd", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/dell.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Ford Motor", + symbol: "F", + contractAddress: "0x25c288e6d899b9bc30160965ad9644c67e73be0c", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/f.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "GameStop", + symbol: "GME", + contractAddress: "0x1b0e319c6a659f002271b69db8a7df2f911c153e", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/gmex.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Alphabet Class A", + symbol: "GOOGL", + contractAddress: "0x2e0847e8910a9732eb3fb1bb4b70a580adad4fe3", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/googlx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Intel", + symbol: "INTC", + contractAddress: "0xc72b96e0e48ecd4dc75e1e45396e26300bc39681", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/intcx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Intuit", + symbol: "INTU", + contractAddress: "0x56d23bee5f41a7120170b0c603dae30128e460e9", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/intu.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Eli Lilly", + symbol: "LLY", + contractAddress: "0x8005d266423c7ea827372c9c864491e5786600ea", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/llyx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Lululemon", + symbol: "LULU", + contractAddress: "0x4e62068525ab11fe768e29dfd00ef909b9803016", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/lulu.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Meta Platforms", + symbol: "META", + contractAddress: "0xc0d6457c16cc70d6790dd43521c899c87ce02f35", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/metax.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Marvell Technology", + symbol: "MRVL", + contractAddress: "0x62fd0668e10d8b72339be2dcf7643001688ff13b", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/mrvlx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Microsoft", + symbol: "MSFT", + contractAddress: "0xe93237c50d904957cf27e7b1133b510c669c2e74", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/msftx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Strategy Inc.", + symbol: "MSTR", + contractAddress: "0xec262a75e413fafd0df80480274532c79d42da09", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/mstrx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Micron Technology", + symbol: "MU", + contractAddress: "0xff080c8ce2e5feadaca0da81314ae59d232d4afd", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/mu.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Netflix", + symbol: "NFLX", + contractAddress: "0xe0444ef8bf4ed74f74fd73686e2ddf4c1c5591e8", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/nflxx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Nu", + symbol: "NU", + contractAddress: "0x408c14038a04f7bd235329e26d2bf569ee20e250", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/nu.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "NVIDIA", + symbol: "NVDA", + contractAddress: "0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/nvdax.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Oracle", + symbol: "ORCL", + contractAddress: "0xb0992820e760d836549ba69bc7598b4af75dee03", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/orclx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Palantir Technologies", + symbol: "PLTR", + contractAddress: "0x894e1ec2d74ffe5aef8dc8a9e84686accb964f2a", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/pltrx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Qualcomm", + symbol: "QCOM", + contractAddress: "0x0f17206447090e464c277571124dd2688e48aea9", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/qcom.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Invesco QQQ", + symbol: "QQQ", + contractAddress: "0xd5f3879160bc7c32ebb4dc785f8a4f505888de68", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/qqqx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Rivian Automotive", + symbol: "RIVN", + contractAddress: "0xb1bf26c1d20ff267a4f93550d1e0d06ac40a114b", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/rivn.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Shopify", + symbol: "SHOP", + contractAddress: "0xf53f66751b1eff985311b693531e3290f600c410", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/shop.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "SoFi Technologies", + symbol: "SOFI", + contractAddress: "0x98e75885157c80992a8d41b696d8c9c6fb30a926", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/sofi.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "SPDR S&P 500 ETF Trust", + symbol: "SPY", + contractAddress: "0x117cc2133c37b721f49de2a7a74833232b3b4c0c", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/spyx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Tesla", + symbol: "TSLA", + contractAddress: "0x322f0929c4625ed5bad873c95208d54e1c003b2d", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/tslax.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Taiwan Semiconductor Manufacturing", + symbol: "TSM", + contractAddress: "0x58ffe4a942d3885baa22d7520691f611ef09e7aa", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/tsm.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Take-Two Interactive Software", + symbol: "TTWO", + contractAddress: "0x5e81213613b6b86eab4c6c50d718d34359459786", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/ttwo.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "UPS", + symbol: "UPS", + contractAddress: "0xf23250dac154d05bb671cb0d0ebef3c635c79ce2", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/ups.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "Workday", + symbol: "WDAY", + contractAddress: "0x82da4646242e1d962e96e932269dc644c94a9caa", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/wday.png", + groups: const {CurrencyGroups.tokenizedStock}, + ), + Erc20Token( + name: "ExxonMobil Holdings Corporation", + symbol: "XOM", + contractAddress: "0xf9b46d3d1b22199d4d1025a9cedb540a33f1a2d5", + decimal: 18, + enabled: false, + iconPath: "assets/images/stocks/xomx.webp", + groups: const {CurrencyGroups.tokenizedStock}, + ), + ]; + + return tokens.map((token) { + String? iconPath; + if (token.iconPath?.isEmpty ?? true) { + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + } else { + iconPath = token.iconPath; + } + + return Erc20Token.copyWith(token, icon: iconPath, tag: "ROB"); + }).toList(); + } +} diff --git a/cw_evm/lib/utils/evm_chain_utils.dart b/cw_evm/lib/utils/evm_chain_utils.dart index 39e52d50b..5a7611a31 100644 --- a/cw_evm/lib/utils/evm_chain_utils.dart +++ b/cw_evm/lib/utils/evm_chain_utils.dart @@ -3,19 +3,22 @@ import "package:web3dart/web3dart.dart" show EtherAmount, EtherUnit; /// Utility class for chain-specific EVM chain operations class EVMChainUtils { - static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) => switch (chainId) { - 1 => _ethereumPriorityFee(priority), - 137 => _polygonPriorityFee(priority), - 8453 => _basePriorityFee(priority), - 56 => _ethereumPriorityFee(priority), - 42161 => 0, // Arbitrum doesn't use priority fees - _ => _ethereumPriorityFee(priority), - }; + static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) => + switch (chainId) { + 1 => _ethereumPriorityFee(priority), + 137 => _polygonPriorityFee(priority), + 8453 => _basePriorityFee(priority), + 56 => _ethereumPriorityFee(priority), + 42161 => 0, // Arbitrum doesn't use priority fees + 4663 => 0, // Robinhood Chain (Arbitrum Orbit) doesn't use priority fees + _ => _ethereumPriorityFee(priority), + }; static bool hasPriorityFee(int chainId) => switch (chainId) { - 42161 => false, // Arbitrum doesn't use priority fees - _ => true, - }; + 42161 => false, // Arbitrum doesn't use priority fees + 4663 => false, // Robinhood Chain (Arbitrum Orbit) doesn't use priority fees + _ => true, + }; static int computeBufferedMaxFeePerGasWei({ required int? gasBaseFee, @@ -34,52 +37,67 @@ class EVMChainUtils { } static String getTransactionHistoryFileName(int chainId) => switch (chainId) { - 1 => "transactions.json", // Ethereum - 137 => "polygon_transactions.json", - 8453 => "base_transactions.json", - 42161 => "arbitrum_transactions.json", - 56 => "bsc_transactions.json", - _ => "transactions_$chainId.json", // Generic format for other chains - }; + 1 => "transactions.json", // Ethereum + 137 => "polygon_transactions.json", + 8453 => "base_transactions.json", + 42161 => "arbitrum_transactions.json", + 56 => "bsc_transactions.json", + 4663 => "robinhood_transactions.json", + _ => "transactions_$chainId.json", // Generic format for other chains + }; /// Get scan provider preference key for a wallet type static String getScanProviderPreferenceKey(int chainId) => switch (chainId) { - 1 => "use_etherscan", - 137 => "use_polygonscan", - 8453 => "use_base_scan", - 42161 => "use_arbitrum_scan", - 56 => "use_bscscan", - _ => "use_etherscan", - }; + 1 => "use_etherscan", + 137 => "use_polygonscan", + 8453 => "use_base_scan", + 42161 => "use_arbitrum_scan", + 56 => "use_bscscan", + 4663 => "use_robinhood_scan", + _ => "use_etherscan", + }; static String getDefaultTokenTag(int chainId) => switch (chainId) { - 1 => "ETH", - 137 => "POL", - 8453 => "BASE", - 42161 => "ARB", - 56 => "BSC", - _ => "ETH", - }; + 1 => "ETH", + 137 => "POL", + 8453 => "BASE", + 42161 => "ARB", + 56 => "BSC", + 4663 => "ROB", + _ => "ETH", + }; static String getFeeCurrency(int chainId) => switch (chainId) { - 1 => "ETH", - 137 => "POL", - 8453 => "ETH", - 42161 => "ETH", - 56 => "BNB", - _ => "ETH", - }; + 1 => "ETH", + 137 => "POL", + 8453 => "ETH", + 42161 => "ETH", + 56 => "BNB", + 4663 => "ETH", + _ => "ETH", + }; static String getDefaultTokenSymbol(int chainId) => switch (chainId) { - 1 => "ETH", - 137 => "POL", - 8453 => "BASE", - 42161 => "ARBITRUM", - 56 => "BSC", - _ => "ETH", - }; + 1 => "ETH", + 137 => "POL", + 8453 => "BASE", + 42161 => "ARBITRUM", + 56 => "BSC", + 4663 => "ETH", + _ => "ETH", + }; - static int _ethereumPriorityFee(EVMChainTransactionPriority priority) => EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + static String? getMoralisChainName(int chainId) => switch (chainId) { + 1 => "eth", + 137 => "polygon", + 8453 => "base", + 42161 => "arbitrum", + 56 => "bsc", + _ => null, + }; + + static int _ethereumPriorityFee(EVMChainTransactionPriority priority) => + EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); // Polygon priority fee calculation (minimum 25 gwei + additional based on priority) static int _polygonPriorityFee(EVMChainTransactionPriority priority) { @@ -98,9 +116,10 @@ class EVMChainUtils { } static int _basePriorityFee(EVMChainTransactionPriority priority) => switch (priority) { - EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(), - EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(), - EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), - _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), - }; + EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(), + EVMChainTransactionPriority.medium => + EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(), + EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), + _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), + }; } diff --git a/cw_evm/test/robinhood_tokens_test.dart b/cw_evm/test/robinhood_tokens_test.dart new file mode 100644 index 000000000..d4a39ed2f --- /dev/null +++ b/cw_evm/test/robinhood_tokens_test.dart @@ -0,0 +1,91 @@ +import "package:cw_core/currency_groups.dart"; +import "package:cw_evm/tokens/robinhood_tokens.dart"; +import "package:cw_evm/utils/evm_chain_utils.dart"; +import "package:flutter_test/flutter_test.dart"; + +void main() { + group("RobinhoodTokens integrity", () { + final tokens = RobinhoodTokens.tokens; + final addressPattern = RegExp(r"^0x[0-9a-f]{40}$"); + + test("token list is non-empty", () => expect(tokens.isNotEmpty, isTrue)); + + test("every contract address is a lowercased 0x-prefixed 40-hex-char address", () { + for (final token in tokens) { + expect( + addressPattern.hasMatch(token.contractAddress), + isTrue, + reason: "bad address for ${token.symbol}: ${token.contractAddress}", + ); + } + }); + + test("every token has a non-empty name and symbol", () { + for (final token in tokens) { + expect(token.name.trim(), isNotEmpty, reason: "empty name for ${token.contractAddress}"); + expect( + token.symbol.trim(), + isNotEmpty, + reason: "empty symbol for ${token.contractAddress}", + ); + } + }); + + test("every decimal is within the 0..18 range", () { + for (final token in tokens) { + expect( + token.decimal, + inInclusiveRange(0, 18), + reason: "bad decimal for ${token.symbol}: ${token.decimal}", + ); + } + }); + + test("no duplicate contract addresses", () { + final seen = {}; + for (final token in tokens) { + final addr = token.contractAddress.toLowerCase(); + expect(seen.add(addr), isTrue, reason: "duplicate address $addr (${token.symbol})"); + } + }); + + test("USDG is the 6-decimal stablecoin and is enabled by default", () { + final usdg = tokens.firstWhere((t) => t.symbol == "USDG"); + expect(usdg.decimal, 6); + expect(usdg.enabled, isTrue); + }); + + test("tokenized stock tokens exist and ship disabled", () { + final stocks = tokens.where((t) => t.groups.contains(CurrencyGroups.tokenizedStock)).toList(); + expect(stocks, isNotEmpty); + expect(stocks.every((t) => !t.enabled), isTrue); + }); + }); + + group("EVMChainUtils Robinhood chain params", () { + test("getMoralisChainName is null for Robinhood (Moralis does not index 4663)", () { + expect(EVMChainUtils.getMoralisChainName(4663), isNull); + }); + + test("getMoralisChainName maps supported chains to Moralis slugs", () { + expect(EVMChainUtils.getMoralisChainName(1), "eth"); + expect(EVMChainUtils.getMoralisChainName(137), "polygon"); + expect(EVMChainUtils.getMoralisChainName(8453), "base"); + expect(EVMChainUtils.getMoralisChainName(42161), "arbitrum"); + expect(EVMChainUtils.getMoralisChainName(56), "bsc"); + }); + + test("getMoralisChainName is null for unknown chains", () { + expect(EVMChainUtils.getMoralisChainName(999999), isNull); + }); + + test("Robinhood is an Orbit chain with no priority fee", () { + expect(EVMChainUtils.hasPriorityFee(4663), isFalse); + }); + + test("Robinhood default token tag is ROB and fee currency is ETH", () { + expect(EVMChainUtils.getDefaultTokenTag(4663), "ROB"); + expect(EVMChainUtils.getFeeCurrency(4663), "ETH"); + }); + }); +} diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index c69ec8387..ed5a365a8 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -439,14 +439,16 @@ class SolanaWalletClient { return walletSent && walletReceived; } + static const unresolvedTokenTitle = "TOKEN"; + static CryptoCurrency currencyForRawAmount(SPLToken? token, int mintDecimals) { if (token != null && token.decimals == mintDecimals) { return token; } return CryptoCurrency( - name: (token?.title ?? "TOKEN").toLowerCase(), - title: token?.title ?? "TOKEN", + name: (token?.title ?? unresolvedTokenTitle).toLowerCase(), + title: token?.title ?? unresolvedTokenTitle, decimals: mintDecimals, ); } @@ -1272,7 +1274,10 @@ class SolanaWalletClient { }, ); - if (response.statusCode != 200) return null; + if (response.statusCode != 200) { + throw Exception("Moralis token metadata returned ${response.statusCode}"); + } + final decodedResponse = jsonDecode(response.body) as Map; final symbol = decodedResponse['symbol'] ?? ''; diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 8253b5894..89eb4b75e 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -360,6 +360,9 @@ abstract class SolanaWalletBase @override Future updateTransactionsHistory({List? specificTokenMints}) async { + _seedTokenInfoCache(); + await _reparseUnresolvedTokenTransactionsOnce(); + await Future.wait([ _updateNativeSOLTransactions(), updateSPLTokenTransactions(specificMints: specificTokenMints), @@ -393,6 +396,32 @@ abstract class SolanaWalletBase await prefs.remove(_lastSyncedSignatureKey(source)); } + void _seedTokenInfoCache() { + for (final token in _splTokens) { + _client.tokenInfoCache[token.mintAddress] = token; + } + } + + Future _reparseUnresolvedTokenTransactionsOnce() async { + final prefs = await _sharedPrefs.future; + final key = "solana_unresolved_token_reparse_done_${walletInfo.name}"; + + if (prefs.getBool(key) ?? false) { + return; + } + + final hasUnresolvedTransactions = transactionHistory.transactions.values.any( + (transaction) => + transaction.amount.currency.symbol == SolanaWalletClient.unresolvedTokenTitle, + ); + + if (hasUnresolvedTransactions) { + await _clearLastSyncedSignature(_nativeSource); + } + + await prefs.setBool(key, true); + } + /// Polls for a specific transaction by signature with exponential backoff /// I'm using this in case we make the call to fetch the transaction and it has not finished its confirmations on the solana network and been indexed by the node networks we use. Future pollForTransaction({ @@ -401,6 +430,7 @@ abstract class SolanaWalletBase int maxRetries = 5, }) async { final walletAddress = _solanaPublicKey.toAddress().address; + _seedTokenInfoCache(); for (int i = 0; i < maxRetries; i++) { await Future.delayed(initialDelay * (i + 1)); diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 43eec935d..0898ac791 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -363,6 +363,26 @@ bsc-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + robinhood + CFBundleURLSchemes + + robinhood + + + + CFBundleTypeRole + Viewer + CFBundleURLName + robinhood-wallet + CFBundleURLSchemes + + robinhood-wallet + + CFBundleTypeRole Viewer diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 282ed7698..846099d18 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -137,6 +137,7 @@ class DFXBuyProvider extends BuyProvider { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: return wallet.signMessage(message); diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 651dc6b84..090e67da6 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -108,6 +108,7 @@ class RobinhoodBuyProvider extends BuyProvider { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 1c4c6d372..7d198efc0 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -113,6 +113,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.eth: case CryptoCurrency.baseEth: case CryptoCurrency.arbEth: + case CryptoCurrency.robEth: case CryptoCurrency.mana: case CryptoCurrency.matic: case CryptoCurrency.maticpoly: @@ -271,6 +272,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.avaxc: case CryptoCurrency.baseEth: case CryptoCurrency.arbEth: + case CryptoCurrency.robEth: return [42]; case CryptoCurrency.bch: return [42, 54, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]; @@ -382,6 +384,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.maticpoly: case CryptoCurrency.baseEth: case CryptoCurrency.arbEth: + case CryptoCurrency.robEth: case CryptoCurrency.arb: pattern = '0x[0-9a-zA-Z]+'; case CryptoCurrency.nano: diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index efe0f3655..9123a491e 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -37,6 +37,7 @@ class SeedValidator extends Validator { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.getEVMWordList(language); case WalletType.bitcoinCash: return getBitcoinWordList(language); diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 3700a1c84..4ab9b67cb 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -81,6 +81,7 @@ class WalletCreationService { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: diff --git a/lib/di.dart b/lib/di.dart index bd1e79b24..5d4834ca9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1234,6 +1234,7 @@ Future setup({ case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.createEVMWalletService(param1, SettingsStoreBase.walletPasswordDirectInput); case WalletType.bitcoinCash: return bitcoinCash!.createBitcoinCashWalletService( diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index ce43f9362..4f1225ca4 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -64,6 +64,7 @@ const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022'; const baseDefaultNodeUri = 'base-rpc.publicnode.com'; const arbitrumDefaultNodeUri = 'arbitrum-one-rpc.publicnode.com'; const bscDefaultNodeUri = 'bsc-dataseed.bnbchain.org'; +const robinhoodDefaultNodeUri = "robinhood-rpc.publicnode.com"; const zcashDefaultNodeUri = 'zec.rocks:443'; Future defaultSettingsMigration( @@ -660,6 +661,12 @@ Future defaultSettingsMigration( break; case 72: await createDefaultChartsData(); + await addWalletNodeList(type: WalletType.robinhood); + await _changeDefaultNode( + sharedPreferences: sharedPreferences, + type: WalletType.robinhood, + currentNodePreferenceKey: PreferencesKey.currentRobinhoodNodeIdKey, + ); break; default: break; diff --git a/lib/entities/node_check.dart b/lib/entities/node_check.dart index e66fc6dce..dbc0b583f 100644 --- a/lib/entities/node_check.dart +++ b/lib/entities/node_check.dart @@ -16,6 +16,7 @@ const Map nodePreferenceKeys = { WalletType.base: PreferencesKey.currentBaseNodeIdKey, WalletType.arbitrum: PreferencesKey.currentArbitrumNodeIdKey, WalletType.bsc: PreferencesKey.currentBscNodeIdKey, + WalletType.robinhood: PreferencesKey.currentRobinhoodNodeIdKey, WalletType.nano: PreferencesKey.currentNanoNodeIdKey, WalletType.decred: PreferencesKey.currentDecredNodeIdKey, WalletType.bitcoinCash: PreferencesKey.currentBitcoinCashNodeIdKey, diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index de1ea9c76..088cb45a0 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -13,6 +13,7 @@ class PreferencesKey { static const currentBaseNodeIdKey = 'current_node_id_base'; static const currentArbitrumNodeIdKey = 'current_node_id_arbitrum'; static const currentBscNodeIdKey = 'current_node_id_bsc'; + static const currentRobinhoodNodeIdKey = "current_node_id_robinhood"; static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; static const currentDecredNodeIdKey = 'current_node_id_decred'; @@ -92,6 +93,7 @@ class PreferencesKey { static const useBaseScan = 'use_base_scan'; static const useArbiScan = 'use_arbitrum_scan'; static const useBscScan = 'use_bscscan'; + static const useRobinhoodScan = "use_robinhood_scan"; static const useTronGrid = 'use_trongrid'; static const useMempoolFeeAPI = 'use_mempool_fee_api'; static const evmHiddenChainIds = 'evm_hidden_chain_ids'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index f41c3b34b..71d4c0e44 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -30,6 +30,7 @@ List priorityForWalletType(WalletType type) { case WalletType.dogecoin: return dogecoin!.getTransactionPriorities(); case WalletType.arbitrum: + case WalletType.robinhood: case WalletType.nano: case WalletType.banano: case WalletType.solana: diff --git a/lib/evm/cw_evm.dart b/lib/evm/cw_evm.dart index a291169ee..5145d643d 100644 --- a/lib/evm/cw_evm.dart +++ b/lib/evm/cw_evm.dart @@ -199,10 +199,13 @@ class CWEVM extends EVM { @override Future getErc20Token(WalletBase wallet, String contractAddress) { final evmWallet = wallet as EVMChainWallet; - final chainName = EVMChainUtils.getDefaultTokenSymbol(evmWallet.selectedChainId).toLowerCase(); - return evmWallet.getErc20Token(contractAddress, chainName); + return evmWallet.getErc20Token(contractAddress, getMoralisChainName(wallet)); } + @override + String? getMoralisChainName(WalletBase wallet) => + EVMChainUtils.getMoralisChainName((wallet as EVMChainWallet).selectedChainId); + @override CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { transaction as EVMChainTransactionInfo; diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart index e6c94d559..c3721c9c4 100644 --- a/lib/exchange/provider/changenow_exchange_provider.dart +++ b/lib/exchange/provider/changenow_exchange_provider.dart @@ -356,6 +356,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { return 'cchain'; case 'ARB': return 'arbitrum'; + case "ROB": + return "hood"; default: return tag.toLowerCase(); } @@ -366,6 +368,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { 'POLY' => 'MATIC', 'AVAXC' => 'CCHAIN', 'ARBITRUM' => 'ARB', + "HOOD" => "ROB", _ => network, }; } diff --git a/lib/exchange/provider/near_Intents_exchange_provider.dart b/lib/exchange/provider/near_Intents_exchange_provider.dart index 7b1c2ef1d..e47d5017d 100644 --- a/lib/exchange/provider/near_Intents_exchange_provider.dart +++ b/lib/exchange/provider/near_Intents_exchange_provider.dart @@ -50,6 +50,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider { 'AVAXC': '0x1111111111111111111111111111111111111111', 'ARB': '0x1111111111111111111111111111111111111111', 'BASE': '0x1111111111111111111111111111111111111111', + "ROB": "0x1111111111111111111111111111111111111111", // Others 'SOL': '11111111111111111111111111111111', @@ -516,6 +517,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider { return switch (tag) { 'TRX' => 'tron', 'AVAXC' => 'avax', + "ROB" => "hood", _ => tag?.toLowerCase(), }; } @@ -524,6 +526,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider { return switch (blockchain) { 'tron' => 'TRX', 'avax' => 'AVAXC', + "hood" => "ROB", _ => blockchain?.toUpperCase(), }; } diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index d111edcef..ae3e80b24 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -457,6 +457,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { return 'zcash'; case 'AVAXC': return 'avax'; + case "ROB": + return "robinhood"; default: return tag.toLowerCase(); } diff --git a/lib/exchange/provider/swaptrade_exchange_provider.dart b/lib/exchange/provider/swaptrade_exchange_provider.dart index e3db6929f..d67dfde1a 100644 --- a/lib/exchange/provider/swaptrade_exchange_provider.dart +++ b/lib/exchange/provider/swaptrade_exchange_provider.dart @@ -54,6 +54,10 @@ class SwapTradeExchangeProvider extends ExchangeProvider { required CryptoCurrency to, required bool isFixedRateMode, }) async { + if (!_isPairSupported(from, to)) { + return null; + } + try { final uri = Uri.https(apiAuthority, getCoins); final response = await ProxyWrapper().get(clearnetUri: uri); @@ -96,6 +100,7 @@ class SwapTradeExchangeProvider extends ExchangeProvider { required bool isReceiveAmount}) async { try { if (amount == 0) return 0.0; + if (!_isPairSupported(from, to)) return 0.0; if (isFixedRateMode && !supportsFixedRate) { return 0.0; } @@ -191,13 +196,24 @@ class SwapTradeExchangeProvider extends ExchangeProvider { required bool isFixedRateMode, required bool isSendAll, }) async { + final sendNetwork = _networkFor(request.fromCurrency); + final receiveNetwork = _networkFor(request.toCurrency); + if (sendNetwork == null || receiveNetwork == null) { + throw TradeNotCreatedException( + description, + description: "SwapTrade has no network for " + "${request.fromCurrency.title} ${request.fromCurrency.tag ?? ""} or " + "${request.toCurrency.title} ${request.toCurrency.tag ?? ""}", + ); + } + try { final params = {}; var body = { 'coin_send': _normalizeCurrency(request.fromCurrency), - 'coin_send_network': _networkFor(request.fromCurrency), + 'coin_send_network': sendNetwork, 'coin_receive': _normalizeCurrency(request.toCurrency), - 'coin_receive_network': _networkFor(request.toCurrency), + 'coin_receive_network': receiveNetwork, 'amount_send': request.fromAmount, 'recipient': request.toAddress, 'ref': 'cake', @@ -396,22 +412,16 @@ class SwapTradeExchangeProvider extends ExchangeProvider { } } - String _networkFor(CryptoCurrency currency) { - final network = switch (currency) { - CryptoCurrency.eth => 'ETH', - CryptoCurrency.bnb => 'BNB_BSC', - CryptoCurrency.usdterc20 => 'USDT_ERC20', - CryptoCurrency.usdttrc20 => 'TRX_USDT_S2UZ', - CryptoCurrency.usdtbsc => 'USDT_BSC', - CryptoCurrency.sol => 'SOL', - CryptoCurrency.btc => 'BTC', - CryptoCurrency.xmr => 'XMR', - CryptoCurrency.ltc => 'LTC', - CryptoCurrency.ada => 'ADA', - CryptoCurrency.bch => 'BCH', - CryptoCurrency.zec => 'ZEC', - _ => currency.title.toUpperCase(), - }; - return network; - } + bool _isPairSupported(CryptoCurrency from, CryptoCurrency to) => + _networkFor(from) != null && _networkFor(to) != null; + + String? _networkFor(CryptoCurrency currency) => + switch ((currency.title.toUpperCase(), currency.tag?.toUpperCase())) { + ("BNB", "BSC") => "BNB_BSC", + ("USDT", "ETH") => "USDT_ERC20", + ("USDT", "TRX") => "TRX_USDT_S2UZ", + ("USDT", "BSC") => "USDT_BSC", + (final title, null) => title, + _ => null, + }; } diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index e78b5d247..0a41e2297 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -612,7 +612,10 @@ class _NewSendPageState extends State { subtitle: "~${output.estimatedFee} ${widget.sendViewModel.currencySymbol} (${output.estimatedFeeFiatAmount} ${widget.sendViewModel.fiatCurrency})", // Without fee priorities the row does nothing, - // so it must not be announced as interactive. + // so it must not be announced as interactive or + // show a tappable arrow. + showArrow: widget + .sendViewModel.feesViewModel.hasFeesPriority, onTap: widget.sendViewModel.feesViewModel .hasFeesPriority ? () => diff --git a/lib/new-ui/widgets/currency_picker/multi_network_currency_picker.dart b/lib/new-ui/widgets/currency_picker/multi_network_currency_picker.dart index aa6316d33..aa3339d53 100644 --- a/lib/new-ui/widgets/currency_picker/multi_network_currency_picker.dart +++ b/lib/new-ui/widgets/currency_picker/multi_network_currency_picker.dart @@ -481,6 +481,7 @@ class _MultiNetworkPickerBodyState extends State<_MultiNetworkPickerBody> { String _shortChainLabel(CryptoCurrency c) { if (cryptoCurrencyOrTokenToWalletType(c) == WalletType.bsc) return 'BSC'; + if (cryptoCurrencyOrTokenToWalletType(c) == WalletType.robinhood) return "ROB"; return chainNameForCurrency(c); } @@ -493,7 +494,7 @@ class _MultiNetworkPickerBodyState extends State<_MultiNetworkPickerBody> { } bool _isL2NativeEth(CryptoCurrency c) => - c == CryptoCurrency.arbEth || c == CryptoCurrency.baseEth; + c == CryptoCurrency.arbEth || c == CryptoCurrency.baseEth || c == CryptoCurrency.robEth; _SelSection? _selectedSection({ required List recents, diff --git a/lib/new-ui/widgets/receive_page/receive_info_box.dart b/lib/new-ui/widgets/receive_page/receive_info_box.dart index ac3f1acfd..e47269bd7 100644 --- a/lib/new-ui/widgets/receive_page/receive_info_box.dart +++ b/lib/new-ui/widgets/receive_page/receive_info_box.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; +import "package:cw_core/currency_for_wallet_type.dart"; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; @@ -36,6 +37,7 @@ class ReceiveInfoBox extends StatelessWidget { case WalletType.polygon: case WalletType.zano: case WalletType.bsc: + case WalletType.robinhood: if (autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.disabled) return null; return ReceiveInfoBox( iconPath: "", @@ -43,8 +45,7 @@ class ReceiveInfoBox extends StatelessWidget { onDismissed: onDismissed, bottomWidget: InfoboxCurrencyRow( currencies: supportedCurrencies ?? [], - chainIconPath: - "assets/new-ui/chain_badges/${walletTypeToString(type).toLowerCase()}.svg", + chainIconPath: walletTypeToCryptoCurrency(type).chainIconPath ?? "", )); default: if (autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.disabled) return null; diff --git a/lib/new-ui/widgets/receive_page/receive_token_display.dart b/lib/new-ui/widgets/receive_page/receive_token_display.dart index db0486da4..e123db950 100644 --- a/lib/new-ui/widgets/receive_page/receive_token_display.dart +++ b/lib/new-ui/widgets/receive_page/receive_token_display.dart @@ -1,9 +1,9 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import "package:cw_core/currency_for_wallet_type.dart"; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter_svg/flutter_svg.dart'; class ReceiveTokenDisplay extends StatelessWidget { const ReceiveTokenDisplay({super.key, required this.addressListViewModel}); @@ -35,7 +35,7 @@ class ReceiveTokenDisplay extends StatelessWidget { children: [ CakeImageWidget( imageUrl: - "assets/new-ui/chain_badges/${addressListViewModel.wallet.type == WalletType.bsc ? 'bnb' : walletTypeToString(addressListViewModel.wallet.type).toLowerCase()}.svg", + walletTypeToCryptoCurrency(addressListViewModel.wallet.type).chainIconPath, width: 16, height: 16, colorFilter: diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart index 61a6c11d2..e9cb16973 100644 --- a/lib/reactions/wallet_connect.dart +++ b/lib/reactions/wallet_connect.dart @@ -11,6 +11,7 @@ const List walletConnectCompatibleChains = [ WalletType.base, WalletType.arbitrum, WalletType.bsc, + WalletType.robinhood, WalletType.solana, ]; @@ -39,6 +40,7 @@ bool isEVMCompatibleChain(WalletType walletType) { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return true; default: return false; @@ -93,6 +95,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType, {int? chai return EVMChainId.arbitrum.chain(); case WalletType.bsc: return EVMChainId.bsc.chain(); + case WalletType.robinhood: + return EVMChainId.robinhood.chain(); case WalletType.solana: return SolanaChainId.mainnet.chain(); default: @@ -107,6 +111,7 @@ List getChainSupportedMethodsOnWalletType(WalletType walletType) { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return EVMSupportedMethods.values.map((e) => e.name).toList(); case WalletType.solana: return SolanaSupportedMethods.values.map((e) => e.name).toList(); diff --git a/lib/reactions/wallet_utils.dart b/lib/reactions/wallet_utils.dart index 57d11a4c8..b0d66fc00 100644 --- a/lib/reactions/wallet_utils.dart +++ b/lib/reactions/wallet_utils.dart @@ -7,6 +7,7 @@ bool isBIP39Wallet(WalletType walletType) { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: case WalletType.bitcoin: @@ -49,6 +50,7 @@ bool hasTokens(WalletType walletType) { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return true; default: return false; @@ -61,6 +63,7 @@ String tokenStandardFor(WalletType walletType) { case WalletType.polygon: case WalletType.arbitrum: case WalletType.base: + case WalletType.robinhood: return 'ERC-20'; case WalletType.bsc: return 'BEP-20'; diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index 61fa3f38a..8c7d78417 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -388,9 +388,12 @@ class _ContactListBodyState extends State { } Widget generateContactRaw(BuildContext context, ContactRecord contact, bool isLast) { - final image = contact.type == CryptoCurrency.baseEth - ? 'assets/new-ui/crypto_full_icons/base.svg' - : contact.type.iconPath; + const l2NativeChainIcons = { + CryptoCurrency.baseEth: "assets/new-ui/crypto_full_icons/base.svg", + CryptoCurrency.arbEth: "assets/new-ui/crypto_full_icons/arbitrum.svg", + CryptoCurrency.robEth: "assets/new-ui/crypto_full_icons/robinhood.svg", + }; + final image = l2NativeChainIcons[contact.type] ?? contact.type.iconPath; final currencyIcon = (image != null && image.isNotEmpty) ? CakeImageWidget(imageUrl: image, height: 24, width: 24) : const SizedBox(height: 24, width: 24); diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index fbdb05861..6025d91dc 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/setting_action_button.dart'; import 'package:cake_wallet/src/widgets/setting_actions.dart'; import 'package:flutter/material.dart'; +import "package:flutter_svg/svg.dart"; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -36,6 +37,7 @@ class MenuWidgetState extends State { this.baseIcon = Image.asset('assets/new-ui/crypto_full_icons/base.svg'), this.arbitrumIcon = Image.asset('assets/new-ui/crypto_full_icons/arbitrum.svg'), this.bscIcon = Image.asset('assets/new-ui/crypto_full_icons/bnb.svg'), + this.robinhoodIcon = SvgPicture.asset("assets/new-ui/crypto_full_icons/robinhood.svg"), this.solanaIcon = Image.asset('assets/new-ui/crypto_full_icons/solana.svg'), this.tronIcon = Image.asset('assets/new-ui/crypto_full_icons/tron.svg'), this.wowneroIcon = Image.asset('assets/new-ui/crypto_full_icons/wownero.svg'), @@ -67,6 +69,7 @@ class MenuWidgetState extends State { Image baseIcon; Image arbitrumIcon; Image bscIcon; + Widget robinhoodIcon; Image solanaIcon; Image tronIcon; Image wowneroIcon; @@ -261,6 +264,8 @@ class MenuWidgetState extends State { return arbitrumIcon; case WalletType.bsc: return bscIcon; + case WalletType.robinhood: + return robinhoodIcon; case WalletType.tron: return tronIcon; case WalletType.wownero: diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index ca9c46165..0a0ac9f7c 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -93,6 +93,14 @@ class ConnectionSyncPage extends BasePage { onChanged: (val) { _connectionSyncViewModel.setUseBaseScan(val); }), + if (_connectionSyncViewModel.canUseRobinhoodScan) + ListItemToggle( + keyValue: "can_use_robinhoodscan", + label: S.of(context).robinhoodscan_history, + value: _connectionSyncViewModel.useRobinhoodScan, + onChanged: (val) { + _connectionSyncViewModel.setUseRobinhoodScan(val); + }), if (_connectionSyncViewModel.canUseArbiScan) ListItemToggle( keyValue: "can_use_arbiscan", diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart index d13b99bcf..a6150dfbe 100644 --- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart @@ -6,6 +6,7 @@ enum EVMChainId { base, arbitrum, bsc, + robinhood, } extension EVMChainIdX on EVMChainId { @@ -24,6 +25,7 @@ extension EVMChainIdX on EVMChainId { EVMChainId.base => 8453, EVMChainId.arbitrum => 42161, EVMChainId.bsc => 56, + EVMChainId.robinhood => 4663, }; } diff --git a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart index 4d09213d7..53f446be0 100644 --- a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart +++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart @@ -18,6 +18,7 @@ class KeyServiceImpl implements WalletConnectKeyService { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.getPrivateKey(wallet); case WalletType.solana: return solana!.getPrivateKey(wallet); @@ -33,6 +34,7 @@ class KeyServiceImpl implements WalletConnectKeyService { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.getPublicKey(wallet); case WalletType.solana: return solana!.getPublicKey(wallet); @@ -52,6 +54,7 @@ class KeyServiceImpl implements WalletConnectKeyService { 'eip155:137', 'eip155:8453', 'eip155:42161', + "eip155:4663", ], privateKey: _getPrivateKeyForWallet(wallet), publicKey: _getPublicKeyForWallet(wallet), diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index ff9f41dc0..c0945ab1c 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -120,6 +120,7 @@ abstract class SettingsStoreBase with Store { required this.useBaseScan, required this.useArbiScan, required this.useBscScan, + required this.useRobinhoodScan, required this.usePolygonScan, required this.useTronGrid, required this.useMempoolFeeAPI, @@ -533,6 +534,11 @@ abstract class SettingsStoreBase with Store { reaction((_) => useBscScan, (bool useBscScan) => _sharedPreferences.setBool(PreferencesKey.useBscScan, useBscScan)); + reaction( + (_) => useRobinhoodScan, + (bool useRobinhoodScan) => + _sharedPreferences.setBool(PreferencesKey.useRobinhoodScan, useRobinhoodScan)); + reaction((_) => useTronGrid, (bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid)); @@ -978,6 +984,9 @@ abstract class SettingsStoreBase with Store { @observable bool useBscScan; + @observable + bool useRobinhoodScan; + @observable bool useTronGrid; @@ -1144,6 +1153,8 @@ abstract class SettingsStoreBase with Store { return PreferencesKey.currentArbitrumNodeIdKey; case 56: return PreferencesKey.currentBscNodeIdKey; + case 4663: + return PreferencesKey.currentRobinhoodNodeIdKey; default: // Default to Ethereum for unknown chainIds return PreferencesKey.currentEthereumNodeIdKey; @@ -1338,6 +1349,7 @@ abstract class SettingsStoreBase with Store { final useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true; final useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true; final useBscScan = sharedPreferences.getBool(PreferencesKey.useBscScan) ?? true; + final useRobinhoodScan = sharedPreferences.getBool(PreferencesKey.useRobinhoodScan) ?? true; final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true; final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true; final useBlinkProtection = sharedPreferences.getBool(PreferencesKey.useBlinkProtection) ?? true; @@ -1402,6 +1414,7 @@ abstract class SettingsStoreBase with Store { final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey); final arbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey); final bscNodeId = sharedPreferences.getInt(PreferencesKey.currentBscNodeIdKey); + final robinhoodNodeId = sharedPreferences.getInt(PreferencesKey.currentRobinhoodNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); @@ -1455,6 +1468,8 @@ abstract class SettingsStoreBase with Store { nodeSource.firstWhereOrNull((e) => e.uriRaw == zcashDefaultNodeUri); final bscNode = nodeSource.firstWhereOrNull((e) => e.id == bscNodeId) ?? nodeSource.firstWhereOrNull((e) => e.uriRaw == bscDefaultNodeUri); + final robinhoodNode = nodeSource.firstWhereOrNull((e) => e.id == robinhoodNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == robinhoodDefaultNodeUri); final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; @@ -1523,6 +1538,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.bsc] = bscNode; } + if (robinhoodNode != null) { + nodes[WalletType.robinhood] = robinhoodNode; + } + if (bitcoinCashElectrumServer != null) { nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; } @@ -1725,6 +1744,7 @@ abstract class SettingsStoreBase with Store { useBaseScan: useBaseScan, useArbiScan: useArbiScan, useBscScan: useBscScan, + useRobinhoodScan: useRobinhoodScan, useTronGrid: useTronGrid, useMempoolFeeAPI: useMempoolFeeAPI, useBlinkProtection: useBlinkProtection, @@ -1972,6 +1992,7 @@ abstract class SettingsStoreBase with Store { useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true; useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true; useBscScan = sharedPreferences.getBool(PreferencesKey.useBscScan) ?? true; + useRobinhoodScan = sharedPreferences.getBool(PreferencesKey.useRobinhoodScan) ?? true; useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true; useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true; useBlinkProtection = sharedPreferences.getBool(PreferencesKey.useBlinkProtection) ?? true; @@ -2014,6 +2035,7 @@ abstract class SettingsStoreBase with Store { final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey); final arbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey); final bscNodeId = sharedPreferences.getInt(PreferencesKey.currentBscNodeIdKey); + final robinhoodNodeId = sharedPreferences.getInt(PreferencesKey.currentRobinhoodNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); @@ -2031,6 +2053,7 @@ abstract class SettingsStoreBase with Store { final baseNode = await Node.get(baseNodeId ?? -1); final arbitrumNode = await Node.get(arbitrumNodeId ?? -1); final bscNode = await Node.get(bscNodeId ?? -1); + final robinhoodNode = await Node.get(robinhoodNodeId ?? -1); final bitcoinCashNode = await Node.get(bitcoinCashElectrumServerId ?? -1); final nanoNode = await Node.get(nanoNodeId ?? -1); final solanaNode = await Node.get(solanaNodeId ?? -1); @@ -2077,6 +2100,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.bsc] = bscNode; } + if (robinhoodNode != null) { + nodes[WalletType.robinhood] = robinhoodNode; + } + if (bitcoinCashNode != null) { nodes[WalletType.bitcoinCash] = bitcoinCashNode; } @@ -2230,6 +2257,7 @@ abstract class SettingsStoreBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: final chainId = evm!.getChainIdByWalletType(node.type); final preferenceKey = _getEVMNodePreferenceKey(chainId); await _sharedPreferences.setInt(preferenceKey, node.id); diff --git a/lib/utils/qr_util.dart b/lib/utils/qr_util.dart index f03937c32..ab43544a8 100644 --- a/lib/utils/qr_util.dart +++ b/lib/utils/qr_util.dart @@ -12,6 +12,8 @@ String getQrImage(WalletType type) { return 'assets/images/arbitrum_chain_QR.svg'; case WalletType.bsc: return 'assets/images/bnb_chain_QR.svg'; + case WalletType.robinhood: + return "assets/images/eth_chain_qr.svg"; case WalletType.solana: return 'assets/images/sol_chain_qr.svg'; case WalletType.tron: @@ -55,6 +57,8 @@ String getChainMonoImage(WalletType type) { return 'assets/images/arbitrum_chain_mono.svg'; case WalletType.bsc: return 'assets/images/bnb_chain_mono.svg'; + case WalletType.robinhood: + return "assets/images/robinhood_chain_mono.svg"; case WalletType.solana: return 'assets/images/sol_chain_mono.svg'; case WalletType.tron: diff --git a/lib/utils/token_utilities.dart b/lib/utils/token_utilities.dart index efb7b684d..748485ccf 100644 --- a/lib/utils/token_utilities.dart +++ b/lib/utils/token_utilities.dart @@ -2,7 +2,6 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/evm/evm.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/tron/tron.dart'; -import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/erc20_token.dart'; @@ -12,7 +11,6 @@ import "package:cw_core/utils/print_verbose.dart"; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:hive/hive.dart'; class TokenUtilities { static Future> loadAllUniqueEvmTokens() async { @@ -192,6 +190,7 @@ class TokenUtilities { WalletType.base => 8453, WalletType.arbitrum => 42161, WalletType.bsc => 56, + WalletType.robinhood => 4663, _ => 1, }; @@ -298,7 +297,12 @@ class TokenUtilities { // Only check EVM registry for currencies that might be EVM-related final isPotentialEVM = isNativeToken(currency) || (tag != null && - (tag == "ETH" || tag == "POL" || tag == "BASE" || tag == "ARB" || tag == "BSC")); + (tag == "ETH" || + tag == "POL" || + tag == "BASE" || + tag == "ARB" || + tag == "BSC" || + tag == "ROB")); if (isPotentialEVM) { // Try by tag first if available (e.g., 'POL', 'BASE', 'ARB') diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index f8f1ce1d7..03a67c385 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -56,6 +56,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: case WalletType.zcash: @@ -100,6 +101,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { WalletType.base, WalletType.arbitrum, WalletType.bsc, + WalletType.robinhood, WalletType.tron, WalletType.solana, WalletType.monero, diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index f464076b6..b39580096 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -219,6 +219,7 @@ abstract class BalanceViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: return S.current.xmr_full_balance; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 2cc5ce1d0..061a0d612 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1300,6 +1300,7 @@ abstract class DashboardViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.nano: case WalletType.banano: diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 5039c927b..777af604a 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -194,10 +194,10 @@ abstract class HomeSettingsViewModelBase with Store { return false; } - bool isPotentialScamViaMoralis = await _isPotentialScamTokenViaMoralis( - contractAddress, - getChainNameBasedOnWalletType(_balanceViewModel.wallet.type), - ); + final moralisChainName = evm!.getMoralisChainName(_balanceViewModel.wallet); + + final isPotentialScamViaMoralis = moralisChainName != null && + await _isPotentialScamTokenViaMoralis(contractAddress, moralisChainName); bool isUnverifiedContract = await _isContractUnverified( contractAddress, @@ -221,6 +221,7 @@ abstract class HomeSettingsViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: defaultTokenAddresses = evm!.getDefaultTokenContractAddresses(_balanceViewModel.wallet); break; case WalletType.solana: @@ -262,6 +263,7 @@ abstract class HomeSettingsViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: defaultTokenSymbols = evm!.getDefaultTokenSymbols(_balanceViewModel.wallet); break; case WalletType.solana: diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index f72ab6da4..a90c2394e 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -207,6 +207,7 @@ class TransactionListItem extends ActionListItem with Keyable { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: final asset = assetOfTransaction; final price = balanceViewModel.fiatConversionStore.prices[asset]; amount = calculateFiatAmountRaw( diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 6c95bf614..82e6c873a 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -277,7 +277,8 @@ abstract class ExchangeTradeViewModelBase with Store { _isTokenBelongingToWallet(CryptoCurrency.arbEth) || _isTokenBelongingToWallet(CryptoCurrency.trx) || _isTokenBelongingToWallet(CryptoCurrency.sol) || - _isTokenBelongingToWallet(CryptoCurrency.bnb); + _isTokenBelongingToWallet(CryptoCurrency.bnb) || + _isTokenBelongingToWallet(CryptoCurrency.robEth); if (!canSend) { return 'Wallet currency ${wallet.currency.title} does not match trade from currency ${tradeFrom.title} or is not a supported token for this wallet.'; @@ -346,6 +347,7 @@ abstract class ExchangeTradeViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return _createERC681URI(fromCurrency, inputAddress, amount); case WalletType.solana: return SolanaURI( diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 52510b4a9..0373f5e24 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -1547,6 +1547,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.bnb; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.robinhood: + depositCurrency = CryptoCurrency.robEth; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.solana: depositCurrency = CryptoCurrency.sol; receiveCurrency = CryptoCurrency.xmr; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 00fb961c1..23fc0117b 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -126,6 +126,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.banano: case WalletType.nano: diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index f1456b9b5..58225c441 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -35,6 +35,7 @@ class WalletRestoreFromQRCode { 'base-wallet': WalletType.base, 'arbitrum-wallet': WalletType.arbitrum, 'bsc-wallet': WalletType.bsc, + "robinhood-wallet": WalletType.robinhood, 'nano-wallet': WalletType.nano, 'nano_wallet': WalletType.nano, 'bitcoincash': WalletType.bitcoinCash, diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart index c9fb0b5d5..c062d1d9e 100644 --- a/lib/view_model/send/fees_view_model.dart +++ b/lib/view_model/send/fees_view_model.dart @@ -109,6 +109,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.solana: case WalletType.tron: case WalletType.arbitrum: + case WalletType.robinhood: case WalletType.zcash: return false; } @@ -128,8 +129,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor wallet.type != WalletType.banano && wallet.type != WalletType.solana && wallet.type != WalletType.tron && - wallet.chainId != - 42161; // Wallet type is generic for all EVM chains, so we need to check the chainId + wallet.chainId != 42161 && + wallet.chainId != 4663; @computed bool get isElectrumWallet => diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 618416b95..566b0ef5c 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -189,12 +189,14 @@ abstract class OutputBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: final isNative = [ CryptoCurrency.eth, CryptoCurrency.maticpoly, CryptoCurrency.baseEth, CryptoCurrency.arbEth, - CryptoCurrency.bnb + CryptoCurrency.bnb, + CryptoCurrency.robEth ].contains(cryptoCurrencyHandler()); final fee = isNative diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 5bed4c39f..07ab72b9b 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -293,6 +293,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.tron: case WalletType.solana: case WalletType.bitcoin: @@ -1273,6 +1274,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor WalletType.solana, WalletType.tron, WalletType.arbitrum, + WalletType.robinhood, WalletType.zcash, ].contains(wallet.type)) { throw Exception('Priority is null for wallet type: ${wallet.type}'); @@ -1311,6 +1313,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.createEVMTransactionCredentials( outputs, priority: priority, diff --git a/lib/view_model/settings/connection_sync_view_model.dart b/lib/view_model/settings/connection_sync_view_model.dart index 972aacc09..af47ecb61 100644 --- a/lib/view_model/settings/connection_sync_view_model.dart +++ b/lib/view_model/settings/connection_sync_view_model.dart @@ -205,6 +205,9 @@ abstract class ConnectionSyncViewModelBase with Store { @computed bool get useBaseScan => _settingsStore.useBaseScan; + @computed + bool get useRobinhoodScan => _settingsStore.useRobinhoodScan; + @computed bool get useArbiScan => _settingsStore.useArbiScan; @@ -223,6 +226,9 @@ abstract class ConnectionSyncViewModelBase with Store { @computed bool get canUseBaseScan => _wallet.chainId == 8453; + @computed + bool get canUseRobinhoodScan => _wallet.chainId == 4663; + @computed bool get canUseArbiScan => _wallet.chainId == 42161; @@ -271,6 +277,12 @@ abstract class ConnectionSyncViewModelBase with Store { evm!.updateScanProviderUsageState(_wallet, value); } + @action + void setUseRobinhoodScan(bool value) { + _settingsStore.useRobinhoodScan = value; + evm!.updateScanProviderUsageState(_wallet, value); + } + @action void setUseTronGrid(bool value) { _settingsStore.useTronGrid = value; diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 35fa34ea2..d6d70dc73 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -104,6 +104,7 @@ abstract class OtherSettingsViewModelBase with Store { WalletType.solana, WalletType.tron, WalletType.arbitrum, + WalletType.robinhood, ].contains(_wallet.type)); String getDisplayPriority(dynamic priority) { diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index df9c72721..df5012f3f 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -476,6 +476,8 @@ abstract class TransactionDetailsViewModelBase with Store { return "https://arbiscan.io/tx/${txId}"; case WalletType.bsc: return "https://bscscan.com/tx/${txId}"; + case WalletType.robinhood: + return "https://robinhoodchain.blockscout.com/tx/${txId}"; case WalletType.polygon: return "https://polygonscan.com/tx/${txId}"; case WalletType.nano: diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 42dbcd3ca..136a41235 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -562,6 +562,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo "assets/images/usdt_wallet_icon.svg", "assets/images/more_tokens.svg", ]; + case 4663: + return [ + "assets/new-ui/crypto_full_icons/robinhood.svg", + "assets/images/usdg_icon.png", + "assets/images/more_tokens.svg", + ]; default: return [ "assets/new-ui/crypto_full_icons/ethereum.svg", diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index ce10a269b..87e038f26 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -171,6 +171,7 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.solana: case WalletType.tron: items.addAll([ @@ -321,6 +322,8 @@ abstract class WalletKeysViewModelBase with Store { return 'arbitrum-wallet'; case WalletType.bsc: return 'bsc-wallet'; + case WalletType.robinhood: + return "robinhood-wallet"; case WalletType.solana: return 'solana-wallet'; case WalletType.tron: diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 4d50370f2..66006beb4 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -83,6 +83,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.createEVMNewWalletCredentials( name: name, password: walletPassword, diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 360cbf116..4f49c7b25 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -54,6 +54,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: case WalletType.decred: case WalletType.bitcoin: case WalletType.litecoin: @@ -97,6 +98,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { WalletType.base, WalletType.arbitrum, WalletType.bsc, + WalletType.robinhood, WalletType.nano, WalletType.banano, WalletType.solana, @@ -179,6 +181,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.createEVMRestoreWalletFromSeedCredentials( name: name, mnemonic: seed, @@ -288,6 +291,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: + case WalletType.robinhood: return evm!.createEVMRestoreWalletFromPrivateKey( name: name, password: password, diff --git a/lib/view_model/wallet_switcher_view_model.dart b/lib/view_model/wallet_switcher_view_model.dart index 4d66e5725..fad34f3b2 100644 --- a/lib/view_model/wallet_switcher_view_model.dart +++ b/lib/view_model/wallet_switcher_view_model.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/wallet_base.dart'; @@ -32,13 +31,6 @@ abstract class WalletSwitcherViewModelBase with Store { Future> getWallets(WalletType? walletType) async { final wiList = await WalletInfo.getAll(); if (walletType == null) return wiList; - - // For EVM-compatible wallet types, show all EVM-compatible wallets - // This allows users to switch between any EVM wallet regardless of the specific chain - if (isEVMCompatibleChain(walletType)) { - return wiList.where((wallet) => isEVMCompatibleChain(wallet.type)).toList(); - } - return wiList.where((wallet) => wallet.type == walletType).toList(); } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index af82a421b..71bf305a5 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -287,6 +287,7 @@ flutter: - assets/arbitrum_node_list.yml - assets/zcash_node_list.yml - assets/bsc_node_list.yml + - assets/robinhood_node_list.yml - assets/text/ - assets/faq/ - assets/animation/ diff --git a/res/pictures/balance_card_backgrounds/robinhood.svg b/res/pictures/balance_card_backgrounds/robinhood.svg new file mode 100644 index 000000000..8082ed778 --- /dev/null +++ b/res/pictures/balance_card_backgrounds/robinhood.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/pictures/balance_card_icons/robinhood.svg b/res/pictures/balance_card_icons/robinhood.svg new file mode 100644 index 000000000..7f56d1bda --- /dev/null +++ b/res/pictures/balance_card_icons/robinhood.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/card_icons/chain_icons/robinhood.svg b/res/pictures/card_icons/chain_icons/robinhood.svg new file mode 100644 index 000000000..2a0337d22 --- /dev/null +++ b/res/pictures/card_icons/chain_icons/robinhood.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/pictures/card_icons/og_icons/rob-og.svg b/res/pictures/card_icons/og_icons/rob-og.svg new file mode 100644 index 000000000..2a0337d22 --- /dev/null +++ b/res/pictures/card_icons/og_icons/rob-og.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/pictures/card_icons/outline_icons/rob-outline.svg b/res/pictures/card_icons/outline_icons/rob-outline.svg new file mode 100644 index 000000000..d4f523285 --- /dev/null +++ b/res/pictures/card_icons/outline_icons/rob-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/card_icons/symbol_icons/rob-symbol.svg b/res/pictures/card_icons/symbol_icons/rob-symbol.svg new file mode 100644 index 000000000..7f56d1bda --- /dev/null +++ b/res/pictures/card_icons/symbol_icons/rob-symbol.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/chain_badges/robinhood.svg b/res/pictures/chain_badges/robinhood.svg new file mode 100644 index 000000000..7f56d1bda --- /dev/null +++ b/res/pictures/chain_badges/robinhood.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/crypto_full_icons/robinhood.svg b/res/pictures/crypto_full_icons/robinhood.svg new file mode 100644 index 000000000..2a0337d22 --- /dev/null +++ b/res/pictures/crypto_full_icons/robinhood.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7f80e1f0b..e551b19d0 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -987,6 +987,7 @@ "return_address_desc": "في الحالة النادرة لفشل عملية المبادلة، يمكن لشركائنا ردّ دفعتك تلقائيًا إلى عنوان الاسترداد المُقدَّم.", "return_address_desc_external": "نظرًا لأنك اخترت Send From External، يُرجى تعيين عنوان الاسترداد يدويًا أدناه.", "robinhood_option_description": "اشترِ وحوّل فورًا باستخدام بطاقة الخصم أو حسابك المصرفي أو رصيد Robinhood. الولايات المتحدة الأمريكية فقط.", + "robinhoodscan_history": "تاريخ روبنهود", "rotate_address": "احصل على عنوان جديد", "router_no_route": "لم يتم تعريف مسار لـ ${name}", "salary": "الراتب", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index daec5e533..b29f2ac68 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -987,6 +987,7 @@ "return_address_desc": "В редкия случай на неуспешен суап, нашите партньори могат автоматично да възстановят плащането ви към предоставения адрес за възстановяване.", "return_address_desc_external": "Тъй като избрахте „Send From External“, моля, задайте ръчно адрес за възстановяване по‑долу.", "robinhood_option_description": "Купувайте и прехвърляйте незабавно с дебитна карта, банкова сметка или баланс в Robinhood. Само за САЩ.", + "robinhoodscan_history": "История на Робинхуд", "rotate_address": "Вземете нов адрес", "router_no_route": "Няма дефиниран маршрут за ${name}", "salary": "Заплата", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index c82c45a36..845d1d43d 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -987,6 +987,7 @@ "return_address_desc": "Ve vzácném případě neúspěšného swapu mohou naši partneři automaticky vrátit vaši platbu na zadanou refund adresu.", "return_address_desc_external": "Protože jste zvolili možnost Odeslat z externího zdroje, nastavte níže ručně adresu pro vrácení.", "robinhood_option_description": "Nakupujte a převádějte okamžitě pomocí debetní karty, bankovního účtu nebo zůstatku Robinhood. Pouze v USA.", + "robinhoodscan_history": "Historie Robinhood", "rotate_address": "Získejte novou adresu", "router_no_route": "Pro ${name} není definována žádná trasa", "salary": "Plat", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index fcd55f654..6a8dd526a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -990,6 +990,7 @@ "return_address_desc": "Im seltenen Fall eines fehlgeschlagenen Swaps können unsere Partner Ihre Zahlung automatisch an die angegebene Rückerstattungsadresse erstatten.", "return_address_desc_external": "Da Sie „Von extern senden“ ausgewählt haben, legen Sie bitte unten manuell eine Erstattungsadresse fest.", "robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrer Debitkarte, Ihrem Bankkonto oder Ihrem Robinhood-Guthaben. Nur USA.", + "robinhoodscan_history": "Robinhood-Geschichte", "rotate_address": "Neue Adresse abrufen", "router_no_route": "Keine Route für ${name} definiert", "salary": "Gehalt", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index c9ec1b7df..6cec6c229 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -997,6 +997,7 @@ "return_address_desc": "In the rare case of a failed swap, our partners can automatically refund your payment to the provided refund address.", "return_address_desc_external": "Since you chose Send From External, please set a refund address manually below.", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", + "robinhoodscan_history": "Robinhood History", "rotate_address": "Get new address", "router_no_route": "No route defined for ${name}", "salary": "Salary", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 62f5a930a..ea6decadd 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -988,6 +988,7 @@ "return_address_desc": "En el caso poco frecuente de que un intercambio falle, nuestros socios pueden reembolsar automáticamente su pago a la dirección de reembolso proporcionada.", "return_address_desc_external": "Como elegiste Enviar desde externo, establece manualmente una dirección de reembolso a continuación.", "robinhood_option_description": "Compra y transfiere al instante con tu tarjeta de débito, cuenta bancaria o saldo de Robinhood. Solo en EE. UU.", + "robinhoodscan_history": "Historia de Robinhood", "rotate_address": "Obtener nueva dirección", "router_no_route": "No se ha definido ninguna ruta para ${name}", "salary": "Salario", diff --git a/res/values/strings_fa.arb b/res/values/strings_fa.arb index 9e0ec0245..cac2a0319 100644 --- a/res/values/strings_fa.arb +++ b/res/values/strings_fa.arb @@ -987,6 +987,7 @@ "return_address_desc": "در موارد نادرِ ناموفق بودن سواپ، شرکای ما می‌توانند پرداخت شما را به‌صورت خودکار به آدرس بازپرداختِ ارائه‌شده بازگردانند.", "return_address_desc_external": "از آنجایی که «ارسال از خارجی» را انتخاب کرده‌اید، لطفاً آدرس بازپرداخت را به‌صورت دستی در زیر تنظیم کنید.", "robinhood_option_description": "با استفاده از کارت نقدی، حساب بانکی یا موجودی Robinhood خود، فوراً خرید و انتقال دهید. فقط در ایالات متحده.", + "robinhoodscan_history": "تاریخچه رابینهود", "rotate_address": "آدرس جدید دریافت کنید", "router_no_route": "هیچ مسیری برای ${name} تعریف نشده است", "salary": "حقوق", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2e44f9036..031c1df8f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -987,6 +987,7 @@ "return_address_desc": "Dans le cas rare d'un swap échoué, nos partenaires peuvent automatiquement rembourser votre paiement à l'adresse de remboursement fournie.", "return_address_desc_external": "Puisque vous avez choisi Envoyer depuis une source externe, veuillez définir manuellement une adresse de remboursement ci-dessous.", "robinhood_option_description": "Achetez et transférez instantanément avec votre carte de débit, votre compte bancaire ou votre solde Robinhood. Réservé aux États-Unis.", + "robinhoodscan_history": "Histoire de Robinhood", "rotate_address": "Obtenir une nouvelle adresse", "router_no_route": "Aucune route définie pour ${name}", "salary": "Salaire", diff --git a/res/values/strings_gn.arb b/res/values/strings_gn.arb index 0c7ff9f11..277a7d38c 100644 --- a/res/values/strings_gn.arb +++ b/res/values/strings_gn.arb @@ -990,6 +990,7 @@ "return_address_desc": "Pe káso sa’ietéva peteĩ swap ojavy hag̃uarã, ore irũnguéra ikatu ombojevy hag̃ua automáticamente nde pago pe dirección de reembolso oñeme’ẽvape.", "return_address_desc_external": "Reiporavo rupi Enviar de Externo, ikatúpiko emoĩ peteĩ dirección de reembolso pópe ko’ápe.", "robinhood_option_description": "Ejogua ha embohasa pyaʼe reipuruvo nde tarjeta débito, nde cuenta bancaria térã nde saldo Robinhood. Estados Unidos-pe añoite.", + "robinhoodscan_history": "Robinhood Tembiasakue", "rotate_address": "Ehupyty dirección pyahu", "router_no_route": "Ndaipóri tape jeiporavo hag̃ua ${name} hag̃ua", "salary": "Tembiapo repy", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 981818a46..ecd8851f4 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -989,6 +989,7 @@ "return_address_desc": "A cikin yanayin da ba kasafai ba na musanya da ta gaza, abokan hulɗarmu za su iya mayar da kuɗin biyan ku ta atomatik zuwa adireshin mayar da kuɗi da aka bayar.", "return_address_desc_external": "Tun da ka zaɓi Aika Daga Waje, don Allah ka saita adireshin mayar da kuɗi da hannu a ƙasa.", "robinhood_option_description": "Sayi kuma canja wurin nan take ta amfani da katin debit, asusun banki, ko ma'aunin Robinhood. Amurka kawai.", + "robinhoodscan_history": "Tarihin Robinhood", "rotate_address": "Samu sabon adireshin", "router_no_route": "Ba a ayyana wata hanya don ${name} ba", "salary": "Albashi", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 86624c9b0..55370866c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -989,6 +989,7 @@ "return_address_desc": "स्वैप विफल होने की दुर्लभ स्थिति में, हमारे साझेदार आपके भुगतान को दिए गए रिफंड पते पर स्वचालित रूप से वापस कर सकते हैं।", "return_address_desc_external": "चूंकि आपने बाहरी स्रोत से भेजें चुना है, कृपया नीचे मैन्युअल रूप से रिफंड पता सेट करें।", "robinhood_option_description": "अपने डेबिट कार्ड, बैंक खाते या Robinhood बैलेंस का उपयोग करके तुरंत खरीदें और ट्रांसफर करें। केवल USA।", + "robinhoodscan_history": "रॉबिनहुड इतिहास", "rotate_address": "नया पता प्राप्त करें", "router_no_route": "${name} के लिए कोई मार्ग निर्धारित नहीं है", "salary": "वेतन", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 76d627115..d229d6f20 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -987,6 +987,7 @@ "return_address_desc": "U rijetkom slučaju neuspjele zamjene, naši partneri mogu automatski vratiti vašu uplatu na navedenu adresu za povrat.", "return_address_desc_external": "Budući da ste odabrali Slanje s vanjskog, u nastavku ručno postavite adresu za povrat.", "robinhood_option_description": "Kupite i prenesite odmah pomoću svoje debitne kartice, bankovnog računa ili Robinhood salda. Samo za SAD.", + "robinhoodscan_history": "Povijest Robinhooda", "rotate_address": "Nađi novu adresu", "router_no_route": "Nije definirana ruta za ${name}", "salary": "Plaća", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 21dde734e..3d76ae42a 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -987,6 +987,7 @@ "return_address_desc": "Փոխանակման ձախողման հազվագյուտ դեպքում մեր գործընկերները կարող են ավտոմատ կերպով վերադարձնել ձեր վճարումը տրամադրված վերադարձման հասցեին։", "return_address_desc_external": "Քանի որ դուք ընտրել եք «Ուղարկել արտաքինից», խնդրում ենք ստորև ձեռքով նշել վերադարձի հասցեն:", "robinhood_option_description": "Գնեք և փոխանցեք ակնթարթորեն՝ օգտագործելով ձեր դեբետային քարտը, բանկային հաշիվը կամ Robinhood-ի մնացորդը։ Միայն ԱՄՆ-ում։", + "robinhoodscan_history": "Ռոբինհուդի պատմություն", "rotate_address": "Ստացեք նոր հասցե", "router_no_route": "${name}-ի համար երթուղի սահմանված չէ", "salary": "Աշխատավարձ", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 6574a60cb..be9f4f2bf 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -990,6 +990,7 @@ "return_address_desc": "Dalam kasus swap gagal yang jarang terjadi, mitra kami dapat secara otomatis mengembalikan pembayaran Anda ke alamat refund yang diberikan.", "return_address_desc_external": "Karena Anda memilih Kirim dari Eksternal, harap tetapkan alamat pengembalian secara manual di bawah.", "robinhood_option_description": "Beli dan transfer secara instan menggunakan kartu debit, rekening bank, atau saldo Robinhood Anda. Hanya untuk AS.", + "robinhoodscan_history": "Sejarah Robinhood", "rotate_address": "Dapatkan alamat baru", "router_no_route": "Tidak ada rute yang ditentukan untuk ${name}", "salary": "Gaji", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 2d7b7262f..a44cba1b6 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -988,6 +988,7 @@ "return_address_desc": "Nel raro caso di uno swap non riuscito, i nostri partner possono rimborsare automaticamente il pagamento all'indirizzo di rimborso fornito.", "return_address_desc_external": "Poiché hai scelto Invia da esterno, imposta manualmente un indirizzo di rimborso qui sotto.", "robinhood_option_description": "Acquista e trasferisci istantaneamente utilizzando la tua carta di debito, il tuo conto bancario o il saldo Robinhood. Solo negli USA.", + "robinhoodscan_history": "Storia di Robinhood", "rotate_address": "Ottieni un nuovo indirizzo", "router_no_route": "Nessuna rotta definita per ${name}", "salary": "Stipendio", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 65aba10dc..3aa12c694 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -988,6 +988,7 @@ "return_address_desc": "まれにスワップが失敗した場合、当社のパートナーは指定された返金先アドレスへお支払いを自動的に返金できます。", "return_address_desc_external": "「外部から送信」を選択したため、以下で返金先アドレスを手動で設定してください。", "robinhood_option_description": "デビットカード、銀行口座、またはRobinhood残高を使用して、即座に購入して送金できます。米国のみ。", + "robinhoodscan_history": "ロビンフッドの歴史", "rotate_address": "新しい住所を取得する", "router_no_route": "${name} のルートが定義されていません", "salary": "給与", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index ab25aea5d..a02fb214f 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -987,6 +987,7 @@ "return_address_desc": "드물게 스왑이 실패하는 경우, 파트너사가 제공된 환불 주소로 결제 금액을 자동으로 환불할 수 있습니다.", "return_address_desc_external": "외부에서 보내기를 선택하셨으므로 아래에서 환불 주소를 수동으로 설정해 주세요.", "robinhood_option_description": "직불 카드, 은행 계좌 또는 Robinhood 잔액을 사용하여 즉시 구매하고 이체할 수 있습니다. 미국 전용.", + "robinhoodscan_history": "로빈후드의 역사", "rotate_address": "새 주소 받기", "router_no_route": "${name}에 대해 정의된 경로가 없습니다", "salary": "급여", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 91e541138..57a35d5b1 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -987,6 +987,7 @@ "return_address_desc": "လဲလှယ်မှု မအောင်မြင်သည့် ရှားပါးသော အခြေအနေတွင်၊ ကျွန်ုပ်တို့၏ လုပ်ဖော်ကိုင်ဖက်များသည် သင့်ငွေပေးချေမှုကို ပေးထားသော ပြန်အမ်းလိပ်စာသို့ အလိုအလျောက် ပြန်အမ်းပေးနိုင်ပါသည်။", "return_address_desc_external": "Send From External ကို သင်ရွေးချယ်ထားသောကြောင့်၊ ကျေးဇူးပြု၍ အောက်တွင် ပြန်အမ်းရန် လိပ်စာကို ကိုယ်တိုင် သတ်မှတ်ပါ။", "robinhood_option_description": "သင်၏ ဒက်ဘစ်ကဒ်၊ ဘဏ်အကောင့် သို့မဟုတ် Robinhood လက်ကျန်ငွေကို အသုံးပြုပြီး ချက်ချင်း ဝယ်ယူပြီး လွှဲပြောင်းနိုင်သည်။ USA အတွက်သာ။", + "robinhoodscan_history": "Robinhood သမိုင်း", "rotate_address": "လိပ်စာအသစ်ရယူပါ။", "router_no_route": "${name} အတွက် သတ်မှတ်ထားသော လမ်းကြောင်း မရှိပါ", "salary": "လစာ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index e2ed5ea04..b18b186a7 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -987,6 +987,7 @@ "return_address_desc": "In het zeldzame geval dat de swap mislukt, kunnen onze partners je betaling automatisch terugstorten naar het opgegeven terugbetalingsadres.", "return_address_desc_external": "Omdat je voor Verzenden vanaf extern hebt gekozen, stel hieronder handmatig een terugbetalingsadres in.", "robinhood_option_description": "Koop en zet direct over met je bankpas, bankrekening of Robinhood-saldo. Alleen in de VS.", + "robinhoodscan_history": "Robinhood-geschiedenis", "rotate_address": "Nieuw adres krijgen", "router_no_route": "Geen route gedefinieerd voor ${name}", "salary": "Salaris", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index d874155be..a5a99774e 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -987,6 +987,7 @@ "return_address_desc": "W rzadkich przypadkach nieudanej wymiany nasi partnerzy mogą automatycznie zwrócić Twoją płatność na podany adres zwrotu środków.", "return_address_desc_external": "Ponieważ wybrałeś opcję Wyślij z zewnątrz, ustaw poniżej ręcznie adres zwrotu środków.", "robinhood_option_description": "Kup i natychmiast przelej, używając karty debetowej, konta bankowego lub salda Robinhood. Tylko USA.", + "robinhoodscan_history": "Historia Robinhooda", "rotate_address": "Uzyskaj nowy adres", "router_no_route": "Nie zdefiniowano ścieżki dla ${name}", "salary": "Wynagrodzenie", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 52fd4d042..c314e85c2 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -989,6 +989,7 @@ "return_address_desc": "No caso raro de falha na troca, nossos parceiros podem reembolsar automaticamente seu pagamento para o endereço de reembolso fornecido.", "return_address_desc_external": "Como você escolheu Enviar de Externo, defina manualmente abaixo um endereço de reembolso.", "robinhood_option_description": "Compre e transfira instantaneamente usando seu cartão de débito, conta bancária ou saldo da Robinhood. Apenas nos EUA.", + "robinhoodscan_history": "História Robinhood", "rotate_address": "Obter novo endereço", "router_no_route": "Nenhuma rota definida para ${name}", "salary": "Salário", diff --git a/res/values/strings_pt_BR.arb b/res/values/strings_pt_BR.arb index d27391613..5c8c76da9 100644 --- a/res/values/strings_pt_BR.arb +++ b/res/values/strings_pt_BR.arb @@ -993,6 +993,7 @@ "return_address_desc": "No caso raro de uma falha no swap, nossos parceiros podem reembolsar automaticamente seu pagamento para o endereço de reembolso fornecido.", "return_address_desc_external": "Como você escolheu Enviar de Externo, defina um endereço de reembolso manualmente abaixo.", "robinhood_option_description": "Compre e transfira instantaneamente usando seu cartão de débito, conta bancária ou saldo da Robinhood. Apenas nos EUA.", + "robinhoodscan_history": "História Robinhood", "rotate_address": "Obter novo endereço", "router_no_route": "Nenhuma rota definida para ${name}", "salary": "Salário", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 8d8d39d54..415cf3272 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -988,6 +988,7 @@ "return_address_desc": "В редком случае неудачного обмена наши партнеры могут автоматически вернуть ваш платеж на указанный адрес для возврата.", "return_address_desc_external": "Поскольку вы выбрали вариант «Отправить из внешнего источника», укажите ниже адрес для возврата вручную.", "robinhood_option_description": "Покупайте и переводите мгновенно, используя дебетовую карту, банковский счет или баланс Robinhood. Только для США.", + "robinhoodscan_history": "История Робинхуда", "rotate_address": "Получить новый адрес", "router_no_route": "Маршрут для ${name} не определён", "salary": "Зарплата", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 25f7c0ce2..837ded235 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -987,6 +987,7 @@ "return_address_desc": "ในกรณีที่การสวอปล้มเหลวซึ่งเกิดขึ้นไม่บ่อยนัก พันธมิตรของเราสามารถคืนเงินการชำระเงินของคุณโดยอัตโนมัติไปยังที่อยู่สำหรับการคืนเงินที่ระบุไว้", "return_address_desc_external": "เนื่องจากคุณเลือกส่งจากภายนอก โปรดตั้งค่าที่อยู่สำหรับการคืนเงินด้วยตนเองด้านล่าง", "robinhood_option_description": "ซื้อและโอนทันทีโดยใช้บัตรเดบิต บัญชีธนาคาร หรือยอดคงเหลือ Robinhood ใช้ได้เฉพาะในสหรัฐอเมริกาเท่านั้น", + "robinhoodscan_history": "ประวัติศาสตร์โรบินฮู้ด", "rotate_address": "รับที่อยู่ใหม่", "router_no_route": "ไม่มีเส้นทางที่กำหนดไว้สำหรับ ${name}", "salary": "เงินเดือน", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d624aafbb..e6a2a3208 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -987,6 +987,7 @@ "return_address_desc": "Sa bihirang pagkakataon ng nabigong swap, maaaring awtomatikong i-refund ng aming mga kasosyo ang iyong bayad sa ibinigay na refund address.", "return_address_desc_external": "Dahil pinili mo ang Send From External, pakitakda nang manu-mano ang refund address sa ibaba.", "robinhood_option_description": "Bumili at maglipat kaagad gamit ang iyong debit card, bank account, o balanse sa Robinhood. USA lang.", + "robinhoodscan_history": "Kasaysayan ng Robinhood", "rotate_address": "Kumuha ng bagong address", "router_no_route": "Walang rutang tinukoy para sa ${name}", "salary": "Suweldo", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index c7adc0d02..543eb97a9 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -987,6 +987,7 @@ "return_address_desc": "Nadiren de olsa başarısız bir takas durumunda, ortaklarımız ödemenizi sağlanan iade adresine otomatik olarak geri gönderebilir.", "return_address_desc_external": "\"Dışarıdan Gönder\" seçeneğini seçtiğiniz için lütfen aşağıda bir iade adresini manuel olarak belirleyin.", "robinhood_option_description": "Banka kartınızı, banka hesabınızı veya Robinhood bakiyenizi kullanarak anında satın alın ve aktarın. Yalnızca ABD'de.", + "robinhoodscan_history": "Robinhood'un Tarihi", "rotate_address": "Yeni adres al", "router_no_route": "${name} için rota tanımlanmadı", "salary": "Maaş", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 268bd88bb..2ea9c2994 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -988,6 +988,7 @@ "return_address_desc": "У рідкісних випадках невдалого обміну наші партнери можуть автоматично повернути ваш платіж на вказану адресу для повернення коштів.", "return_address_desc_external": "Оскільки ви вибрали «Надіслати із зовнішнього джерела», будь ласка, вручну вкажіть нижче адресу для повернення коштів.", "robinhood_option_description": "Купуйте та переказуйте миттєво за допомогою дебетової картки, банківського рахунку або балансу Robinhood. Тільки для США.", + "robinhoodscan_history": "Історія Робінхуд", "rotate_address": "Отримати нову адресу", "router_no_route": "Маршрут для ${name} не визначено", "salary": "Зарплата", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 921c87b1f..1ac8a594d 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -989,6 +989,7 @@ "return_address_desc": "سواپ ناکام ہونے کی نایاب صورت میں، ہمارے شراکت دار آپ کی ادائیگی کو فراہم کردہ ریفنڈ ایڈریس پر خودکار طور پر واپس کر سکتے ہیں۔", "return_address_desc_external": "چونکہ آپ نے بیرونی ذریعے سے بھیجنے کا انتخاب کیا ہے، براہِ کرم نیچے دستی طور پر ریفنڈ ایڈریس سیٹ کریں۔", "robinhood_option_description": "اپنے ڈیبٹ کارڈ، بینک اکاؤنٹ یا Robinhood بیلنس کا استعمال کرتے ہوئے فوری طور پر خریدیں اور منتقل کریں۔ صرف USA کے لیے۔", + "robinhoodscan_history": "رابن ہڈ ہسٹری", "rotate_address": "نیا پتہ حاصل کریں۔", "router_no_route": "${name} کے لیے کوئی روٹ متعین نہیں ہے", "salary": "تنخواہ", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index d060f1fb3..c219bfd1c 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -988,6 +988,7 @@ "return_address_desc": "Trong trường hợp hiếm hoi hoán đổi thất bại, các đối tác của chúng tôi có thể tự động hoàn tiền thanh toán của bạn về địa chỉ hoàn tiền đã cung cấp.", "return_address_desc_external": "Vì bạn đã chọn Gửi từ bên ngoài, vui lòng đặt thủ công địa chỉ hoàn tiền bên dưới.", "robinhood_option_description": "Mua và chuyển ngay lập tức bằng thẻ ghi nợ, tài khoản ngân hàng hoặc số dư Robinhood của bạn. Chỉ dành cho Hoa Kỳ.", + "robinhoodscan_history": "Lịch sử Robinhood", "rotate_address": "Nhận địa chỉ mới", "router_no_route": "Không có tuyến đường nào được xác định cho ${name}", "salary": "Lương", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 5b97c28a1..46bd63c05 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -988,6 +988,7 @@ "return_address_desc": "Nínú ọ̀ràn tí ó ṣọ̀wọ́n tí swap bá kùnà, àwọn alábàáṣiṣẹ́pọ̀ wa lè dá ìsanwó rẹ padà laifọwọ́yì sí àdírẹ́sì agbapada tí a pèsè.", "return_address_desc_external": "Nítorí pé o ti yan Firanṣẹ Látì Ita, jọwọ ṣètò àdírẹ́sì ìpadà owó pẹ̀lú ọwọ́ ní isalẹ.", "robinhood_option_description": "Ra ki o gbe lọ lẹsẹkẹsẹ ní lílo kaadi débítì rẹ, àkàǹtì banki, tàbí ìwọ̀ntúnwọ̀nsì Robinhood. USA nìkan.", + "robinhoodscan_history": "Robinhood History", "rotate_address": "Gba adirẹsi titun", "router_no_route": "Kò sí ọ̀nà tí a ṣàlàyé fún ${name}", "salary": "Owo-oṣù", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 6839eb08b..7df890309 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -987,6 +987,7 @@ "return_address_desc": "在极少数情况下兑换失败,我们的合作伙伴可以自动将您的付款退还至您提供的退款地址。", "return_address_desc_external": "由于您选择“从外部发送”,请在下方手动设置退款地址。", "robinhood_option_description": "使用您的借记卡、银行账户或 Robinhood 余额即可即时购买并转账。仅限美国。", + "robinhoodscan_history": "罗宾汉历史", "rotate_address": "获取新地址", "router_no_route": "未为 ${name} 定义路由", "salary": "薪资", diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index 9cf8976d6..a71284b3f 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum --bsc" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum --bsc --robinhood" ;; esac diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index 70577e4da..007ed0d9a 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -31,7 +31,7 @@ case $APP_IOS_TYPE in ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum --bsc" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum --bsc --robinhood" ;; esac diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh index 050bee6c7..083914782 100755 --- a/scripts/linux/app_config.sh +++ b/scripts/linux/app_config.sh @@ -13,7 +13,7 @@ CONFIG_ARGS="" case $APP_LINUX_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --bsc --excludeFlutterSecureStorage";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --bsc --robinhood --excludeFlutterSecureStorage";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 64ae2dd96..d274de7ae 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --bsc";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --bsc --robinhood";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index 7c7bc8e98..56a6660b0 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -35,8 +35,9 @@ Future main(List args) async { final hasBase = args.contains('${prefix}base'); final hasArbitrum = args.contains('${prefix}arbitrum'); final hasBsc = args.contains('${prefix}bsc'); + final hasRobinhood = args.contains("${prefix}robinhood"); final hasZcash = args.contains('${prefix}zcash'); - final hasEVM = hasEthereum || hasPolygon || hasBase || hasArbitrum || hasBsc; + final hasEVM = hasEthereum || hasPolygon || hasBase || hasArbitrum || hasBsc || hasRobinhood; final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); await generateBitcoin(hasBitcoin); @@ -71,6 +72,7 @@ Future main(List args) async { hasBase: hasBase, hasArbitrum: hasArbitrum, hasBsc: hasBsc, + hasRobinhood: hasRobinhood, hasZcash: hasZcash, ); await generateWalletTypes( @@ -90,6 +92,7 @@ Future main(List args) async { hasBase: hasBase, hasArbitrum: hasArbitrum, hasBsc: hasBsc, + hasRobinhood: hasRobinhood, hasZcash: hasZcash, ); await injectSecureStorage(!excludeFlutterSecureStorage); @@ -1599,6 +1602,7 @@ abstract class EVM { WalletType? getWalletTypeByChainId(int chainId); String getChainNameByChainId(int chainId); String getTokenNameByChainId(int chainId); + String? getMoralisChainName(WalletBase wallet); // Chain selection methods List getAllChains(); ChainInfo? getCurrentChain(WalletBase wallet); @@ -1849,6 +1853,7 @@ Future generatePubspec({ required bool hasBase, required bool hasArbitrum, required bool hasBsc, + required bool hasRobinhood, required bool hasZcash, }) async { const cwCore = """ @@ -1960,7 +1965,7 @@ Future generatePubspec({ output += '\n$flutterSecureStorage\n'; } - if (hasEthereum || hasPolygon || hasBase || hasArbitrum || hasBsc) { + if (hasEthereum || hasPolygon || hasBase || hasArbitrum || hasBsc || hasRobinhood) { output += '\n$cwEVM'; } @@ -2009,6 +2014,7 @@ Future generateWalletTypes({ required bool hasBase, required bool hasArbitrum, required bool hasBsc, + required bool hasRobinhood, required bool hasZcash, }) async { final walletTypesFile = File(walletTypesPath); @@ -2037,6 +2043,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.bsc,\n'; } + if (hasRobinhood) { + outputContent += "\tWalletType.robinhood,\n"; + } + if (hasSolana) { outputContent += '\tWalletType.solana,\n'; } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 6128637df..c3cadec4b 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -112,6 +112,7 @@ class SecretKey { SecretKey('moralisApiKey', () => ''), SecretKey('nowNodesApiKey', () => ''), SecretKey('blinkApiKey', () => ''), + SecretKey('alchemyApiKey', () => ''), ]; static final solanaSecrets = [