Add Robinhood Chain (#3398)

* fix android CI

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

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

* Cleanup

* auto-reformat

* fix ui issue and contact listing for tokens

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

* chore: cleanup

* fixes from feedbacks

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

* fix: address Robinhood review comments

* fix: show Solana token icons in history

* fix: address review comments and default Robinhood to PublicNode

* fix:  revert filter and refuse unmapped networks in provider

---------

Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
This commit is contained in:
David Adegoke
2026-10-04 13:22:05 +02:00
committed by GitHub
co-authored by Robert Malikowski
parent 77e4b946eb
commit 046e57c598
143 changed files with 1214 additions and 159 deletions
@@ -56,6 +56,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.solana:
case WalletType.tron:
case WalletType.zcash:
@@ -100,6 +101,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
WalletType.base,
WalletType.arbitrum,
WalletType.bsc,
WalletType.robinhood,
WalletType.tron,
WalletType.solana,
WalletType.monero,
@@ -219,6 +219,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.solana:
case WalletType.tron:
return S.current.xmr_full_balance;
@@ -1300,6 +1300,7 @@ abstract class DashboardViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.solana:
case WalletType.nano:
case WalletType.banano:
@@ -194,10 +194,10 @@ abstract class HomeSettingsViewModelBase with Store {
return false;
}
bool isPotentialScamViaMoralis = await _isPotentialScamTokenViaMoralis(
contractAddress,
getChainNameBasedOnWalletType(_balanceViewModel.wallet.type),
);
final moralisChainName = evm!.getMoralisChainName(_balanceViewModel.wallet);
final isPotentialScamViaMoralis = moralisChainName != null &&
await _isPotentialScamTokenViaMoralis(contractAddress, moralisChainName);
bool isUnverifiedContract = await _isContractUnverified(
contractAddress,
@@ -221,6 +221,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
defaultTokenAddresses = evm!.getDefaultTokenContractAddresses(_balanceViewModel.wallet);
break;
case WalletType.solana:
@@ -262,6 +263,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
defaultTokenSymbols = evm!.getDefaultTokenSymbols(_balanceViewModel.wallet);
break;
case WalletType.solana:
@@ -207,6 +207,7 @@ class TransactionListItem extends ActionListItem with Keyable {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
final asset = assetOfTransaction;
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
@@ -277,7 +277,8 @@ abstract class ExchangeTradeViewModelBase with Store {
_isTokenBelongingToWallet(CryptoCurrency.arbEth) ||
_isTokenBelongingToWallet(CryptoCurrency.trx) ||
_isTokenBelongingToWallet(CryptoCurrency.sol) ||
_isTokenBelongingToWallet(CryptoCurrency.bnb);
_isTokenBelongingToWallet(CryptoCurrency.bnb) ||
_isTokenBelongingToWallet(CryptoCurrency.robEth);
if (!canSend) {
return 'Wallet currency ${wallet.currency.title} does not match trade from currency ${tradeFrom.title} or is not a supported token for this wallet.';
@@ -346,6 +347,7 @@ abstract class ExchangeTradeViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
return _createERC681URI(fromCurrency, inputAddress, amount);
case WalletType.solana:
return SolanaURI(
@@ -1547,6 +1547,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.bnb;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.robinhood:
depositCurrency = CryptoCurrency.robEth;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.solana:
depositCurrency = CryptoCurrency.sol;
receiveCurrency = CryptoCurrency.xmr;
@@ -126,6 +126,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.solana:
case WalletType.banano:
case WalletType.nano:
@@ -35,6 +35,7 @@ class WalletRestoreFromQRCode {
'base-wallet': WalletType.base,
'arbitrum-wallet': WalletType.arbitrum,
'bsc-wallet': WalletType.bsc,
"robinhood-wallet": WalletType.robinhood,
'nano-wallet': WalletType.nano,
'nano_wallet': WalletType.nano,
'bitcoincash': WalletType.bitcoinCash,
+3 -2
View File
@@ -109,6 +109,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.solana:
case WalletType.tron:
case WalletType.arbitrum:
case WalletType.robinhood:
case WalletType.zcash:
return false;
}
@@ -128,8 +129,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana &&
wallet.type != WalletType.tron &&
wallet.chainId !=
42161; // Wallet type is generic for all EVM chains, so we need to check the chainId
wallet.chainId != 42161 &&
wallet.chainId != 4663;
@computed
bool get isElectrumWallet =>
+3 -1
View File
@@ -189,12 +189,14 @@ abstract class OutputBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
final isNative = [
CryptoCurrency.eth,
CryptoCurrency.maticpoly,
CryptoCurrency.baseEth,
CryptoCurrency.arbEth,
CryptoCurrency.bnb
CryptoCurrency.bnb,
CryptoCurrency.robEth
].contains(cryptoCurrencyHandler());
final fee = isNative
+3
View File
@@ -293,6 +293,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.tron:
case WalletType.solana:
case WalletType.bitcoin:
@@ -1273,6 +1274,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
WalletType.solana,
WalletType.tron,
WalletType.arbitrum,
WalletType.robinhood,
WalletType.zcash,
].contains(wallet.type)) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
@@ -1311,6 +1313,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
return evm!.createEVMTransactionCredentials(
outputs,
priority: priority,
@@ -205,6 +205,9 @@ abstract class ConnectionSyncViewModelBase with Store {
@computed
bool get useBaseScan => _settingsStore.useBaseScan;
@computed
bool get useRobinhoodScan => _settingsStore.useRobinhoodScan;
@computed
bool get useArbiScan => _settingsStore.useArbiScan;
@@ -223,6 +226,9 @@ abstract class ConnectionSyncViewModelBase with Store {
@computed
bool get canUseBaseScan => _wallet.chainId == 8453;
@computed
bool get canUseRobinhoodScan => _wallet.chainId == 4663;
@computed
bool get canUseArbiScan => _wallet.chainId == 42161;
@@ -271,6 +277,12 @@ abstract class ConnectionSyncViewModelBase with Store {
evm!.updateScanProviderUsageState(_wallet, value);
}
@action
void setUseRobinhoodScan(bool value) {
_settingsStore.useRobinhoodScan = value;
evm!.updateScanProviderUsageState(_wallet, value);
}
@action
void setUseTronGrid(bool value) {
_settingsStore.useTronGrid = value;
@@ -104,6 +104,7 @@ abstract class OtherSettingsViewModelBase with Store {
WalletType.solana,
WalletType.tron,
WalletType.arbitrum,
WalletType.robinhood,
].contains(_wallet.type));
String getDisplayPriority(dynamic priority) {
@@ -476,6 +476,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return "https://arbiscan.io/tx/${txId}";
case WalletType.bsc:
return "https://bscscan.com/tx/${txId}";
case WalletType.robinhood:
return "https://robinhoodchain.blockscout.com/tx/${txId}";
case WalletType.polygon:
return "https://polygonscan.com/tx/${txId}";
case WalletType.nano:
@@ -562,6 +562,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
"assets/images/usdt_wallet_icon.svg",
"assets/images/more_tokens.svg",
];
case 4663:
return [
"assets/new-ui/crypto_full_icons/robinhood.svg",
"assets/images/usdg_icon.png",
"assets/images/more_tokens.svg",
];
default:
return [
"assets/new-ui/crypto_full_icons/ethereum.svg",
@@ -171,6 +171,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.solana:
case WalletType.tron:
items.addAll([
@@ -321,6 +322,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'arbitrum-wallet';
case WalletType.bsc:
return 'bsc-wallet';
case WalletType.robinhood:
return "robinhood-wallet";
case WalletType.solana:
return 'solana-wallet';
case WalletType.tron:
+1
View File
@@ -83,6 +83,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
return evm!.createEVMNewWalletCredentials(
name: name,
password: walletPassword,
@@ -54,6 +54,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
case WalletType.decred:
case WalletType.bitcoin:
case WalletType.litecoin:
@@ -97,6 +98,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletType.base,
WalletType.arbitrum,
WalletType.bsc,
WalletType.robinhood,
WalletType.nano,
WalletType.banano,
WalletType.solana,
@@ -179,6 +181,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
return evm!.createEVMRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
@@ -288,6 +291,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.robinhood:
return evm!.createEVMRestoreWalletFromPrivateKey(
name: name,
password: password,
@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/wallet_base.dart';
@@ -32,13 +31,6 @@ abstract class WalletSwitcherViewModelBase with Store {
Future<List<WalletInfo>> getWallets(WalletType? walletType) async {
final wiList = await WalletInfo.getAll();
if (walletType == null) return wiList;
// For EVM-compatible wallet types, show all EVM-compatible wallets
// This allows users to switch between any EVM wallet regardless of the specific chain
if (isEVMCompatibleChain(walletType)) {
return wiList.where((wallet) => isEVMCompatibleChain(wallet.type)).toList();
}
return wiList.where((wallet) => wallet.type == walletType).toList();
}