diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 1d0039ff111..31bf1836601 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1904,7 +1904,7 @@ }, "packages/client-utils/src/mappers/local-transaction-mapper.test.ts": { "vitest/no-conditional-in-test": { - "count": 19 + "count": 16 } }, "packages/client-utils/src/mappers/ramps-order-mapper.test.ts": { diff --git a/packages/client-utils/CHANGELOG.md b/packages/client-utils/CHANGELOG.md index 3b6c1bff649..a7e4e939c23 100644 --- a/packages/client-utils/CHANGELOG.md +++ b/packages/client-utils/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Map `musdConversion` local transactions as `send` instead of `convert` ([#10217](https://github.com/MetaMask/core/pull/10217)) + ## [3.1.1] ### Changed diff --git a/packages/client-utils/src/mappers/local-transaction-mapper.test.ts b/packages/client-utils/src/mappers/local-transaction-mapper.test.ts index 75cf5f2bcfa..8a745e98d1a 100644 --- a/packages/client-utils/src/mappers/local-transaction-mapper.test.ts +++ b/packages/client-utils/src/mappers/local-transaction-mapper.test.ts @@ -12,15 +12,8 @@ vi.mock(import('./helpers/token-metadata.js'), async () => ({ .getKnownTokenMetadata, })); -const { - from, - to, - baseUsdc, - lineaDai, - lineaMusd, - wethContractAddress, - mainnetUsdt, -} = localTransactionFixtures.addresses; +const { from, to, baseUsdc, lineaMusd, wethContractAddress, mainnetUsdt } = + localTransactionFixtures.addresses; // Fixtures intentionally omit full TransactionMeta fields (e.g. networkClientId). const mapLocal = (input: unknown): ActivityItem => @@ -367,30 +360,24 @@ describe('mapLocalTransaction', () => { 'eip155:1/slip44:60', ); }); - it('maps an mUSD conversion to a Convert activity', () => { + it('maps an mUSD conversion to a Send activity', () => { const item = mapLocal( localTransactionFixtures.mapInputs.mapsAnMusdConversionToA, ); expect(item).toStrictEqual({ - type: 'convert', + type: 'send', chainId: 'eip155:59144', status: 'success', timestamp: 1779805800000, hash: '0xmusdconversion', data: { from, - sourceToken: { - assetId: formatAddressToAssetId(lineaDai, 'eip155:59144'), - decimals: 18, - direction: 'out', - symbol: 'DAI', - }, - destinationToken: { - amount: '100099', + to: lineaMusd, + token: { assetId: formatAddressToAssetId(lineaMusd, 'eip155:59144'), assetType: 'erc20', decimals: 6, - direction: 'in', + direction: 'out', symbol: 'mUSD', }, }, @@ -545,6 +532,75 @@ describe('mapLocalTransaction', () => { data: { from, destinationToken: undefined }, }); }); + it('maps a withdraw whose received token log has no contract address to no destination token', () => { + const base = + localTransactionFixtures.mapInputs.mapsAWithdrawContractInteractionFrom; + const item = mapLocal({ + ...base, + initialTransaction: { + ...base.initialTransaction, + txReceipt: { + logs: [ + { + address: '', + data: '0x0000000000000000000000000000000000000000000000000000000000030d40', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000004e65fe4dba92790696d040ac24aa414708f5c0ab', + '0x0000000000000000000000009bed78535d6a03a955f1504aadba974d9a29e292', + ], + }, + ], + }, + }, + }); + expect(item).toMatchObject({ + type: 'lendingWithdrawal', + data: { destinationToken: undefined }, + }); + }); + it('maps a withdraw on a chain without wrapped-native metadata for an unencodable token', () => { + const base = + localTransactionFixtures.mapInputs.mapsAWithdrawContractInteractionFrom; + const item = mapLocal({ + ...base, + initialTransaction: { + ...base.initialTransaction, + chainId: '0x53a', + txReceipt: { + logs: [ + { + address: 'not-an-address', + data: '0x0000000000000000000000000000000000000000000000000000000000030d40', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000004e65fe4dba92790696d040ac24aa414708f5c0ab', + '0x0000000000000000000000009bed78535d6a03a955f1504aadba974d9a29e292', + ], + }, + ], + }, + }, + primaryTransaction: { + ...base.primaryTransaction, + chainId: '0x53a', + }, + }); + expect(item).toStrictEqual({ + type: 'lendingWithdrawal', + chainId: 'eip155:1338', + status: 'success', + timestamp: 1779912434153, + hash: '0x26f4911467b538702c0945e4ec5e303de44c0c1c174897141d1b548ea3161795', + data: { + from, + destinationToken: { + direction: 'in', + assetType: 'erc20', + }, + }, + }); + }); it('maps bridge history token data to a local swap', () => { const item = mapLocal( localTransactionFixtures.mapInputs.mapsBridgeHistoryTokenDataTo, @@ -975,8 +1031,8 @@ describe('mapLocalTransaction', () => { }); expect(item).toMatchObject({ - type: 'convert', - data: { from: '', destinationToken: undefined }, + type: 'send', + data: { from: '', token: undefined }, }); }); it('maps an mUSD conversion for an unknown destination token without optional metadata fields', () => { @@ -1001,19 +1057,23 @@ describe('mapLocalTransaction', () => { }, }); - expect(item).toMatchObject({ - type: 'convert', + expect(item).toStrictEqual({ + type: 'send', + chainId: 'eip155:1338', + status: 'success', + timestamp: 1779805800000, + hash: '0xmusdconversionnodata', data: { - destinationToken: { - direction: 'in', + from, + to: 'not-an-address', + token: { + direction: 'out', + assetType: 'erc20', }, }, }); - expect( - item.type === 'convert' ? item.data.destinationToken : undefined, - ).toStrictEqual({ direction: 'in', assetType: 'erc20' }); }); - it('maps an mUSD conversion with transferInformation amount to convert decimals from transferInformation', () => { + it('maps an mUSD conversion with transferInformation amount to send decimals from transferInformation', () => { const base = localTransactionFixtures.mapInputs.mapsAnMusdConversionToA; const item = mapLocal({ ...base, @@ -1027,58 +1087,40 @@ describe('mapLocalTransaction', () => { }); expect(item).toMatchObject({ - type: 'convert', + type: 'send', data: { - destinationToken: { - direction: 'in', + token: { + direction: 'out', amount: '100000', decimals: 6, }, }, }); }); - it('maps an mUSD conversion with no calldata to a convert without a destination amount', () => { + it('maps an mUSD conversion with no calldata to a send without a token amount', () => { const item = mapLocal( localTransactionFixtures.mapInputs.mapsAnMusdConversionWithNo, ); - expect(item).toMatchObject({ - type: 'convert', - data: { destinationToken: { direction: 'in', symbol: 'mUSD' } }, - }); - expect( - item.type === 'convert' ? item.data.destinationToken?.amount : 'unset', - ).toBeUndefined(); - }); - it('maps an mUSD conversion with invalid calldata amount to a convert without a destination amount', () => { - const base = localTransactionFixtures.mapInputs.mapsAnMusdConversionToA; - const invalidAmountData = `0x${'0'.repeat(72)}zz${'0'.repeat(64)}`; - const item = mapLocal({ - ...base, - initialTransaction: { - ...base.initialTransaction, - txParams: { - ...base.initialTransaction.txParams, - data: invalidAmountData, - }, - }, - primaryTransaction: { - ...base.primaryTransaction, - txParams: { - ...base.primaryTransaction.txParams, - data: invalidAmountData, + expect(item).toStrictEqual({ + type: 'send', + chainId: 'eip155:59144', + status: 'success', + timestamp: 1779805800000, + hash: '0xmusdconversionnodata', + data: { + from, + to: lineaMusd, + token: { + assetId: formatAddressToAssetId(lineaMusd, 'eip155:59144'), + assetType: 'erc20', + decimals: 6, + direction: 'out', + symbol: 'mUSD', }, }, }); - - expect(item).toMatchObject({ - type: 'convert', - data: { destinationToken: { direction: 'in', symbol: 'mUSD' } }, - }); - expect( - item.type === 'convert' ? item.data.destinationToken?.amount : 'unset', - ).toBeUndefined(); }); - it('maps an mUSD conversion without a destination contract to a convert without a destination token', () => { + it('maps an mUSD conversion without a destination contract to a send without a token', () => { const base = localTransactionFixtures.mapInputs.mapsAnMusdConversionWithNo; const item = mapLocal({ ...base, @@ -1093,8 +1135,8 @@ describe('mapLocalTransaction', () => { }); expect(item).toMatchObject({ - type: 'convert', - data: { destinationToken: undefined }, + type: 'send', + data: { token: undefined }, }); }); it('maps a token approve with no calldata to an approve spending cap', () => { diff --git a/packages/client-utils/src/mappers/local-transaction-mapper.ts b/packages/client-utils/src/mappers/local-transaction-mapper.ts index ef3cf410c63..80055132ec2 100644 --- a/packages/client-utils/src/mappers/local-transaction-mapper.ts +++ b/packages/client-utils/src/mappers/local-transaction-mapper.ts @@ -254,7 +254,8 @@ export function mapLocalTransaction( case TransactionType.tokenMethodSafeTransferFrom: case TransactionType.tokenMethodTransfer: - case TransactionType.tokenMethodTransferFrom: { + case TransactionType.tokenMethodTransferFrom: + case TransactionType.musdConversion: { return { type: 'send', ...common, @@ -309,33 +310,6 @@ export function mapLocalTransaction( }, }; - case TransactionType.musdConversion: { - let conversionAmount: string | undefined; - - if (txData && txData.length >= 138) { - try { - conversionAmount = BigInt(`0x${txData.slice(74, 138)}`).toString(); - } catch { - conversionAmount = undefined; - } - } - - return { - type: 'convert', - ...common, - data: { - from, - sourceToken: transactionGroup.sourceToken, - destinationToken: getContractTokenWithKnownMetadata({ - amount: conversionAmount, - transaction: initialTransaction, - direction: 'in', - contractAddress: to, - }), - }, - }; - } - case TransactionType.bridgeApproval: case TransactionType.shieldSubscriptionApprove: case TransactionType.swapApproval: