From 80f3c41e14fbd2cc13b0eb7ffce6a69ceb294c2c Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Mon, 5 May 2025 14:43:39 +0100 Subject: [PATCH 01/13] chore: refactor to support devnnet chains and organizes data structure --- .../MultichainTransactionsController.test.ts | 199 ++++++++++++------ .../src/MultichainTransactionsController.ts | 134 +++++++----- 2 files changed, 216 insertions(+), 117 deletions(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts index 6aafbdc680e..1b1321c4638 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts @@ -228,14 +228,13 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); - expect(controller.state).toStrictEqual({ - nonEvmTransactions: { - [mockBtcAccount.id]: { - transactions: mockTransactionResult.data, - next: null, - lastUpdated: expect.any(Number), - }, - }, + const chain = mockTransactionResult.data[0].chain; + expect( + controller.state.nonEvmTransactions[mockBtcAccount.id][chain], + ).toStrictEqual({ + transactions: mockTransactionResult.data, + next: null, + lastUpdated: expect.any(Number), }); }); @@ -244,22 +243,20 @@ describe('MultichainTransactionsController', () => { setupController(); await controller.updateTransactionsForAccount(mockBtcAccount.id); - expect(controller.state).toStrictEqual({ - nonEvmTransactions: { - [mockBtcAccount.id]: { - transactions: mockTransactionResult.data, - next: null, - lastUpdated: expect.any(Number), - }, - }, + + const chain = mockTransactionResult.data[0].chain; + expect( + controller.state.nonEvmTransactions[mockBtcAccount.id][chain], + ).toStrictEqual({ + transactions: mockTransactionResult.data, + next: null, + lastUpdated: expect.any(Number), }); messenger.publish('AccountsController:accountRemoved', mockBtcAccount.id); mockListMultichainAccounts.mockReturnValue([]); - expect(controller.state).toStrictEqual({ - nonEvmTransactions: {}, - }); + expect(controller.state.nonEvmTransactions).toStrictEqual({}); }); it('does not track balances for EVM accounts', async () => { @@ -282,8 +279,9 @@ describe('MultichainTransactionsController', () => { const { controller } = setupController(); await controller.updateTransactionsForAccount(mockBtcAccount.id); + const chain = mockTransactionResult.data[0].chain; expect( - controller.state.nonEvmTransactions[mockBtcAccount.id], + controller.state.nonEvmTransactions[mockBtcAccount.id][chain], ).toStrictEqual({ transactions: mockTransactionResult.data, next: null, @@ -291,7 +289,7 @@ describe('MultichainTransactionsController', () => { }); }); - it('filters out non-mainnet Solana transactions', async () => { + it('stores transactions by chain for accounts', async () => { const mockSolTransaction = { account: mockSolAccount.id, type: 'send' as const, @@ -327,10 +325,6 @@ describe('MultichainTransactionsController', () => { ], next: null, }; - // First transaction must be the mainnet one (for the test), so we assert this. - expect(mockSolTransactions.data[0].chain).toStrictEqual( - MultichainNetwork.Solana, - ); const { controller, mockSnapHandleRequest } = setupController({ mocks: { @@ -341,10 +335,42 @@ describe('MultichainTransactionsController', () => { await controller.updateTransactionsForAccount(mockSolAccount.id); - const { transactions } = - controller.state.nonEvmTransactions[mockSolAccount.id]; - expect(transactions).toHaveLength(1); - expect(transactions[0]).toStrictEqual(mockSolTransactions.data[0]); // First transaction is the mainnet one. + expect( + Object.keys(controller.state.nonEvmTransactions[mockSolAccount.id]), + ).toHaveLength(4); + + expect( + controller.state.nonEvmTransactions[mockSolAccount.id][ + MultichainNetwork.Solana + ].transactions, + ).toHaveLength(1); + expect( + controller.state.nonEvmTransactions[mockSolAccount.id][ + MultichainNetwork.Solana + ].transactions[0], + ).toStrictEqual(mockSolTransactions.data[0]); + + expect( + controller.state.nonEvmTransactions[mockSolAccount.id][ + MultichainNetwork.SolanaTestnet + ].transactions, + ).toHaveLength(1); + expect( + controller.state.nonEvmTransactions[mockSolAccount.id][ + MultichainNetwork.SolanaTestnet + ].transactions[0], + ).toStrictEqual(mockSolTransactions.data[1]); + + expect( + controller.state.nonEvmTransactions[mockSolAccount.id][ + MultichainNetwork.SolanaDevnet + ].transactions, + ).toHaveLength(1); + expect( + controller.state.nonEvmTransactions[mockSolAccount.id][ + MultichainNetwork.SolanaDevnet + ].transactions[0], + ).toStrictEqual(mockSolTransactions.data[2]); }); it('handles pagination when fetching transactions', async () => { @@ -455,31 +481,37 @@ describe('MultichainTransactionsController', () => { id: TEST_ACCOUNT_ID, }; + const chain = mockTransactionResult.data[0].chain; const existingTransaction = { ...mockTransactionResult.data[0], id: '123', status: 'confirmed' as const, + chain, }; const newTransaction = { ...mockTransactionResult.data[0], id: '456', status: 'submitted' as const, + chain, }; const updatedExistingTransaction = { ...mockTransactionResult.data[0], id: '123', status: 'failed' as const, + chain, }; const { controller, messenger } = setupController({ state: { nonEvmTransactions: { [mockSolAccountWithId.id]: { - transactions: [existingTransaction], - next: null, - lastUpdated: Date.now(), + [chain]: { + transactions: [existingTransaction], + next: null, + lastUpdated: Date.now(), + }, }, }, }, @@ -494,7 +526,8 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); const finalTransactions = - controller.state.nonEvmTransactions[mockSolAccountWithId.id].transactions; + controller.state.nonEvmTransactions[mockSolAccountWithId.id][chain] + .transactions; expect(finalTransactions).toStrictEqual([ updatedExistingTransaction, newTransaction, @@ -502,13 +535,16 @@ describe('MultichainTransactionsController', () => { }); it('handles empty transaction updates gracefully', async () => { + const chain = mockTransactionResult.data[0].chain; const { controller, messenger } = setupController({ state: { nonEvmTransactions: { [TEST_ACCOUNT_ID]: { - transactions: [], - next: null, - lastUpdated: Date.now(), + [chain]: { + transactions: [], + next: null, + lastUpdated: Date.now(), + }, }, }, }, @@ -520,7 +556,9 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); - expect(controller.state.nonEvmTransactions[TEST_ACCOUNT_ID]).toStrictEqual({ + expect( + controller.state.nonEvmTransactions[TEST_ACCOUNT_ID][chain], + ).toStrictEqual({ transactions: [], next: null, lastUpdated: expect.any(Number), @@ -528,6 +566,8 @@ describe('MultichainTransactionsController', () => { }); it('initializes new accounts with empty transactions array when receiving updates', async () => { + const chain = mockTransactionResult.data[0].chain; + const { controller, messenger } = setupController({ state: { nonEvmTransactions: {}, @@ -541,21 +581,26 @@ describe('MultichainTransactionsController', () => { }); await waitForAllPromises(); - - expect(controller.state.nonEvmTransactions[NEW_ACCOUNT_ID]).toStrictEqual({ + expect( + controller.state.nonEvmTransactions[NEW_ACCOUNT_ID][chain], + ).toStrictEqual({ transactions: mockTransactionResult.data, + next: null, lastUpdated: expect.any(Number), }); }); it('handles undefined transactions in update payload', async () => { + const chain = mockTransactionResult.data[0].chain; const { controller, messenger } = setupController({ state: { nonEvmTransactions: { [TEST_ACCOUNT_ID]: { - transactions: [], - next: null, - lastUpdated: Date.now(), + [chain]: { + transactions: [], + next: null, + lastUpdated: Date.now(), + }, }, }, }, @@ -570,8 +615,10 @@ describe('MultichainTransactionsController', () => { const initialStateSnapshot = { [TEST_ACCOUNT_ID]: { - ...controller.state.nonEvmTransactions[TEST_ACCOUNT_ID], - lastUpdated: expect.any(Number), + [chain]: { + ...controller.state.nonEvmTransactions[TEST_ACCOUNT_ID][chain], + lastUpdated: expect.any(Number), + }, }, }; @@ -587,6 +634,7 @@ describe('MultichainTransactionsController', () => { }); it('sorts transactions by timestamp (newest first)', async () => { + const chain = mockTransactionResult.data[0].chain; const olderTransaction = { ...mockTransactionResult.data[0], id: '123', @@ -602,9 +650,11 @@ describe('MultichainTransactionsController', () => { state: { nonEvmTransactions: { [TEST_ACCOUNT_ID]: { - transactions: [olderTransaction], - next: null, - lastUpdated: Date.now(), + [chain]: { + transactions: [olderTransaction], + next: null, + lastUpdated: Date.now(), + }, }, }, }, @@ -619,7 +669,7 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); const finalTransactions = - controller.state.nonEvmTransactions[TEST_ACCOUNT_ID].transactions; + controller.state.nonEvmTransactions[TEST_ACCOUNT_ID][chain].transactions; expect(finalTransactions).toStrictEqual([ newerTransaction, olderTransaction, @@ -627,6 +677,7 @@ describe('MultichainTransactionsController', () => { }); it('sorts transactions by timestamp and handles null timestamps', async () => { + const chain = mockTransactionResult.data[0].chain; const nullTimestampTx1 = { ...mockTransactionResult.data[0], id: '123', @@ -647,9 +698,11 @@ describe('MultichainTransactionsController', () => { state: { nonEvmTransactions: { [TEST_ACCOUNT_ID]: { - transactions: [nullTimestampTx1], - next: null, - lastUpdated: Date.now(), + [chain]: { + transactions: [nullTimestampTx1], + next: null, + lastUpdated: Date.now(), + }, }, }, }, @@ -664,7 +717,7 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); const finalTransactions = - controller.state.nonEvmTransactions[TEST_ACCOUNT_ID].transactions; + controller.state.nonEvmTransactions[TEST_ACCOUNT_ID][chain].transactions; expect(finalTransactions).toStrictEqual([ withTimestampTx, nullTimestampTx1, @@ -685,8 +738,10 @@ describe('MultichainTransactionsController', () => { mockGetKeyringState.mockReturnValue({ isUnlocked: true }); await controller.updateTransactionsForAccount(mockBtcAccount.id); + + const chain = mockTransactionResult.data[0].chain; expect( - controller.state.nonEvmTransactions[mockBtcAccount.id], + controller.state.nonEvmTransactions[mockBtcAccount.id][chain], ).toStrictEqual({ transactions: mockTransactionResult.data, next: null, @@ -694,7 +749,7 @@ describe('MultichainTransactionsController', () => { }); }); - it('filters out non-mainnet Solana transactions in transaction updates', async () => { + it('updates transactions by chain when receiving transaction updates', async () => { const mockSolAccountWithId = { ...mockSolAccount, id: TEST_ACCOUNT_ID, @@ -732,9 +787,11 @@ describe('MultichainTransactionsController', () => { state: { nonEvmTransactions: { [mockSolAccountWithId.id]: { - transactions: [], - next: null, - lastUpdated: Date.now(), + [MultichainNetwork.Solana]: { + transactions: [], + next: null, + lastUpdated: Date.now(), + }, }, }, }, @@ -748,11 +805,31 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); - const finalTransactions = - controller.state.nonEvmTransactions[mockSolAccountWithId.id].transactions; + expect( + Object.keys(controller.state.nonEvmTransactions[mockSolAccountWithId.id]), + ).toHaveLength(2); - expect(finalTransactions).toHaveLength(1); - expect(finalTransactions[0]).toBe(mainnetTransaction); + expect( + controller.state.nonEvmTransactions[mockSolAccountWithId.id][ + MultichainNetwork.Solana + ].transactions, + ).toHaveLength(1); + expect( + controller.state.nonEvmTransactions[mockSolAccountWithId.id][ + MultichainNetwork.Solana + ].transactions[0], + ).toBe(mainnetTransaction); + + expect( + controller.state.nonEvmTransactions[mockSolAccountWithId.id][ + MultichainNetwork.SolanaDevnet + ].transactions, + ).toHaveLength(1); + expect( + controller.state.nonEvmTransactions[mockSolAccountWithId.id][ + MultichainNetwork.SolanaDevnet + ].transactions[0], + ).toBe(devnetTransaction); }); it('publishes transactionConfirmed event when transaction is confirmed', async () => { diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index fc8b09b239a..e1f712fe0a0 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -51,7 +51,9 @@ export type PaginationOptions = { */ export type MultichainTransactionsControllerState = { nonEvmTransactions: { - [accountId: string]: TransactionStateEntry; + [accountId: string]: { + [chain: string]: TransactionStateEntry; + }; }; }; @@ -156,7 +158,7 @@ const multichainTransactionsControllerMetadata = { }; /** - * The state of transactions for a specific account. + * The state of transactions for a specific chain. */ export type TransactionStateEntry = { transactions: Transaction[]; @@ -285,16 +287,35 @@ export class MultichainTransactionsController extends BaseController< { limit: 10 }, ); - const transactions = this.#filterTransactions(response.data); + const transactionsByChain: Record = {}; + + response.data.forEach((transaction) => { + const chain = transaction.chain as string; + if (!transactionsByChain[chain]) { + transactionsByChain[chain] = []; + } + transactionsByChain[chain].push(transaction); + }); + + const chainUpdates = Object.entries(transactionsByChain).map( + ([chain, transactions]) => ({ + chain, + entry: { + transactions, + next: response.next, + lastUpdated: Date.now(), + }, + }), + ); this.update((state: Draft) => { - const entry: TransactionStateEntry = { - transactions, - next: response.next, - lastUpdated: Date.now(), - }; + if (!state.nonEvmTransactions[account.id]) { + state.nonEvmTransactions[account.id] = {}; + } - Object.assign(state.nonEvmTransactions, { [account.id]: entry }); + chainUpdates.forEach(({ chain, entry }) => { + state.nonEvmTransactions[account.id][chain] = entry; + }); }); } } catch (error) { @@ -305,27 +326,6 @@ export class MultichainTransactionsController extends BaseController< } } - /** - * Filters transactions to only include mainnet Solana transactions for Solana chains. - * Non-Solana chain transactions are kept as is. - * - * @param transactions - Array of transactions to filter - * @returns Filtered transactions array - */ - #filterTransactions(transactions: Transaction[]): Transaction[] { - return transactions.filter((tx) => { - const chain = tx.chain as MultichainNetwork; - const { namespace } = parseCaipChainId(chain); - - // Enum comparison is safe here as we control both enum values - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - if (namespace === KnownCaipNamespace.Solana) { - return chain === MultichainNetwork.Solana; - } - return true; - }); - } - /** * Checks for non-EVM accounts. * @@ -395,7 +395,10 @@ export class MultichainTransactionsController extends BaseController< #handleOnAccountTransactionsUpdated( transactionsUpdate: AccountTransactionsUpdatedEventPayload, ): void { - const updatedTransactions: Record = {}; + const updatedTransactions: Record< + string, + Record + > = {}; const transactionsToPublish: Transaction[] = []; if (!transactionsUpdate?.transactions) { @@ -404,45 +407,64 @@ export class MultichainTransactionsController extends BaseController< Object.entries(transactionsUpdate.transactions).forEach( ([accountId, newTransactions]) => { - // Account might not have any transactions yet, so use `[]` in that case. - const oldTransactions = - this.state.nonEvmTransactions[accountId]?.transactions ?? []; + updatedTransactions[accountId] = {}; - const filteredNewTransactions = - this.#filterTransactions(newTransactions); + newTransactions.forEach((tx) => { + const chain = tx.chain as string; - // Uses a `Map` to deduplicate transactions by ID, ensuring we keep the latest version - // of each transaction while preserving older transactions and transactions from other accounts. - // Transactions are sorted by timestamp (newest first). - const transactions = new Map(); + if (!updatedTransactions[accountId][chain]) { + updatedTransactions[accountId][chain] = []; + } - oldTransactions.forEach((tx) => { - transactions.set(tx.id, tx); - }); - - filteredNewTransactions.forEach((tx) => { - transactions.set(tx.id, tx); + updatedTransactions[accountId][chain].push(tx); transactionsToPublish.push(tx); }); - // Sorted by timestamp (newest first). If the timestamp is not provided, those - // transactions will be put in the end of this list. - updatedTransactions[accountId] = Array.from(transactions.values()).sort( - (a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0), + Object.entries(updatedTransactions[accountId]).forEach( + ([chain, chainTransactions]) => { + // Account might not have any transactions yet, so use `[]` in that case. + const oldTransactions = + this.state.nonEvmTransactions[accountId]?.[chain]?.transactions ?? + []; + + // Uses a `Map` to deduplicate transactions by ID, ensuring we keep the latest version + // of each transaction while preserving older transactions and transactions from other accounts. + // Transactions are sorted by timestamp (newest first). + const transactions = new Map(); + + oldTransactions.forEach((tx) => { + transactions.set(tx.id, tx); + }); + + chainTransactions.forEach((tx) => { + transactions.set(tx.id, tx); + }); + + // Sorted by timestamp (newest first). If the timestamp is not provided, those + // transactions will be put in the end of this list. + updatedTransactions[accountId][chain] = Array.from( + transactions.values(), + ).sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0)); + }, ); }, ); this.update((state) => { - Object.entries(updatedTransactions).forEach( - ([accountId, transactions]) => { - state.nonEvmTransactions[accountId] = { - ...state.nonEvmTransactions[accountId], + Object.entries(updatedTransactions).forEach(([accountId, chainsData]) => { + if (!state.nonEvmTransactions[accountId]) { + state.nonEvmTransactions[accountId] = {}; + } + + Object.entries(chainsData).forEach(([chain, transactions]) => { + state.nonEvmTransactions[accountId][chain] = { + ...state.nonEvmTransactions[accountId][chain], transactions, + next: null, lastUpdated: Date.now(), }; - }, - ); + }); + }); }); // After we update the state, publish the events for new/updated transactions From e7e1c18e866ddfa3db5efee7b9096fb30d8c2f7f Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Mon, 5 May 2025 16:06:48 +0100 Subject: [PATCH 02/13] chore: changelog update --- packages/multichain-transactions-controller/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/multichain-transactions-controller/CHANGELOG.md b/packages/multichain-transactions-controller/CHANGELOG.md index 544831b98e8..cf759da5e96 100644 --- a/packages/multichain-transactions-controller/CHANGELOG.md +++ b/packages/multichain-transactions-controller/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Bump `@metamask/snaps-controllers` peer dependency from ^9.19.0 to ^11.0.0 ([#5639](https://github.com/MetaMask/core/pull/5639)) - Bump `@metamask/snaps-sdk` from ^6.17.1 to ^6.22.0 ([#5639](https://github.com/MetaMask/core/pull/5639)) - Bump `@metamask/snaps-utils` from ^8.10.0 to ^9.2.0 ([#5639](https://github.com/MetaMask/core/pull/5639)) +- **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) ## [0.9.0] From 2f69a0be581630f5d833d399bd4dc7d03d513c62 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Mon, 5 May 2025 16:36:41 +0100 Subject: [PATCH 03/13] chore: lint update --- .../MultichainTransactionsController.test.ts | 20 +++++++++---------- .../src/MultichainTransactionsController.ts | 9 +-------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts index 1b1321c4638..326f7d6a6a7 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.test.ts @@ -228,7 +228,7 @@ describe('MultichainTransactionsController', () => { await waitForAllPromises(); - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; expect( controller.state.nonEvmTransactions[mockBtcAccount.id][chain], ).toStrictEqual({ @@ -244,7 +244,7 @@ describe('MultichainTransactionsController', () => { await controller.updateTransactionsForAccount(mockBtcAccount.id); - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; expect( controller.state.nonEvmTransactions[mockBtcAccount.id][chain], ).toStrictEqual({ @@ -279,7 +279,7 @@ describe('MultichainTransactionsController', () => { const { controller } = setupController(); await controller.updateTransactionsForAccount(mockBtcAccount.id); - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; expect( controller.state.nonEvmTransactions[mockBtcAccount.id][chain], ).toStrictEqual({ @@ -481,7 +481,7 @@ describe('MultichainTransactionsController', () => { id: TEST_ACCOUNT_ID, }; - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; const existingTransaction = { ...mockTransactionResult.data[0], id: '123', @@ -535,7 +535,7 @@ describe('MultichainTransactionsController', () => { }); it('handles empty transaction updates gracefully', async () => { - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; const { controller, messenger } = setupController({ state: { nonEvmTransactions: { @@ -566,7 +566,7 @@ describe('MultichainTransactionsController', () => { }); it('initializes new accounts with empty transactions array when receiving updates', async () => { - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; const { controller, messenger } = setupController({ state: { @@ -591,7 +591,7 @@ describe('MultichainTransactionsController', () => { }); it('handles undefined transactions in update payload', async () => { - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; const { controller, messenger } = setupController({ state: { nonEvmTransactions: { @@ -634,7 +634,7 @@ describe('MultichainTransactionsController', () => { }); it('sorts transactions by timestamp (newest first)', async () => { - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; const olderTransaction = { ...mockTransactionResult.data[0], id: '123', @@ -677,7 +677,7 @@ describe('MultichainTransactionsController', () => { }); it('sorts transactions by timestamp and handles null timestamps', async () => { - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; const nullTimestampTx1 = { ...mockTransactionResult.data[0], id: '123', @@ -739,7 +739,7 @@ describe('MultichainTransactionsController', () => { await controller.updateTransactionsForAccount(mockBtcAccount.id); - const chain = mockTransactionResult.data[0].chain; + const { chain } = mockTransactionResult.data[0]; expect( controller.state.nonEvmTransactions[mockBtcAccount.id][chain], ).toStrictEqual({ diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index e1f712fe0a0..5068e05a6b8 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -22,16 +22,9 @@ import { KeyringClient } from '@metamask/keyring-snap-client'; import type { HandleSnapRequest } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { HandlerType } from '@metamask/snaps-utils'; -import { - KnownCaipNamespace, - parseCaipChainId, - type Json, - type JsonRpcRequest, -} from '@metamask/utils'; +import { type Json, type JsonRpcRequest } from '@metamask/utils'; import type { Draft } from 'immer'; -import { MultichainNetwork } from './constants'; - const controllerName = 'MultichainTransactionsController'; /** From c5ce1fc6acda31eb0c5229036f27e084db06ec66 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Tue, 6 May 2025 14:40:58 +0100 Subject: [PATCH 04/13] chore: changelog update --- packages/multichain-transactions-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/multichain-transactions-controller/CHANGELOG.md b/packages/multichain-transactions-controller/CHANGELOG.md index b9c398366ab..8af1b088812 100644 --- a/packages/multichain-transactions-controller/CHANGELOG.md +++ b/packages/multichain-transactions-controller/CHANGELOG.md @@ -11,9 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) - **BREAKING:** Bump `@metamask/accounts-controllers` peer dependency to `^28.0.0` ([#5763](https://github.com/MetaMask/core/pull/5763)) - **BREAKING:** Bump `@metamask/snaps-controllers` peer dependency to `^11.0.0` ([#5639](https://github.com/MetaMask/core/pull/5639)) -- **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) - Bump `@metamask/base-controller` from `^8.0.0` to `^8.0.1` ([#5722](https://github.com/MetaMask/core/pull/5722)) - Bump `@metamask/snaps-sdk` from `^6.17.1` to `^6.22.0` ([#5639](https://github.com/MetaMask/core/pull/5639)) - Bump `@metamask/snaps-utils` from `^8.10.0` to `^9.2.0` ([#5639](https://github.com/MetaMask/core/pull/5639)) From 862f3db33fc3c7f570a54b33b4305a3d60527b51 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Tue, 6 May 2025 14:45:36 +0100 Subject: [PATCH 05/13] chore: changelog update --- packages/multichain-transactions-controller/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/multichain-transactions-controller/CHANGELOG.md b/packages/multichain-transactions-controller/CHANGELOG.md index 8af1b088812..7bf7657b8b8 100644 --- a/packages/multichain-transactions-controller/CHANGELOG.md +++ b/packages/multichain-transactions-controller/CHANGELOG.md @@ -7,11 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) + ## [0.10.0] ### Changed -- **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) - **BREAKING:** Bump `@metamask/accounts-controllers` peer dependency to `^28.0.0` ([#5763](https://github.com/MetaMask/core/pull/5763)) - **BREAKING:** Bump `@metamask/snaps-controllers` peer dependency to `^11.0.0` ([#5639](https://github.com/MetaMask/core/pull/5639)) - Bump `@metamask/base-controller` from `^8.0.0` to `^8.0.1` ([#5722](https://github.com/MetaMask/core/pull/5722)) From dbe188286c36c5bd924f1d5fa4471a007d9c1b8f Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Tue, 6 May 2025 14:49:01 +0100 Subject: [PATCH 06/13] chore: changelog update --- packages/multichain-transactions-controller/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/multichain-transactions-controller/CHANGELOG.md b/packages/multichain-transactions-controller/CHANGELOG.md index 7bf7657b8b8..a8468d1f74f 100644 --- a/packages/multichain-transactions-controller/CHANGELOG.md +++ b/packages/multichain-transactions-controller/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + - **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) ## [0.10.0] From 4316d743f1f078cb64905f1a8a7abf705edbbce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Wed, 7 May 2025 13:36:35 +0100 Subject: [PATCH 07/13] Apply suggestions from code review Co-authored-by: Charly Chevalier --- packages/multichain-transactions-controller/CHANGELOG.md | 3 ++- .../src/MultichainTransactionsController.ts | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/multichain-transactions-controller/CHANGELOG.md b/packages/multichain-transactions-controller/CHANGELOG.md index a8468d1f74f..98c297d2d39 100644 --- a/packages/multichain-transactions-controller/CHANGELOG.md +++ b/packages/multichain-transactions-controller/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING:** Updates the support for Solana devnnet chain and txs data structure ([#5756](https://github.com/MetaMask/core/pull/5756)) +- **BREAKING:** Store transactions by chain IDs ([#5756](https://github.com/MetaMask/core/pull/5756)) +- Remove Solana mainnet filtering to support other Solana networks (devnet, testnet) ([#5756](https://github.com/MetaMask/core/pull/5756)) ## [0.10.0] diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index 5068e05a6b8..0fb4460d66a 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -45,7 +45,7 @@ export type PaginationOptions = { export type MultichainTransactionsControllerState = { nonEvmTransactions: { [accountId: string]: { - [chain: string]: TransactionStateEntry; + [chain: CaipChainId]: TransactionStateEntry; }; }; }; @@ -283,7 +283,7 @@ export class MultichainTransactionsController extends BaseController< const transactionsByChain: Record = {}; response.data.forEach((transaction) => { - const chain = transaction.chain as string; + const { chain } = transaction; if (!transactionsByChain[chain]) { transactionsByChain[chain] = []; } @@ -403,7 +403,7 @@ export class MultichainTransactionsController extends BaseController< updatedTransactions[accountId] = {}; newTransactions.forEach((tx) => { - const chain = tx.chain as string; + const { chain } = tx; if (!updatedTransactions[accountId][chain]) { updatedTransactions[accountId][chain] = []; From 19aaf8397aef89eed9b558dccfb5b78dd09479c7 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Wed, 7 May 2025 13:38:37 +0100 Subject: [PATCH 08/13] chore: update type --- .../src/MultichainTransactionsController.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index 0fb4460d66a..cc5e32fb7a0 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -22,7 +22,7 @@ import { KeyringClient } from '@metamask/keyring-snap-client'; import type { HandleSnapRequest } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { HandlerType } from '@metamask/snaps-utils'; -import { type Json, type JsonRpcRequest } from '@metamask/utils'; +import { CaipChainId, type Json, type JsonRpcRequest } from '@metamask/utils'; import type { Draft } from 'immer'; const controllerName = 'MultichainTransactionsController'; @@ -284,6 +284,7 @@ export class MultichainTransactionsController extends BaseController< response.data.forEach((transaction) => { const { chain } = transaction; + if (!transactionsByChain[chain]) { transactionsByChain[chain] = []; } @@ -307,7 +308,7 @@ export class MultichainTransactionsController extends BaseController< } chainUpdates.forEach(({ chain, entry }) => { - state.nonEvmTransactions[account.id][chain] = entry; + state.nonEvmTransactions[account.id][chain as CaipChainId] = entry; }); }); } @@ -417,8 +418,8 @@ export class MultichainTransactionsController extends BaseController< ([chain, chainTransactions]) => { // Account might not have any transactions yet, so use `[]` in that case. const oldTransactions = - this.state.nonEvmTransactions[accountId]?.[chain]?.transactions ?? - []; + this.state.nonEvmTransactions[accountId]?.[chain as CaipChainId] + ?.transactions ?? []; // Uses a `Map` to deduplicate transactions by ID, ensuring we keep the latest version // of each transaction while preserving older transactions and transactions from other accounts. @@ -450,8 +451,7 @@ export class MultichainTransactionsController extends BaseController< } Object.entries(chainsData).forEach(([chain, transactions]) => { - state.nonEvmTransactions[accountId][chain] = { - ...state.nonEvmTransactions[accountId][chain], + state.nonEvmTransactions[accountId][chain as CaipChainId] = { transactions, next: null, lastUpdated: Date.now(), From 998d1d47c9d4af4bb077199702cf78091ebcdb92 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Wed, 7 May 2025 13:54:01 +0100 Subject: [PATCH 09/13] chore: lint fix --- .../src/MultichainTransactionsController.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index cc5e32fb7a0..2a36fec8f2b 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -22,7 +22,11 @@ import { KeyringClient } from '@metamask/keyring-snap-client'; import type { HandleSnapRequest } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { HandlerType } from '@metamask/snaps-utils'; -import { CaipChainId, type Json, type JsonRpcRequest } from '@metamask/utils'; +import { + type CaipChainId, + type Json, + type JsonRpcRequest, +} from '@metamask/utils'; import type { Draft } from 'immer'; const controllerName = 'MultichainTransactionsController'; From a5da45655599ae1a2adc4ad9b11ee88bd2a2375b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Wed, 7 May 2025 16:57:50 +0100 Subject: [PATCH 10/13] Update packages/multichain-transactions-controller/src/MultichainTransactionsController.ts Co-authored-by: Charly Chevalier --- .../src/MultichainTransactionsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index 2a36fec8f2b..f18bfbcace3 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -284,7 +284,7 @@ export class MultichainTransactionsController extends BaseController< { limit: 10 }, ); - const transactionsByChain: Record = {}; + const transactionsByChain: Record = {}; response.data.forEach((transaction) => { const { chain } = transaction; From 445b7b2d13d0a74296485e51563ff0fd185a496d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Wed, 7 May 2025 16:57:58 +0100 Subject: [PATCH 11/13] Update packages/multichain-transactions-controller/src/MultichainTransactionsController.ts Co-authored-by: Charly Chevalier --- .../src/MultichainTransactionsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index f18bfbcace3..492bfe1cc00 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -312,7 +312,7 @@ export class MultichainTransactionsController extends BaseController< } chainUpdates.forEach(({ chain, entry }) => { - state.nonEvmTransactions[account.id][chain as CaipChainId] = entry; + state.nonEvmTransactions[account.id][chain] = entry; }); }); } From aa3eec76e718c964a667eb510ee87dc1de3eddae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3nio=20Regadas?= Date: Wed, 7 May 2025 16:58:09 +0100 Subject: [PATCH 12/13] Update packages/multichain-transactions-controller/src/MultichainTransactionsController.ts Co-authored-by: Charly Chevalier --- .../src/MultichainTransactionsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index 492bfe1cc00..11bd4c72d04 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -395,7 +395,7 @@ export class MultichainTransactionsController extends BaseController< ): void { const updatedTransactions: Record< string, - Record + Record > = {}; const transactionsToPublish: Transaction[] = []; From e1f00ff776068c4f73c5059d7c3ad6c14a07f3d5 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Wed, 7 May 2025 17:00:11 +0100 Subject: [PATCH 13/13] chore: lint --- .../src/MultichainTransactionsController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts index 11bd4c72d04..b36b3c667e3 100644 --- a/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts +++ b/packages/multichain-transactions-controller/src/MultichainTransactionsController.ts @@ -312,7 +312,7 @@ export class MultichainTransactionsController extends BaseController< } chainUpdates.forEach(({ chain, entry }) => { - state.nonEvmTransactions[account.id][chain] = entry; + state.nonEvmTransactions[account.id][chain as CaipChainId] = entry; }); }); } @@ -440,7 +440,7 @@ export class MultichainTransactionsController extends BaseController< // Sorted by timestamp (newest first). If the timestamp is not provided, those // transactions will be put in the end of this list. - updatedTransactions[accountId][chain] = Array.from( + updatedTransactions[accountId][chain as CaipChainId] = Array.from( transactions.values(), ).sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0)); },