Add Robinhood Chain (#3398)

* fix android CI

* feat: Add Robinhood Chain, all major flows done. minor issue now is our providers don't support it yet as its still pretty early, two weeks from launch

* feat: Add Robinhood Chain, all major flows done. minor issue now is our providers don't support it yet as its still pretty early, two weeks from launch

* Cleanup

* auto-reformat

* fix ui issue and contact listing for tokens

* fix: missing icons and crash for fee priority in settings

* chore: cleanup

* fixes from feedbacks

* fix: review issues and sync to latest dev
feat: switch history api to etherscan, add history toggle to privacy settings, exclude robETH from exchange pickers, skip tokens discovery for chains that moralis hasn't indexed, add tests

* fix: address Robinhood review comments

* fix: show Solana token icons in history

* fix: address review comments and default Robinhood to PublicNode

* fix:  revert filter and refuse unmapped networks in provider

---------

Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
This commit is contained in:
David Adegoke
2026-10-04 13:22:05 +02:00
committed by GitHub
co-authored by Robert Malikowski
parent 77e4b946eb
commit 046e57c598
143 changed files with 1214 additions and 159 deletions
@@ -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
+1
View File
@@ -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
@@ -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
@@ -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" />
+3
View File
@@ -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>

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+23
View File
@@ -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"
+1 -1
View File
@@ -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%
+17 -1
View File
@@ -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 = {
+21 -1
View File
@@ -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}) {
+12
View File
@@ -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:
+3
View File
@@ -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)
+2
View File
@@ -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:
+1
View File
@@ -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:
+6 -1
View File
@@ -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 {
+16 -1
View File
@@ -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;
}
+5
View File
@@ -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;
}
}
+29 -11
View File
@@ -57,7 +57,7 @@ class EVMChainClient {
}
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
final res = (jsonResponse['result'] as List);
final res = jsonResponse["result"] as List;
res.removeWhere((e) => e['value'] == '0');
// Filter out spam native transactions below 0.00001 ETH (10000000000000 wei)
@@ -175,17 +175,31 @@ class EVMChainClient {
try {
Uri? rpcUri;
bool isModifiedNodeUri = false;
final nodeHost = Uri.parse("https://${node.uriRaw}").host;
final pathSegments =
(node.path ?? "").split("/").where((segment) => segment.isNotEmpty).toList();
if (node.uriRaw.contains('nownodes.io')) {
if (nodeHost.endsWith(".nownodes.io") && pathSegments.isEmpty) {
isModifiedNodeUri = true;
String nowNodeApiKey = secrets.nowNodesApiKey;
if (nowNodeApiKey.isEmpty) {
printV('NowNodes API key is empty, cannot connect to ${node.uriRaw}');
printV("NowNodes API key is empty, cannot connect to ${node.uriRaw}");
return false;
}
rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey');
} else if (nodeHost.endsWith(".g.alchemy.com") &&
(pathSegments.isEmpty || (pathSegments.length == 1 && pathSegments.first == "v2"))) {
isModifiedNodeUri = true;
String alchemyApiKey = secrets.alchemyApiKey;
if (alchemyApiKey.isEmpty) {
printV("Alchemy API key is empty, cannot connect to ${node.uriRaw}");
return false;
}
rpcUri = Uri.https(node.uriRaw, "/v2/$alchemyApiKey");
}
_client = Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client);
@@ -318,6 +332,7 @@ class EVMChainClient {
currency == CryptoCurrency.baseEth ||
currency == CryptoCurrency.arbEth ||
currency == CryptoCurrency.bnb ||
currency == CryptoCurrency.robEth ||
contractAddress != null);
final isNativeToken = [
@@ -325,7 +340,8 @@ class EVMChainClient {
CryptoCurrency.maticpoly,
CryptoCurrency.baseEth,
CryptoCurrency.arbEth,
CryptoCurrency.bnb
CryptoCurrency.bnb,
CryptoCurrency.robEth
].contains(currency);
// Get nonce with "pending" block tag to include pending transactions
@@ -540,18 +556,20 @@ class EVMChainClient {
}
}
Future<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();
+42
View File
@@ -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;
}
+3
View File
@@ -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);
}
+48 -20
View File
@@ -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>{};
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> getDefaultTokenAddresses(int chainId) {
return getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
}
static Set<String> getDefaultGroupsByAddress(int chainId, String contractAddress) {
for (final token in getDefaultTokensByChainId(chainId)) {
if (token.contractAddress.toLowerCase() == contractAddress.toLowerCase()) {
return token.groups;
}
}
static List<String> getDefaultTokenSymbols(int chainId) {
return getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList();
return const {};
}
}
+30
View File
@@ -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(
+32 -12
View File
@@ -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) {
+407
View File
@@ -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();
}
}
+71 -52
View File
@@ -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(),
};
}
+91
View File
@@ -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");
});
});
}
+8 -3
View File
@@ -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'] ?? '';
+30
View File
@@ -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));
+20
View File
@@ -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>
+1
View File
@@ -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);
@@ -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:
+3
View File
@@ -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:
+1
View File
@@ -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);
+1
View File
@@ -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:
+1
View File
@@ -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(
@@ -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;
+1
View File
@@ -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,
+2
View File
@@ -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';
@@ -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:
+5 -2
View File
@@ -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;
@@ -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,
};
}
@@ -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(),
};
}
@@ -457,6 +457,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
return 'zcash';
case 'AVAXC':
return 'avax';
case "ROB":
return "robinhood";
default:
return tag.toLowerCase();
}
@@ -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,
};
}
+4 -1
View File
@@ -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
? () =>
@@ -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,
@@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/crypto_currency.dart';
import "package:cw_core/currency_for_wallet_type.dart";
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -36,6 +37,7 @@ class ReceiveInfoBox extends StatelessWidget {
case WalletType.polygon:
case WalletType.zano:
case WalletType.bsc:
case WalletType.robinhood:
if (autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.disabled) return null;
return ReceiveInfoBox(
iconPath: "",
@@ -43,8 +45,7 @@ class ReceiveInfoBox extends StatelessWidget {
onDismissed: onDismissed,
bottomWidget: InfoboxCurrencyRow(
currencies: supportedCurrencies ?? [],
chainIconPath:
"assets/new-ui/chain_badges/${walletTypeToString(type).toLowerCase()}.svg",
chainIconPath: walletTypeToCryptoCurrency(type).chainIconPath ?? "",
));
default:
if (autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.disabled) return null;
@@ -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:
+5
View File
@@ -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();
+3
View File
@@ -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';
@@ -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);
@@ -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:
@@ -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",
@@ -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,
};
}
@@ -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),
+28
View File
@@ -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);
+4
View File
@@ -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:
+7 -3
View File
@@ -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')
@@ -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,
@@ -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;
@@ -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:
@@ -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:
@@ -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(
@@ -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(
@@ -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;
@@ -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:
@@ -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,
+3 -2
View File
@@ -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 =>
+3 -1
View File
@@ -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
+3
View File
@@ -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,
@@ -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;
@@ -104,6 +104,7 @@ abstract class OtherSettingsViewModelBase with Store {
WalletType.solana,
WalletType.tron,
WalletType.arbitrum,
WalletType.robinhood,
].contains(_wallet.type));
String getDisplayPriority(dynamic priority) {
@@ -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:
@@ -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",
@@ -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:
+1
View File
@@ -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,
@@ -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,
@@ -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();
}
+1
View File
@@ -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/
@@ -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>

After

Width:  |  Height:  |  Size: 1.1 KiB

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

After

Width:  |  Height:  |  Size: 748 B

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