Skip to content

Commit c5565f4

Browse files
authored
fix: fall back to zero-address for native tokens without slip44 (#9833)
## Explanation EVM native tokens without a slip44 mapping (e.g. Chiliz) left `assetId` undefined. Fall back to the zero-address ERC-20 CAIP-19 form (`eip155:<chainId>/erc20:0x000…000`) so clients can resolve based still based on assetId by deriving the chain id. ## References N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them Made with [Cursor](https://cursor.com)
1 parent 35fe147 commit c5565f4

5 files changed

Lines changed: 91 additions & 33 deletions

File tree

packages/client-utils/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- Fall back to a zero-address ERC-20 CAIP-19 asset id (`eip155:<chainId>/erc20:0x000…000`) in `resolveNativeAssetId` and `getNativeAsset` when an EVM native has no SLIP-44 coin type (previously `undefined`) ([#9833](https://github.com/MetaMask/core/pull/9833))
13+
- `resolveNativeAssetId` consults chainlist via `getNativeAsset` before that fallback so symbol-less calls stay aligned with `getNativeAsset`
14+
- Non-EVM chains still return `undefined` when no slip44 entry is found
1215
- Bump `@metamask/transaction-controller` from `^69.5.0` to `^69.5.2` ([#9798](https://github.com/MetaMask/core/pull/9798), [#9823](https://github.com/MetaMask/core/pull/9823))
1316

1417
## [2.0.1]

packages/client-utils/src/mappers/helpers/caip.test.ts

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,58 @@ describe('caip helpers', () => {
111111
);
112112
});
113113

114-
it('returns undefined without a symbol', () => {
115-
expect(resolveNativeAssetId('eip155:8453', undefined)).toBeUndefined();
116-
expect(resolveNativeAssetId('eip155:4663', undefined)).toBeUndefined();
114+
it('resolves when symbol is missing but the chain has slip44', () => {
115+
mockGetChainById.mockReturnValue({
116+
slip44: 60,
117+
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
118+
} as ReturnType<typeof getChainById>);
119+
120+
expect(resolveNativeAssetId('eip155:8453', undefined)).toBe(
121+
'eip155:8453/slip44:60',
122+
);
123+
});
124+
125+
it('falls back to the zero-address erc20 form when symbol and chainlist both miss slip44', () => {
126+
mockGetChainById.mockReturnValue({
127+
nativeCurrency: { name: 'Chiliz', symbol: 'CHZ', decimals: 18 },
128+
} as ReturnType<typeof getChainById>);
129+
130+
expect(resolveNativeAssetId('eip155:88888', 'CHZ')).toBe(
131+
'eip155:88888/erc20:0x0000000000000000000000000000000000000000',
132+
);
133+
expect(resolveNativeAssetId('eip155:88888', undefined)).toBe(
134+
'eip155:88888/erc20:0x0000000000000000000000000000000000000000',
135+
);
117136
});
118137

119-
it('returns undefined for unknown symbols', () => {
138+
it('falls back to the zero-address erc20 form when the chain is absent from chainlist', () => {
139+
mockGetChainById.mockReturnValue(undefined);
140+
141+
expect(resolveNativeAssetId('eip155:4663', undefined)).toBe(
142+
'eip155:4663/erc20:0x0000000000000000000000000000000000000000',
143+
);
144+
});
145+
146+
it('returns undefined for non-eip155 chains without a slip44 hit', () => {
120147
expect(
121148
resolveNativeAssetId('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'NOPE'),
122149
).toBeUndefined();
150+
expect(
151+
resolveNativeAssetId(
152+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
153+
undefined,
154+
),
155+
).toBeUndefined();
123156
});
124157

125158
it('returns undefined when the chain id cannot be normalized', () => {
126159
expect(resolveNativeAssetId('0xzzzz', 'ETH')).toBeUndefined();
127160
});
161+
162+
it('returns undefined when chain id is missing', () => {
163+
expect(resolveNativeAssetId(undefined, 'ETH')).toBeUndefined();
164+
expect(resolveNativeAssetId(undefined, undefined)).toBeUndefined();
165+
});
128166
});
129167

130168
describe('getNativeAsset', () => {
@@ -214,16 +252,21 @@ describe('caip helpers', () => {
214252
});
215253
});
216254

217-
it('returns undefined when slip44 and symbol lookup both fail', () => {
255+
it('falls back to the zero-address erc20 assetId when slip44 and symbol lookup both fail', () => {
218256
mockGetChainById.mockReturnValue({
219257
nativeCurrency: {
220-
name: 'Unknown',
221-
symbol: 'NOTACOIN',
258+
name: 'Chiliz',
259+
symbol: 'CHZ',
222260
decimals: 18,
223261
},
224262
} as ReturnType<typeof getChainById>);
225263

226-
expect(getNativeAsset('eip155:1088')).toBeUndefined();
264+
expect(getNativeAsset('eip155:88888')).toStrictEqual({
265+
symbol: 'CHZ',
266+
decimals: 18,
267+
assetId:
268+
'eip155:88888/erc20:0x0000000000000000000000000000000000000000',
269+
});
227270
});
228271
});
229272
});

packages/client-utils/src/mappers/helpers/caip.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,15 @@ const slip44BySymbol = ((): Map<string, string> => {
2626
}
2727
}
2828

29-
const maticCoinType = coinTypeBySymbol.get('MATIC');
30-
if (maticCoinType && !coinTypeBySymbol.has('POL')) {
31-
coinTypeBySymbol.set('POL', maticCoinType);
32-
}
33-
3429
return coinTypeBySymbol;
3530
})();
3631

3732
function getCoinType(symbol: string): string | undefined {
38-
return slip44BySymbol.get(symbol.toUpperCase());
33+
const normalizedSymbol = symbol.toUpperCase();
34+
return (
35+
slip44BySymbol.get(normalizedSymbol) ??
36+
(normalizedSymbol === 'POL' ? slip44BySymbol.get('MATIC') : undefined)
37+
);
3938
}
4039

4140
/**
@@ -73,7 +72,7 @@ export function resolveNativeAssetId(
7372
chainId: string | number | undefined,
7473
symbol: string | undefined,
7574
): CaipAssetType | undefined {
76-
if (chainId === undefined || !symbol) {
75+
if (chainId === undefined) {
7776
return undefined;
7877
}
7978

@@ -83,19 +82,25 @@ export function resolveNativeAssetId(
8382
return undefined;
8483
}
8584

86-
const assetReference = getCoinType(symbol);
85+
const { namespace, reference } = parseCaipChainId(caipChainId);
86+
const assetReference = symbol ? getCoinType(symbol) : undefined;
8787

88-
if (!assetReference) {
89-
return undefined;
88+
if (assetReference) {
89+
return toCaipAssetType(namespace, reference, 'slip44', assetReference);
9090
}
9191

92-
const { namespace, reference } = parseCaipChainId(caipChainId);
92+
if (namespace === KnownCaipNamespace.Eip155) {
93+
return (
94+
getNativeAsset(caipChainId)?.assetId ??
95+
toCaipAssetType(namespace, reference, 'erc20', nativeTokenAddress)
96+
);
97+
}
9398

94-
return toCaipAssetType(namespace, reference, 'slip44', assetReference);
99+
return undefined;
95100
}
96101

97102
/**
98-
* Resolves EVM native symbol, decimals, and slip44 asset id for a chain.
103+
* Resolves EVM native symbol, decimals, and CAIP asset id for a chain.
99104
* Prefers eth-chainlist slip44 except testnet coin type 1, then falls back to
100105
* `@metamask/slip44` by native symbol.
101106
*
@@ -130,14 +135,14 @@ export function getNativeAsset(chainId: CaipChainId):
130135
? String(slip44)
131136
: getCoinType(nativeCurrency.symbol);
132137

133-
if (!assetReference) {
134-
return undefined;
135-
}
138+
const assetId = assetReference
139+
? toCaipAssetType(namespace, reference, 'slip44', assetReference)
140+
: toCaipAssetType(namespace, reference, 'erc20', nativeTokenAddress);
136141

137142
return {
138143
symbol: nativeCurrency.symbol,
139144
decimals: nativeCurrency.decimals ?? nativeTokenDecimals,
140-
assetId: toCaipAssetType(namespace, reference, 'slip44', assetReference),
145+
assetId,
141146
};
142147
}
143148

packages/client-utils/src/mappers/helpers/transactions.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ describe('transaction helpers', () => {
353353
]);
354354
});
355355

356-
it('keeps the nativeAssetSymbol on fees when it cannot be mapped to an assetId', () => {
356+
it('falls back to the zero-address native assetId when the chain is unknown and the symbol has no slip44', () => {
357357
expect(
358358
getLocalTransactionFees({
359359
nativeAssetSymbol: 'NOTACOIN',
@@ -373,6 +373,8 @@ describe('transaction helpers', () => {
373373
decimals: 18,
374374
assetType: 'native',
375375
symbol: 'NOTACOIN',
376+
assetId:
377+
'eip155:999999991/erc20:0x0000000000000000000000000000000000000000',
376378
},
377379
]);
378380
});

packages/client-utils/src/mappers/local-transaction-mapper.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ describe('mapLocalTransaction', () => {
3636
decimals: 18,
3737
direction: 'out',
3838
assetType: 'native',
39+
assetId: 'eip155:1/slip44:60',
3940
},
4041
},
4142
});
4243
});
43-
it('maps a native send on an unknown chain without a ticker to a Send with amount but no symbol', () => {
44+
it('maps a native send on an unknown chain without a ticker to a Send with amount and a zero-address native assetId', () => {
4445
const item = mapLocalTransaction(
4546
localTransactionFixtures.mapInputs.mapsANativeSendOnAn,
4647
);
@@ -58,6 +59,8 @@ describe('mapLocalTransaction', () => {
5859
decimals: 18,
5960
direction: 'out',
6061
assetType: 'native',
62+
assetId:
63+
'eip155:1338/erc20:0x0000000000000000000000000000000000000000',
6164
},
6265
},
6366
});
@@ -334,7 +337,7 @@ describe('mapLocalTransaction', () => {
334337
},
335338
});
336339
});
337-
it('maps an incoming native transfer without nativeAssetSymbol to a Receive with native assetType only', () => {
340+
it('maps an incoming native transfer without nativeAssetSymbol to a Receive with the native assetId', () => {
338341
const item = mapLocalTransaction(
339342
localTransactionFixtures.mapInputs.mapsAnIncomingNativeTransferTo,
340343
);
@@ -347,9 +350,9 @@ describe('mapLocalTransaction', () => {
347350
},
348351
},
349352
});
350-
expect(
351-
item.type === 'receive' ? item.data.token?.assetId : 'unset',
352-
).toBeUndefined();
353+
expect(item.type === 'receive' ? item.data.token?.assetId : 'unset').toBe(
354+
'eip155:1/slip44:60',
355+
);
353356
});
354357
it('maps an mUSD conversion to a Convert activity', () => {
355358
const item = mapLocalTransaction(
@@ -620,7 +623,7 @@ describe('mapLocalTransaction', () => {
620623
data: { from },
621624
});
622625
});
623-
it('maps a WETH9 deposit contract interaction to a Wrap activity with a native source amount but no symbol when nativeAssetSymbol is omitted', () => {
626+
it('maps a WETH9 deposit contract interaction to a Wrap activity with a native source amount and the native assetId when nativeAssetSymbol is omitted', () => {
624627
const item = mapLocalTransaction(
625628
localTransactionFixtures.mapInputs.mapsAWeth9DepositContractInteraction,
626629
);
@@ -637,6 +640,7 @@ describe('mapLocalTransaction', () => {
637640
decimals: 18,
638641
direction: 'out',
639642
assetType: 'native',
643+
assetId: 'eip155:1/slip44:60',
640644
},
641645
destinationToken: {
642646
amount: '0x3782dace9d900000',
@@ -710,7 +714,7 @@ describe('mapLocalTransaction', () => {
710714
},
711715
});
712716
});
713-
it('maps a native value contract interaction with amount but no symbol when nativeAssetSymbol is omitted', () => {
717+
it('maps a native value contract interaction with amount, no symbol, and the chainlist native assetId when nativeAssetSymbol is omitted', () => {
714718
const item = mapLocalTransaction(
715719
localTransactionFixtures.mapInputs.mapsANativeValueContractInteraction,
716720
);
@@ -728,6 +732,7 @@ describe('mapLocalTransaction', () => {
728732
decimals: 18,
729733
direction: 'out',
730734
assetType: 'native',
735+
assetId: 'eip155:1/slip44:60',
731736
},
732737
methodId: '0xd0e30db0',
733738
},

0 commit comments

Comments
 (0)