Add Robinhood Chain (#3398)
What changed, and why it matters
This commit adds support for a new blockchain, "Robinhood Chain" (chain ID 4663), to the Cake Wallet app. It is a large feature patch that wires the new chain into wallet creation, sending, receiving, exchange providers, transaction history, settings, and UI assets. There is no direct evidence in the commit of a security vulnerability, but the size and breadth of the change introduce ordinary implementation risks typical of adding a new EVM chain.
Treat this as a routine but sizable feature commit. Reviewers should focus on the RobinhoodClient transaction-signing path, the new Alchemy secret handling, the default node list trust assumptions, and the Moralis-disabled token-discovery fallback to ensure users cannot be tricked into accepting malicious token metadata. No immediate security action is indicated from the diff alone.
Security signals we found
New EVM chain integration with custom transaction signing path (RobinhoodClient forces gasPrice instead of EIP-1559)
New third-party RPC endpoints added to default node list (PublicNode, NOWNodes, Robinhood, Alchemy)
New API secret (ALCHEMY_API_KEY) written into generated secrets file in CI workflows
Moralis token discovery explicitly disabled for Robinhood Chain (getMoralisChainName returns null for chain 4663)
Token metadata fallback now reads directly from RPC node when Moralis is unavailable
Large cross-cutting change touching 143 files with +1214/-159 lines
Evidence from the diff
The commit introduces WalletType.robinhood / CryptoCurrency.robEth across the core, EVM, Solana, exchange, settings, and view-model layers. Key technical changes include: a new RobinhoodClient (EVM chain ID 4663) that forces legacy gasPrice-style transactions; default RPC nodes including PublicNode, NOWNodes, Robinhood, and Alchemy; a new Alchemy API key secret injected in CI; Robinhood-specific ERC-20 token list (USDG, tokenized stocks); Moralis chain-name mapping that returns null for Robinhood, causing token discovery to be skipped; Etherscan-like history toggle via Blockscout; and WalletConnect/URL-scheme/deep-link registration. The patch also refactors some shared EVM code (e.g., getErc20Token now falls back to on-node metadata when Moralis is unavailable, and SwapTrade provider now validates supported pairs).
Changed components
cw_evm (EVM chain client, registry, wallet, default tokens, utilities)cw_core (crypto_currency, wallet_type, node lists, ERC-20 token model)cw_solana (token icon/history handling)lib/exchange (ChangeNow, Near Intents, SideShift, SwapTrade)lib/view_model (send, exchange, settings, dashboard, restore, wallet switcher)CI workflows (secret injection for Alchemy API key)Android/iOS manifests and URL schemesInspect captured patch +1214 / −159
### .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
### .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
### .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
### android/app/src/main/AndroidManifestBase.xml
@@ -117,6 +117,9 @@
<data android:scheme="bsc" />
<data android:scheme="bsc-wallet" />
<data android:scheme="bsc_wallet" />
+ <data android:scheme="robinhood" />
+ <data android:scheme="robinhood-wallet" />
+ <data android:scheme="robinhood_wallet" />
<data android:scheme="zcash" />
<data android:scheme="zcash-wallet" />
<data android:scheme="lightning" />
### assets/images/robinhood_chain_mono.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#000000" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### assets/images/stocks/amd.png
[binary or diff unavailable]
### assets/images/stocks/asml.png
[binary or diff unavailable]
### assets/images/stocks/baba.png
[binary or diff unavailable]
### assets/images/stocks/cost.png
[binary or diff unavailable]
### assets/images/stocks/ddog.png
[binary or diff unavailable]
### assets/images/stocks/dell.png
[binary or diff unavailable]
### assets/images/stocks/f.png
[binary or diff unavailable]
### assets/images/stocks/intu.png
[binary or diff unavailable]
### assets/images/stocks/lulu.png
[binary or diff unavailable]
### assets/images/stocks/mu.png
[binary or diff unavailable]
### assets/images/stocks/nu.png
[binary or diff unavailable]
### assets/images/stocks/qcom.png
[binary or diff unavailable]
### assets/images/stocks/rivn.png
[binary or diff unavailable]
### assets/images/stocks/shop.png
[binary or diff unavailable]
### assets/images/stocks/sofi.png
[binary or diff unavailable]
### assets/images/stocks/tsm.png
[binary or diff unavailable]
### assets/images/stocks/ttwo.png
[binary or diff unavailable]
### assets/images/stocks/ups.png
[binary or diff unavailable]
### assets/images/stocks/wday.png
[binary or diff unavailable]
### assets/images/usdg_icon.png
[binary or diff unavailable]
### 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"
### 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%
### 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>[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>[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<CryptoCurrency, CardDesign> specialDesignsForCurrencies = {
@@ -474,6 +489,7 @@ class CardDesign {
CryptoCurrency.arbEth: arbitrumSpecial,
CryptoCurrency.zec: zecSpecial,
CryptoCurrency.bnb: bnbSpecial,
+ CryptoCurrency.robEth: robinhoodSpecial,
};
static Map<Gradient, CardColorCombination> preferredColorCombinations = {
### cw_core/lib/crypto_currency.dart
@@ -143,6 +143,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.arbEth,
CryptoCurrency.usdcArb,
CryptoCurrency.usdtArb,
+ CryptoCurrency.robEth,
];
static const havenCurrencies = [
@@ -974,6 +975,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> 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<int, CryptoCurrency> _rawCurrencyMap = [
...all,
@@ -999,7 +1010,14 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
...zcashCurrencies
].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (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<int> with Serializable<int> implemen
'base': baseEth,
'arbitrum': arbEth,
'bsc': bnb,
+ "rob": robEth,
+ "robinhood": robEth,
};
static CryptoCurrency deserialize({required int raw}) {
### 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:
### cw_core/lib/erc20_token.dart
@@ -15,6 +15,7 @@ class Erc20Token extends CryptoCurrency {
this.id = 0,
this.walletName,
this.chainId,
+ Set<String> 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<String, Object?> map)
### 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:
### 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:
### cw_core/lib/node_list.dart
@@ -61,6 +61,9 @@ Future<List<Node>> 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<List<Node>> 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<List<Node>> loadAllDefaultNodes() async {
baseNodes +
arbitrumNodes +
zcashNodes +
- bscNodes;
+ bscNodes +
+ robinhoodNodes;
}
Future<void> resetToDefault() async {
### 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;
}
### cw_core/lib/wallet_type.part.dart
@@ -53,6 +53,8 @@ class WalletTypeAdapter extends TypeAdapter<WalletType> {
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<WalletType> {
case WalletType.bsc:
writer.writeByte(19);
break;
+ case WalletType.robinhood:
+ writer.writeByte(20);
+ break;
}
}
### 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,14 +332,16 @@ class EVMChainClient {
currency == CryptoCurrency.baseEth ||
currency == CryptoCurrency.arbEth ||
currency == CryptoCurrency.bnb ||
+ currency == CryptoCurrency.robEth ||
contractAddress != null);
final isNativeToken = [
CryptoCurrency.eth,
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<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
+ Future<Erc20Token?> 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<Erc20Token?> getErcTokenInfoFromNode(String contractAddress, String chainName) async {
+ Future<Erc20Token?> getErcTokenInfoFromNode(String contractAddress) async {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final name = await erc20.name();
final symbol = await erc20.symbol();
### 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;
+}
### 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);
}
### 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<Erc20Token> getDefaultTokensByChainId(int chainId) {
- return switch (chainId) {
- 1 => EthereumTokens.tokens,
- 137 => PolygonTokens.tokens,
- 8453 => BaseTokens.tokens,
- 42161 => ArbitrumTokens.tokens,
- 56 => BSCTokens.tokens,
- _ => [],
- };
- }
+ static List<Erc20Token> 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<String> getDefaultTokenAddresses(int chainId) =>
+ getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
+
+ static List<String> getDefaultTokenSymbols(int chainId) =>
+ getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList();
+
+ static final Map<int, Map<String, String>> _iconPathsByAddress = {};
+
+ static String? getDefaultIconPathByAddress(int chainId, String contractAddress) {
+ final iconPaths = _iconPathsByAddress.putIfAbsent(chainId, () {
+ final paths = <String, String>{};
- static List<String> getDefaultTokenAddresses(int chainId) {
- return getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
+ 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<String> getDefaultTokenSymbols(int chainId) {
- return getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList();
+ static Set<String> getDefaultGroupsByAddress(int chainId, String contractAddress) {
+ for (final token in getDefaultTokensByChainId(chainId)) {
+ if (token.contractAddress.toLowerCase() == contractAddress.toLowerCase()) {
+ return token.groups;
+ }
+ }
+
+ return const {};
}
}
### 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(
### 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<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
+ Future<Erc20Token?> getErc20Token(String contractAddress, String? chainName) async {
try {
return await _client.getErc20Token(contractAddress, chainName);
} catch (e) {
### 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<Erc20Token> 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();
+ }
+}
### 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(),
+ };
}
### 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 = <String>{};
+ 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");
+ });
+ });
+}
### 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<String, dynamic>;
final symbol = decodedResponse['symbol'] ?? '';
### cw_solana/lib/solana_wallet.dart
@@ -360,6 +360,9 @@ abstract class SolanaWalletBase
@override
Future<void> updateTransactionsHistory({List<String>? 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<void> _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<void> 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));
### ios/Runner/InfoBase.plist
@@ -363,6 +363,26 @@
<string>bsc-wallet</string>
</array>
</dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>robinhood</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>robinhood</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>robinhood-wallet</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>robinhood-wallet</string>
+ </array>
+ </dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
### 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);
### 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:
### 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:
### lib/core/seed_validator.dart
@@ -37,6 +37,7 @@ class SeedValidator extends Validator<MnemonicItem> {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
+ case WalletType.robinhood:
return evm!.getEVMWordList(language);
case WalletType.bitcoinCash:
return getBitcoinWordList(language);
### 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:
### lib/di.dart
@@ -1234,6 +1234,7 @@ Future<void> 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(
### 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<void> defaultSettingsMigration(
@@ -660,6 +661,12 @@ Future<void> defaultSettingsMigration(
break;
case 72:
await createDefaultChartsData();
+ await addWalletNodeList(type: WalletType.robinhood);
+ await _changeDefaultNode(
+ sharedPreferences: sharedPreferences,
+ type: WalletType.robinhood,
+ currentNodePreferenceKey: PreferencesKey.currentRobinhoodNodeIdKey,
+ );
break;
default:
break;
### lib/entities/node_check.dart
@@ -16,6 +16,7 @@ const Map<WalletType, String> 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,
### 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';
### lib/entities/priority_for_wallet_type.dart
@@ -30,6 +30,7 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
case WalletType.dogecoin:
return dogecoin!.getTransactionPriorities();
case WalletType.arbitrum:
+ case WalletType.robinhood:
case WalletType.nano:
case WalletType.banano:
case WalletType.solana:
### lib/evm/cw_evm.dart
@@ -199,10 +199,13 @@ class CWEVM extends EVM {
@override
Future<Erc20Token?> 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;
### 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,
};
}
### 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(),
};
}
### 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();
}
### 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 = <String, dynamic>{};
var body = <String, dynamic>{
'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,
+ };
}
### lib/new-ui/pages/send_page.dart
@@ -612,7 +612,10 @@ class _NewSendPageState extends State<NewSendPage> {
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
? () =>
### 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<CryptoCurrency> recents,
### 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,15 +37,15 @@ 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: "",
message: "${S.current.infobox_multichain} ${walletTypeToString(type)}",
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;
### 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:
### lib/reactions/wallet_connect.dart
@@ -11,6 +11,7 @@ const List<WalletType> 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<String> 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();
### 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';
### lib/src/screens/contact/contact_list_page.dart
@@ -388,9 +388,12 @@ class _ContactListBodyState extends State<ContactListBody> {
}
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);
### 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<MenuWidget> {
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<MenuWidget> {
Image baseIcon;
Image arbitrumIcon;
Image bscIcon;
+ Widget robinhoodIcon;
Image solanaIcon;
Image tronIcon;
Image wowneroIcon;
@@ -261,6 +264,8 @@ class MenuWidgetState extends State<MenuWidget> {
return arbitrumIcon;
case WalletType.bsc:
return bscIcon;
+ case WalletType.robinhood:
+ return robinhoodIcon;
case WalletType.tron:
return tronIcon;
case WalletType.wownero:
### 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",
### 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,
};
}
### 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),
### 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);
### 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:
### 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<List<Erc20Token>> 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')
### 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,
### 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;
### 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:
### 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:
### 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(
### 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(
### 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;
### 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:
### 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,
### 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 =>
### 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
### 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,
### 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;
### 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) {
### 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:
### 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",
### 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:
### 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,
### 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,
### 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<List<WalletInfo>> 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();
}
### 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/
### res/pictures/balance_card_backgrounds/robinhood.svg
@@ -0,0 +1,12 @@
+<svg viewBox="0 0 400 248" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="rhSheen" x1="0" y1="0" x2="1" y2="1">
+ <stop offset="0" stop-color="#000000" stop-opacity="0"/>
+ <stop offset="1" stop-color="#000000" stop-opacity="0.14"/>
+ </linearGradient>
+ </defs>
+ <rect width="400" height="248" fill="url(#rhSheen)"/>
+ <g transform="translate(232 22) scale(9.4)" opacity="0.12">
+ <path fill="#000000" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+ </g>
+</svg>
### res/pictures/balance_card_icons/robinhood.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#000000" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### res/pictures/card_icons/chain_icons/robinhood.svg
@@ -0,0 +1,4 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="12" cy="12" r="12" fill="#CCFF00"/>
+ <path fill="#000000" transform="translate(5.1 5.1) scale(0.575)" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### res/pictures/card_icons/og_icons/rob-og.svg
@@ -0,0 +1,4 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="12" cy="12" r="12" fill="#CCFF00"/>
+ <path fill="#000000" transform="translate(5.1 5.1) scale(0.575)" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### res/pictures/card_icons/outline_icons/rob-outline.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path fill="none" stroke="#000000" stroke-width="0.9" stroke-linejoin="round" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### res/pictures/card_icons/symbol_icons/rob-symbol.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#000000" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### res/pictures/chain_badges/robinhood.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#000000" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### res/pictures/crypto_full_icons/robinhood.svg
@@ -0,0 +1,4 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="12" cy="12" r="12" fill="#CCFF00"/>
+ <path fill="#000000" transform="translate(5.1 5.1) scale(0.575)" d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>
+</svg>
### 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": "الراتب",
### 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": "Заплата",
### 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",
### 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",
### 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",
### 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",
### 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": "حقوق",
### 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",
### 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",
### 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",
### 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": "वेतन",
### 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",
### 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": "Աշխատավարձ",
### 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",
### 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",
### 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": "給与",
### 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": "급여",
### 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": "လစာ",
### 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",
### 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",
### 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",
### 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",
### 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": "Зарплата",
### 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": "เงินเดือน",
### 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",
### 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ş",
### 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": "Зарплата",
### 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": "تنخواہ",
### 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",
### 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ṣù",
### 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": "薪资",
### 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
### 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
### 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
### 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
### tool/configure.dart
@@ -35,8 +35,9 @@ Future<void> main(List<String> 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<void> main(List<String> args) async {
hasBase: hasBase,
hasArbitrum: hasArbitrum,
hasBsc: hasBsc,
+ hasRobinhood: hasRobinhood,
hasZcash: hasZcash,
);
await generateWalletTypes(
@@ -90,6 +92,7 @@ Future<void> main(List<String> 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<ChainInfo> getAllChains();
ChainInfo? getCurrentChain(WalletBase wallet);
@@ -1849,6 +1853,7 @@ Future<void> generatePubspec({
required bool hasBase,
required bool hasArbitrum,
required bool hasBsc,
+ required bool hasRobinhood,
required bool hasZcash,
}) async {
const cwCore = """
@@ -1960,7 +1965,7 @@ Future<void> 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<void> 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<void> generateWalletTypes({
outputContent += '\tWalletType.bsc,\n';
}
+ if (hasRobinhood) {
+ outputContent += "\tWalletType.robinhood,\n";
+ }
+
if (hasSolana) {
outputContent += '\tWalletType.solana,\n';
}
### tool/utils/secret_key.dart
@@ -112,6 +112,7 @@ class SecretKey {
SecretKey('moralisApiKey', () => ''),
SecretKey('nowNodesApiKey', () => ''),
SecretKey('blinkApiKey', () => ''),
+ SecretKey('alchemyApiKey', () => ''),
];
static final solanaSecrets = [Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.