Skip to content

Commit 6f907f4

Browse files
authored
feat(card): enable base on delegation flow (#23509)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> This update adds support for delegating Base assets within the MetaMask Card feature. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Added support for delegating Base assets in the MetaMask Card feature ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/f54fcee4-e30a-40c7-869e-775c24c4e8a1 ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Enables Base network for MetaMask Card, adding network-aware SDK/providers, token/feature flag configs, UI mapping/filters, and balance/allowance handling. > > - **Card SDK/Backend**: > - Add Base to supported networks and validation (`CardNetwork`, `SUPPORTED_ASSET_NETWORKS`). > - Introduce network-aware providers/RPCs (`cardNetworkInfos`, Base/Linea RPC URLs) and `foxConnect` access per network. > - Update `getSupportedTokensByChainId()` to default CAIP and use across callers. > - Make `getLatestAllowanceFromLogs` network-aware (accepts `CardNetwork`). > - Allow `completeEVMDelegation` for `base`; extend delegation settings validation to `base`. > - Map external wallet network -> CAIP via `cardNetworkInfos`; remove unused `totalAllowance` mapping. > - **UI/Hooks**: > - Spending Limit: resolve network via `caipChainIdToNetwork` before delegation. > - Asset Selection: fetch supported tokens without explicit chain id; strict network typing for filters. > - Balances: include Base chain ID when fetching balances. > - Latest allowance: skip non-EVM chains; pass network derived from CAIP when querying logs. > - Chain name mapping: use `caipChainIdToNetwork` (returns Linea by default). > - **Feature Flags**: > - Add Base (`eip155:8453`) config with tokens and `foxConnectAddresses`. > - **Tests**: > - Update/expand tests for Base support, network-aware log queries, and new chain name mapping; remove tests for deleted `totalAllowance` mapping. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8c5fe0d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 1123796 commit 6f907f4

16 files changed

+230
-484
lines changed

app/components/UI/Card/Views/SpendingLimit/SpendingLimit.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import {
1414
} from '../../hooks/useCardDelegation';
1515
import { useCardSDK } from '../../sdk';
1616
import { strings } from '../../../../../../locales/i18n';
17-
import { BAANX_MAX_LIMIT, ARBITRARY_ALLOWANCE } from '../../constants';
17+
import {
18+
BAANX_MAX_LIMIT,
19+
ARBITRARY_ALLOWANCE,
20+
caipChainIdToNetwork,
21+
} from '../../constants';
1822
import Logger from '../../../../../util/Logger';
1923
import Text, {
2024
TextVariant,
@@ -39,27 +43,19 @@ import {
3943
CardTokenAllowance,
4044
DelegationSettingsResponse,
4145
CardExternalWalletDetailsResponse,
42-
CardNetwork,
4346
} from '../../types';
4447
import { createAssetSelectionModalNavigationDetails } from '../../components/AssetSelectionBottomSheet';
4548
import AvatarToken from '../../../../../component-library/components/Avatars/Avatar/variants/AvatarToken';
4649
import { AvatarSize } from '../../../../../component-library/components/Avatars/Avatar';
4750
import { buildTokenIconUrl } from '../../util/buildTokenIconUrl';
4851
import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics';
4952
import { CardActions, CardScreens } from '../../util/metrics';
50-
import { mapCaipChainIdToChainName } from '../../util/mapCaipChainIdToChainName';
5153
import { clearCacheData } from '../../../../../core/redux/slices/card';
5254
import { useDispatch } from 'react-redux';
5355
import Routes from '../../../../../constants/navigation/Routes';
5456
import { SafeAreaView } from 'react-native-safe-area-context';
5557
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
56-
57-
const getNetworkFromCaipChainId = (caipChainId: string): CardNetwork => {
58-
if (caipChainId === SolScope.Mainnet || caipChainId.startsWith('solana:')) {
59-
return 'solana';
60-
}
61-
return 'linea';
62-
};
58+
import { mapCaipChainIdToChainName } from '../../util/mapCaipChainIdToChainName';
6359

6460
const SpendingLimit = ({
6561
route,
@@ -281,8 +277,12 @@ const SpendingLimit = ({
281277
const tokenToUse = selectedToken || priorityToken;
282278
const currency = tokenToUse?.symbol;
283279
const network = tokenToUse?.caipChainId
284-
? getNetworkFromCaipChainId(tokenToUse.caipChainId)
285-
: 'linea';
280+
? caipChainIdToNetwork[tokenToUse.caipChainId]
281+
: null;
282+
283+
if (!network) {
284+
throw new Error('Network not found');
285+
}
286286

287287
await submitDelegation({
288288
amount: delegationAmount,

app/components/UI/Card/components/AssetSelectionBottomSheet/AssetSelectionBottomSheet.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useCardSDK } from '../../sdk';
66
import {
77
AllowanceState,
88
CardExternalWalletDetailsResponse,
9+
CardNetwork,
910
CardTokenAllowance,
1011
DelegationSettingsResponse,
1112
} from '../../types';
@@ -118,7 +119,7 @@ const AssetSelectionBottomSheet: React.FC = () => {
118119

119120
// Get supported tokens from the card SDK to display in the bottom sheet.
120121
const cardSupportedTokens = useMemo(
121-
() => sdk?.getSupportedTokensByChainId(sdk?.lineaChainId) ?? [],
122+
() => sdk?.getSupportedTokensByChainId() ?? [],
122123
[sdk],
123124
);
124125

@@ -162,7 +163,7 @@ const AssetSelectionBottomSheet: React.FC = () => {
162163

163164
// Filter unsupported networks and unknown chains
164165
if (
165-
!SUPPORTED_ASSET_NETWORKS.includes(networkLower) ||
166+
!SUPPORTED_ASSET_NETWORKS.includes(networkLower as CardNetwork) ||
166167
networkLower === 'unknown'
167168
) {
168169
return true;
@@ -271,7 +272,10 @@ const AssetSelectionBottomSheet: React.FC = () => {
271272
const networkLower = network.network?.toLowerCase();
272273

273274
// Filter unsupported networks
274-
if (!networkLower || !SUPPORTED_ASSET_NETWORKS.includes(networkLower)) {
275+
if (
276+
!networkLower ||
277+
!SUPPORTED_ASSET_NETWORKS.includes(networkLower as CardNetwork)
278+
) {
275279
return false;
276280
}
277281

app/components/UI/Card/constants.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,51 @@
11
import { ethers } from 'ethers';
22
import balanceScannerAbi from './sdk/balanceScannerAbi.json';
3+
import { CardNetwork, CardNetworkInfo } from './types';
4+
import { CaipChainId } from '@metamask/utils';
35

6+
const InfuraKey = process.env.MM_INFURA_PROJECT_ID;
7+
const infuraProjectId = InfuraKey === 'null' ? '' : InfuraKey;
8+
9+
export const LINEA_MAINNET_RPC_URL = `https://linea-mainnet.infura.io/v3/${infuraProjectId}`;
10+
export const BASE_MAINNET_RPC_URL = `https://base-mainnet.infura.io/v3/${infuraProjectId}`;
411
export const BALANCE_SCANNER_ABI =
512
balanceScannerAbi as ethers.ContractInterface;
613
export const ARBITRARY_ALLOWANCE = 100000000000;
714
export const DEPOSIT_SUPPORTED_TOKENS = ['USDC', 'USDT', 'mUSD'];
815
export const BAANX_MAX_LIMIT = '2199023255551';
916
export const AUTHENTICATED_CACHE_DURATION = 60 * 1000;
1017
export const UNAUTHENTICATED_CACHE_DURATION = 5 * 60 * 1000;
11-
export const SUPPORTED_ASSET_NETWORKS = ['linea', 'linea-us', 'solana'];
18+
export const SUPPORTED_ASSET_NETWORKS: CardNetwork[] = [
19+
'linea',
20+
'linea-us',
21+
'solana',
22+
'base',
23+
];
24+
export const CARD_SUPPORT_EMAIL = '[email protected]';
25+
26+
export const cardNetworkInfos: Record<CardNetwork, CardNetworkInfo> = {
27+
linea: {
28+
caipChainId: 'eip155:59144',
29+
rpcUrl: LINEA_MAINNET_RPC_URL,
30+
},
31+
'linea-us': {
32+
caipChainId: 'eip155:59144',
33+
rpcUrl: LINEA_MAINNET_RPC_URL,
34+
},
35+
base: {
36+
caipChainId: 'eip155:8453',
37+
rpcUrl: BASE_MAINNET_RPC_URL,
38+
},
39+
solana: {
40+
caipChainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
41+
},
42+
};
43+
44+
export const caipChainIdToNetwork: Record<CaipChainId, CardNetwork> = {
45+
'eip155:59144': 'linea',
46+
'eip155:8453': 'base',
47+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'solana',
48+
};
1249

1350
/**
1451
* Tokens that don't support the spending limit progress bar feature.
@@ -17,4 +54,3 @@ export const SUPPORTED_ASSET_NETWORKS = ['linea', 'linea-us', 'solana'];
1754
* Format: Token symbols in uppercase
1855
*/
1956
export const SPENDING_LIMIT_UNSUPPORTED_TOKENS = ['AUSDC'];
20-
export const CARD_SUPPORT_EMAIL = '[email protected]';

app/components/UI/Card/hooks/useAssetBalances.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ export const useAssetBalances = (
4646
): Map<string, AssetBalanceInfo> => {
4747
const { MultichainAssetsRatesController, TokenRatesController } =
4848
Engine.context;
49-
const chainIds = [CHAIN_IDS.LINEA_MAINNET, SOLANA_MAINNET.chainId];
49+
const chainIds = [
50+
CHAIN_IDS.LINEA_MAINNET,
51+
SOLANA_MAINNET.chainId,
52+
CHAIN_IDS.BASE,
53+
];
5054

5155
const tokensWithBalance = useTokensWithBalance({
5256
chainIds,

app/components/UI/Card/hooks/useCardDelegation.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ interface DelegationParams {
4545
* Hook to handle the complete delegation flow for spending limit increases
4646
* Flow: Token -> Signature -> Approval Transaction -> Completion
4747
*
48-
* Note: Currently only supports EVM chains (Linea)
48+
* Note: Currently only supports EVM chains
4949
*/
5050
export const useCardDelegation = (token?: CardTokenAllowance | null) => {
5151
const { sdk } = useCardSDK();
@@ -147,11 +147,6 @@ export const useCardDelegation = (token?: CardTokenAllowance | null) => {
147147
'TransactionController:transactionConfirmed',
148148
async (transactionMeta) => {
149149
if (transactionMeta.status === TransactionStatus.confirmed) {
150-
Logger.log(
151-
'controllerMessenger::Transaction confirmed',
152-
transactionMeta.id,
153-
transactionId,
154-
);
155150
try {
156151
await sdk.completeEVMDelegation({
157152
address,

0 commit comments

Comments
 (0)