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>
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -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,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%
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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}) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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'] ?? '';
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |