diff --git a/.yarnrc.yml b/.yarnrc.yml index d3acfcab..e3be7f07 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -17,8 +17,10 @@ nodeLinker: node-modules npmMinimalAgeGate: 4320 npmPreapprovedPackages: + - "@metamask/assets-controller" - "@metamask/keyring-api" - "@metamask/keyring-snap-sdk" + - "@metamask-previews/snap-networks-utils" plugins: - path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs diff --git a/package.json b/package.json index 4377470a..baf59957 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "resolutions": { "@metamask/snaps-execution-environments": "11.2.0", - "@metamask/snaps-sdk": "^11.1.1", + "@metamask/snaps-sdk": "^11.2.0", "@types/react": "18.2.4", "@types/react-dom": "18.2.4" }, diff --git a/packages/snap/jest.setup.ts b/packages/snap/jest.setup.ts index df1465e9..1c7c8359 100644 --- a/packages/snap/jest.setup.ts +++ b/packages/snap/jest.setup.ts @@ -1,7 +1,9 @@ import { jest } from '@jest/globals'; +import type { SimulationUserOptions } from '@metamask/snaps-simulation'; import BigNumber from 'bignumber.js'; import dotenv from 'dotenv'; +import { registerCoreAssetsControllerHandlers } from './src/core/test/helpers/registerCoreAssetsControllerHandlers'; import logger from './src/core/utils/logger'; dotenv.config(); @@ -9,6 +11,36 @@ dotenv.config(); // Lowest precision we ever go for: MicroLamports represented in Sol amount BigNumber.config({ EXPONENTIAL_AT: 16 }); +type SnapsTestEnvironment = { + installSnap: ( + snapId?: string, + options?: { options?: SimulationUserOptions }, + ) => Promise<{ + controllerMessenger: Parameters< + typeof registerCoreAssetsControllerHandlers + >[0]; + }>; +}; + +const { snapsEnvironment } = globalThis as { + snapsEnvironment?: SnapsTestEnvironment; +}; + +if (snapsEnvironment) { + const originalInstallSnap = + snapsEnvironment.installSnap.bind(snapsEnvironment); + jest + .spyOn(snapsEnvironment, 'installSnap') + .mockImplementation(async (snapId, options = {}) => { + const installed = await originalInstallSnap(snapId, options); + registerCoreAssetsControllerHandlers( + installed.controllerMessenger, + options.options ?? {}, + ); + return installed; + }); +} + // Mock the console methods jest.spyOn(logger, 'log').mockImplementation(() => { /* no-op */ diff --git a/packages/snap/package.json b/packages/snap/package.json index a684399e..a0e08ff1 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -39,22 +39,28 @@ "prepublishOnly": "mm-snap manifest", "serve": "mm-snap serve", "start": "node scripts/update-manifest-local.js && concurrently \"mm-snap watch\" \"yarn locale:build:watch\"", - "test": "jest --passWithNoTests --runInBand", + "test": "ENVIRONMENT=test yarn build && jest --passWithNoTests --runInBand", "test:core": "jest src/core --passWithNoTests", "test:core:watch": "yarn test:core --watch", "test:features": "jest src/features --passWithNoTests --runInBand", "test:features:watch": "yarn test:features --watch", "test:watch": "yarn test --watch" }, + "dependencies": { + "@metamask-previews/snap-networks-utils": "0.0.0-preview-8589132" + }, "devDependencies": { "@jest/globals": "^29.5.0", + "@metamask/assets-controller": "13.0.0", "@metamask/auto-changelog": "4.0.0", "@metamask/key-tree": "9.1.2", "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-snap-sdk": "^9.2.0", + "@metamask/messenger": "^2.0.0", + "@metamask/remote-feature-flag-controller": "5.0.0", "@metamask/snaps-cli": "^8.4.1", "@metamask/snaps-jest": "^10.2.0", - "@metamask/snaps-sdk": "^11.1.1", + "@metamask/snaps-sdk": "^11.2.0", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "^11.9.0", "@noble/ed25519": "2.1.0", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index b1ed8971..05a08a6f 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "IFUCW/5nFjm2PtAdzVtjtdmN9x8sVAYMvTCnp1fivHU=", + "shasum": "KRhMEDiB6VpAZciUc7txZxIbo2D1q3UMDo/9givv7X8=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -88,8 +88,16 @@ "snap_manageAccounts": {}, "snap_manageState": {}, "snap_dialog": {}, - "snap_getPreferences": {} + "snap_getPreferences": {}, + "endowment:messenger": { + "actions": [ + "AssetsController:getAccountAssetByID", + "AssetsController:getAccountAssetsByIDs", + "AssetsController:getAccountAssetsByScope", + "RemoteFeatureFlagController:getState" + ] + } }, - "platformVersion": "11.1.1", + "platformVersion": "11.2.0", "manifestVersion": "0.1" } diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.test.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.test.tsx index a0eb261f..64877134 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.test.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.test.tsx @@ -1,4 +1,10 @@ -import { assetsService, priceApiClient, state } from '../../../../snapContext'; +import { + accountsService, + assetsService, + configProvider, + priceApiClient, + state, +} from '../../../../snapContext'; import { KnownCaip19Id } from '../../../constants/solana'; import { trackError } from '../../../utils/errors'; import { @@ -33,9 +39,15 @@ jest.mock('../../../../features/send/Send', () => ({ })); jest.mock('../../../../snapContext', () => ({ - assetsService: { + accountsService: { getAll: jest.fn(), }, + assetsService: { + getAccountAssetsByScope: jest.fn(), + }, + configProvider: { + getActiveNetworks: jest.fn(), + }, priceApiClient: { getMultipleSpotPrices: jest.fn(), }, @@ -50,7 +62,13 @@ const setupTest = () => { request: jest.fn(), }; - (assetsService.getAll as jest.Mock).mockResolvedValue([ + (accountsService.getAll as jest.Mock).mockResolvedValue([ + { id: 'account-1' }, + ]); + (configProvider.getActiveNetworks as jest.Mock).mockResolvedValue([ + 'solana:mainnet', + ]); + (assetsService.getAccountAssetsByScope as jest.Mock).mockResolvedValue([ { assetType: KnownCaip19Id.SolMainnet, }, diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx index 68e38712..8efdd9fb 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -3,7 +3,13 @@ import type { OnCronjobHandler } from '@metamask/snaps-sdk'; import { DEFAULT_SEND_CONTEXT } from '../../../../features/send/render'; import { Send } from '../../../../features/send/Send'; import type { SendContext } from '../../../../features/send/types'; -import { assetsService, priceApiClient, state } from '../../../../snapContext'; +import { + assetsService, + configProvider, + priceApiClient, + state, + accountsService, +} from '../../../../snapContext'; import type { UnencryptedStateValue } from '../../../services/state/State'; import { trackError } from '../../../utils/errors'; import { @@ -19,13 +25,25 @@ export const refreshSend: OnCronjobHandler = async () => { logger.info(`Background event triggered`); - const [assets, mapInterfaceNameToId, preferences] = await Promise.all([ - assetsService.getAll(), - state.getKey( - 'mapInterfaceNameToId', - ), - getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), - ]); + const [accounts, activeNetworks, mapInterfaceNameToId, preferences] = + await Promise.all([ + accountsService.getAll(), + configProvider.getActiveNetworks(), + state.getKey( + 'mapInterfaceNameToId', + ), + getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), + ]); + + const assets = ( + await Promise.all( + accounts.flatMap((account) => + activeNetworks.map((network) => + assetsService.getAccountAssetsByScope(network, account.id), + ), + ), + ) + ).flat(); const assetTypes = assets.flatMap((asset) => asset.assetType); diff --git a/packages/snap/src/core/handlers/onKeyringRequest/Keyring.test.ts b/packages/snap/src/core/handlers/onKeyringRequest/Keyring.test.ts index 5c27fe3e..3fc76e52 100644 --- a/packages/snap/src/core/handlers/onKeyringRequest/Keyring.test.ts +++ b/packages/snap/src/core/handlers/onKeyringRequest/Keyring.test.ts @@ -103,7 +103,8 @@ describe('SolanaKeyring', () => { mockAssetsService = { fetch: jest.fn().mockResolvedValue(MOCK_ASSET_ENTITIES), saveMany: jest.fn(), - findByAccount: jest.fn(), + getAccountAssetsForAllActiveScopes: jest.fn(), + getAccountAssetsByIDs: jest.fn(), getNativeAssetTypes: jest .fn() .mockReturnValue([KnownCaip19Id.SolMainnet]), @@ -143,7 +144,7 @@ describe('SolanaKeyring', () => { describe('getAccountAssets', () => { it('calls the assets service', async () => { jest - .spyOn(mockAssetsService, 'findByAccount') + .spyOn(mockAssetsService, 'getAccountAssetsForAllActiveScopes') .mockResolvedValue(MOCK_ASSET_ENTITIES); const result = await keyring.getAccountAssets( @@ -158,10 +159,12 @@ describe('SolanaKeyring', () => { }); it('removes token assets with zero balance', async () => { - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - MOCK_ASSET_ENTITY_1, // Token asset with non-zero balance - { ...MOCK_ASSET_ENTITY_2, rawAmount: '0' }, // Token asset with zero balance - ]); + jest + .spyOn(mockAssetsService, 'getAccountAssetsForAllActiveScopes') + .mockResolvedValue([ + MOCK_ASSET_ENTITY_1, // Token asset with non-zero balance + { ...MOCK_ASSET_ENTITY_2, rawAmount: '0' }, // Token asset with zero balance + ]); const result = await keyring.getAccountAssets( MOCK_SOLANA_KEYRING_ACCOUNT_0.id, @@ -171,10 +174,12 @@ describe('SolanaKeyring', () => { }); it('keeps the native asset even if it has zero balance', async () => { - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { ...MOCK_ASSET_ENTITY_0, rawAmount: '0' }, // Native asset with zero balance - { ...MOCK_ASSET_ENTITY_1, rawAmount: '0' }, // Token asset with zero balance - ]); + jest + .spyOn(mockAssetsService, 'getAccountAssetsForAllActiveScopes') + .mockResolvedValue([ + { ...MOCK_ASSET_ENTITY_0, rawAmount: '0' }, // Native asset with zero balance + { ...MOCK_ASSET_ENTITY_1, rawAmount: '0' }, // Token asset with zero balance + ]); const result = await keyring.getAccountAssets( MOCK_SOLANA_KEYRING_ACCOUNT_0.id, @@ -343,9 +348,9 @@ describe('SolanaKeyring', () => { symbol: 4, } as unknown as AssetEntity; - jest - .spyOn(mockAssetsService, 'findByAccount') - .mockResolvedValue([invalidAsset]); + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [KnownCaip19Id.SolMainnet]: invalidAsset, + }); await expect( keyring.getAccountBalances(MOCK_SOLANA_KEYRING_ACCOUNT_1.id, [ @@ -355,10 +360,13 @@ describe('SolanaKeyring', () => { }); it('removes token assets with zero balance', async () => { - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - MOCK_ASSET_ENTITY_1, // Token asset with non-zero balance - { ...MOCK_ASSET_ENTITY_2, rawAmount: '0' }, // Token asset with zero balance - ]); + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [MOCK_ASSET_ENTITY_1.assetType]: MOCK_ASSET_ENTITY_1, + [MOCK_ASSET_ENTITY_2.assetType]: { + ...MOCK_ASSET_ENTITY_2, + rawAmount: '0', + }, + }); const result = await keyring.getAccountBalances( MOCK_SOLANA_KEYRING_ACCOUNT_0.id, @@ -374,10 +382,16 @@ describe('SolanaKeyring', () => { }); it('keeps the native asset even if it has zero balance', async () => { - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { ...MOCK_ASSET_ENTITY_0, rawAmount: '0' }, // Native asset with zero balance - { ...MOCK_ASSET_ENTITY_1, rawAmount: '0' }, // Token asset with zero balance - ]); + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [MOCK_ASSET_ENTITY_0.assetType]: { + ...MOCK_ASSET_ENTITY_0, + rawAmount: '0', + }, + [MOCK_ASSET_ENTITY_1.assetType]: { + ...MOCK_ASSET_ENTITY_1, + rawAmount: '0', + }, + }); const result = await keyring.getAccountBalances( MOCK_SOLANA_KEYRING_ACCOUNT_0.id, diff --git a/packages/snap/src/core/handlers/onKeyringRequest/Keyring.ts b/packages/snap/src/core/handlers/onKeyringRequest/Keyring.ts index 9dc6a985..9d555d34 100644 --- a/packages/snap/src/core/handlers/onKeyringRequest/Keyring.ts +++ b/packages/snap/src/core/handlers/onKeyringRequest/Keyring.ts @@ -415,7 +415,8 @@ export class SolanaKeyring implements KeyringSnapRpc { const account = await this.getAccountOrThrow(accountId); - const assetEntities = await this.#assetsService.findByAccount(account); + const assetEntities = + await this.#assetsService.getAccountAssetsForAllActiveScopes(accountId); const result = assetEntities // Remove token assets with zero balance @@ -450,8 +451,13 @@ export class SolanaKeyring implements KeyringSnapRpc { const account = await this.getAccountOrThrow(accountId); - const assetsToUse = (await this.#assetsService.findByAccount(account)) - .filter((asset) => assets.includes(asset.assetType)) + const assetsById = await this.#assetsService.getAccountAssetsByIDs( + accountId, + assets, + ); + + const assetsToUse = Object.values(assetsById) + .filter((asset): asset is NonNullable => asset !== null) // Remove token assets with zero balance .filter( (asset) => diff --git a/packages/snap/src/core/services/accounts/AccountsSynchronizer.ts b/packages/snap/src/core/services/accounts/AccountsSynchronizer.ts index d214690c..6212b9ef 100644 --- a/packages/snap/src/core/services/accounts/AccountsSynchronizer.ts +++ b/packages/snap/src/core/services/accounts/AccountsSynchronizer.ts @@ -33,16 +33,26 @@ export class AccountsSynchronizer { const assets = ( await Promise.allSettled( - accountsToSync.map(async (account) => - this.#assetsService.fetch(account), - ), + accountsToSync.map(async (account) => { + if ( + await this.#assetsService.shouldTrackSnapAssetsForAccount( + account.id, + ) + ) { + const fetchedAssets = await this.#assetsService.fetch(account); + await this.#assetsService.saveMany(fetchedAssets); + return fetchedAssets; + } + + return this.#assetsService.getAccountAssetsForAllActiveScopes( + account.id, + ); + }), ) ) .map((item) => (item.status === 'fulfilled' ? item.value : [])) .flat(); - await this.#assetsService.saveMany(assets); - const transactions = await this.#transactionsService.fetchAssetsTransactions(assets, { limit: 20, diff --git a/packages/snap/src/core/services/assets/AssetsService.test.ts b/packages/snap/src/core/services/assets/AssetsService.test.ts index d7e7f672..b32ce5af 100644 --- a/packages/snap/src/core/services/assets/AssetsService.test.ts +++ b/packages/snap/src/core/services/assets/AssetsService.test.ts @@ -1,7 +1,13 @@ +import { + SNAPS_ASSETS_MIGRATION_FLAG_KEYS, + SnapsAssetsMigrationStage, +} from '@metamask/assets-controller'; import { KeyringEvent } from '@metamask/keyring-api'; import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; import { cloneDeep } from 'lodash'; +import type { AssetEntity } from '../../../entities'; +import type { CoreMessengerCaller } from '../../../types/core-messenger'; import type { ICache } from '../../caching/ICache'; import { InMemoryCache } from '../../caching/InMemoryCache'; import { MOCK_NFTS_LIST_RESPONSE_MAPPED } from '../../clients/nft-api/mocks/mockNftsListResponseMapped'; @@ -17,12 +23,15 @@ import { SOLANA_MOCK_TOKEN_METADATA, } from '../../test/mocks/asset-entities'; import { MOCK_SOLANA_KEYRING_ACCOUNT_0 } from '../../test/mocks/solana-keyring-accounts'; +import type { AccountsService } from '../accounts/AccountsService'; import type { ConfigProvider } from '../config'; import type { SolanaConnection } from '../connection'; import { mockLogger } from '../mocks/logger'; import { createMockConnection } from '../mocks/mockConnection'; import { MOCK_SOLANA_RPC_GET_TOKEN_ACCOUNTS_BY_OWNER_RESPONSE } from '../mocks/mockSolanaRpcResponses'; import type { TokenPricesService } from '../token-prices/TokenPrices'; +import type { CoreAssetsAdapter } from './adapters/CoreAssetsAdapter'; +import { SnapAssetsAdapter } from './adapters/SnapAssetsAdapter'; import type { AssetsRepository } from './AssetsRepository'; import { AssetsService } from './AssetsService'; @@ -30,18 +39,38 @@ jest.mock('@metamask/keyring-snap-sdk', () => ({ emitSnapKeyringEvent: jest.fn(), })); +const SOLANA_FLAG_KEY = SNAPS_ASSETS_MIGRATION_FLAG_KEYS.solana; + +function createMessengerCallMock( + getState: () => unknown, +): (method: string, ...args: unknown[]) => Promise { + return async (method: string) => { + if (method === 'RemoteFeatureFlagController:getState') { + return getState(); + } + return undefined; + }; +} + describe('AssetsService', () => { let assetsService: AssetsService; + let snapAssetsAdapter: SnapAssetsAdapter; let mockConnection: SolanaConnection; let mockConfigProvider: ConfigProvider; let mockAssetsRepository: AssetsRepository; + let mockAccountsService: AccountsService; let mockTokenApiClient: TokenApiClient; let mockTokenPricesService: TokenPricesService; let mockNftApiClient: NftApiClient; let mockCache: ICache; + let mockCoreAssetsAdapter: CoreAssetsAdapter; + let migrationStage: SnapsAssetsMigrationStage; + let mockCoreMessenger: CoreMessengerCaller; + let setMigrationStage: (stage: SnapsAssetsMigrationStage) => void; beforeEach(() => { jest.clearAllMocks(); + migrationStage = SnapsAssetsMigrationStage.Off; mockConnection = createMockConnection(); mockConfigProvider = { @@ -81,16 +110,52 @@ describe('AssetsService', () => { saveMany: jest.fn(), } as unknown as AssetsRepository; - assetsService = new AssetsService({ + mockAccountsService = { + findById: jest.fn().mockResolvedValue(MOCK_SOLANA_KEYRING_ACCOUNT_0), + } as unknown as AccountsService; + + snapAssetsAdapter = new SnapAssetsAdapter({ connection: mockConnection, logger: mockLogger, configProvider: mockConfigProvider, assetsRepository: mockAssetsRepository, + accountsService: mockAccountsService, tokenApiClient: mockTokenApiClient, - tokenPricesService: mockTokenPricesService, cache: mockCache, nftApiClient: mockNftApiClient, }); + + mockCoreAssetsAdapter = { + getAccountAssetByID: jest.fn(), + getAccountAssetsByScope: jest.fn(), + getAccountAssetsByIDs: jest.fn(), + } as unknown as CoreAssetsAdapter; + + setMigrationStage = (stage: SnapsAssetsMigrationStage) => { + migrationStage = stage; + }; + + mockCoreMessenger = { + call: jest.fn().mockImplementation( + createMessengerCallMock(() => ({ + remoteFeatureFlags: { + [SOLANA_FLAG_KEY]: { stage: migrationStage }, + }, + })), + ), + }; + + assetsService = new AssetsService({ + logger: mockLogger, + configProvider: mockConfigProvider, + snapAssetsAdapter, + coreAssetsAdapter: mockCoreAssetsAdapter, + coreMessenger: mockCoreMessenger, + accountsService: mockAccountsService, + tokenApiClient: mockTokenApiClient, + tokenPricesService: mockTokenPricesService, + nftApiClient: mockNftApiClient, + }); }); describe('fetch', () => { @@ -604,4 +669,222 @@ describe('AssetsService', () => { expect(assets).toStrictEqual(MOCK_ASSET_ENTITIES); }); }); + + describe('assets migration routing', () => { + beforeEach(() => { + setMigrationStage( + SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, + ); + }); + + describe('getAccountAssetByID', () => { + it('routes fungible assets through CoreAssetsAdapter', async () => { + jest + .spyOn(mockCoreAssetsAdapter, 'getAccountAssetByID') + .mockResolvedValueOnce(MOCK_ASSET_ENTITY_1); + + const asset = await assetsService.getAccountAssetByID( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_ASSET_ENTITY_1.assetType, + ); + + expect(mockCoreAssetsAdapter.getAccountAssetByID).toHaveBeenCalledWith( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_ASSET_ENTITY_1.assetType, + MOCK_SOLANA_KEYRING_ACCOUNT_0.address, + ); + expect(asset).toStrictEqual(MOCK_ASSET_ENTITY_1); + }); + + it('routes NFT assets through SnapAssetsAdapter', async () => { + const nftAssetType = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; + const snapSpy = jest + .spyOn(snapAssetsAdapter, 'getAccountAssetByID') + .mockResolvedValueOnce(null); + + await assetsService.getAccountAssetByID( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + nftAssetType, + ); + + expect(snapSpy).toHaveBeenCalledWith( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + nftAssetType, + ); + expect( + mockCoreAssetsAdapter.getAccountAssetByID, + ).not.toHaveBeenCalled(); + }); + + it('returns null when the fungible asset is missing from Core', async () => { + jest + .spyOn(mockCoreAssetsAdapter, 'getAccountAssetByID') + .mockResolvedValueOnce(null); + + const asset = await assetsService.getAccountAssetByID( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_ASSET_ENTITY_1.assetType, + ); + + expect(asset).toBeNull(); + }); + + it('routes fungible assets through SnapAssetsAdapter when stage is Off', async () => { + setMigrationStage(SnapsAssetsMigrationStage.Off); + jest + .spyOn(mockAssetsRepository, 'findByKeyringAccountId') + .mockResolvedValueOnce(MOCK_ASSET_ENTITIES); + + const asset = await assetsService.getAccountAssetByID( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_ASSET_ENTITY_1.assetType, + ); + + expect( + mockCoreAssetsAdapter.getAccountAssetByID, + ).not.toHaveBeenCalled(); + expect(asset).toStrictEqual(MOCK_ASSET_ENTITY_1); + }); + + it('falls back to SnapAssetsAdapter when Core read fails in WithFallback stage', async () => { + setMigrationStage( + SnapsAssetsMigrationStage.ReadAssetsControllerWithFallback, + ); + jest + .spyOn(mockCoreAssetsAdapter, 'getAccountAssetByID') + .mockRejectedValueOnce(new Error('Core unavailable')); + jest + .spyOn(mockAssetsRepository, 'findByKeyringAccountId') + .mockResolvedValueOnce(MOCK_ASSET_ENTITIES); + + const asset = await assetsService.getAccountAssetByID( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_ASSET_ENTITY_1.assetType, + ); + + expect(asset).toStrictEqual(MOCK_ASSET_ENTITY_1); + }); + }); + + describe('getAccountAssetsByIDs', () => { + it('routes fungible and NFT asset IDs to the correct adapters', async () => { + const nftAssetType = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; + + jest + .spyOn(mockCoreAssetsAdapter, 'getAccountAssetsByIDs') + .mockResolvedValueOnce({ + [MOCK_ASSET_ENTITY_0.assetType]: MOCK_ASSET_ENTITY_0, + }); + jest + .spyOn(snapAssetsAdapter, 'getAccountAssetsByIDs') + .mockResolvedValueOnce({ + [nftAssetType]: null, + }); + + const assets = await assetsService.getAccountAssetsByIDs( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + [MOCK_ASSET_ENTITY_0.assetType, nftAssetType], + ); + + expect( + mockCoreAssetsAdapter.getAccountAssetsByIDs, + ).toHaveBeenCalledWith( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + [MOCK_ASSET_ENTITY_0.assetType], + MOCK_SOLANA_KEYRING_ACCOUNT_0.address, + ); + expect(snapAssetsAdapter.getAccountAssetsByIDs).toHaveBeenCalledWith( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + [nftAssetType], + ); + expect(assets).toStrictEqual({ + [MOCK_ASSET_ENTITY_0.assetType]: MOCK_ASSET_ENTITY_0, + [nftAssetType]: null, + }); + }); + }); + + describe('getAccountAssetsByScope', () => { + it('merges fungible Core assets with Snap-owned NFT assets for the scope', async () => { + const nftAssetType = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; + const nftAsset = { + assetType: nftAssetType, + keyringAccountId: MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + network: Network.Mainnet, + mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + pubkey: '9wt9PfjPD3JCy5r7o4K1cTGiuTG7fq2pQhdDCdQALKjg', + symbol: 'NFT', + decimals: 0, + rawAmount: '1', + uiAmount: '1', + } as AssetEntity; + + jest + .spyOn(mockCoreAssetsAdapter, 'getAccountAssetsByScope') + .mockResolvedValueOnce([MOCK_ASSET_ENTITY_0, MOCK_ASSET_ENTITY_1]); + jest + .spyOn(snapAssetsAdapter, 'getAccountAssetsByScope') + .mockResolvedValueOnce([MOCK_ASSET_ENTITY_2, nftAsset]); + + const assets = await assetsService.getAccountAssetsByScope( + Network.Mainnet, + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + ); + + expect( + mockCoreAssetsAdapter.getAccountAssetsByScope, + ).toHaveBeenCalledWith( + Network.Mainnet, + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_SOLANA_KEYRING_ACCOUNT_0.address, + ); + expect(assets).toStrictEqual([ + MOCK_ASSET_ENTITY_0, + MOCK_ASSET_ENTITY_1, + nftAsset, + ]); + }); + }); + + describe('getAccountAssetsForAllActiveScopes', () => { + it('merges fungible Core assets with Snap-owned NFT assets across active scopes', async () => { + const nftAssetType = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; + const nftAsset = { + assetType: nftAssetType, + keyringAccountId: MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + network: Network.Mainnet, + mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + pubkey: '9wt9PfjPD3JCy5r7o4K1cTGiuTG7fq2pQhdDCdQALKjg', + symbol: 'NFT', + decimals: 0, + rawAmount: '1', + uiAmount: '1', + } as AssetEntity; + + jest + .spyOn(mockCoreAssetsAdapter, 'getAccountAssetsByScope') + .mockResolvedValueOnce([MOCK_ASSET_ENTITY_0]); + jest + .spyOn(snapAssetsAdapter, 'getAccountAssetsByScope') + .mockResolvedValueOnce([MOCK_ASSET_ENTITY_1, nftAsset]); + + const assets = await assetsService.getAccountAssetsForAllActiveScopes( + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + ); + + expect( + mockCoreAssetsAdapter.getAccountAssetsByScope, + ).toHaveBeenCalledWith( + Network.Mainnet, + MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + MOCK_SOLANA_KEYRING_ACCOUNT_0.address, + ); + expect(assets).toStrictEqual([MOCK_ASSET_ENTITY_0, nftAsset]); + }); + }); + }); }); diff --git a/packages/snap/src/core/services/assets/AssetsService.ts b/packages/snap/src/core/services/assets/AssetsService.ts index a2778e7b..b4d69a6d 100644 --- a/packages/snap/src/core/services/assets/AssetsService.ts +++ b/packages/snap/src/core/services/assets/AssetsService.ts @@ -1,117 +1,165 @@ /* eslint-disable jsdoc/require-returns */ -import { KeyringEvent } from '@metamask/keyring-api'; -import type { - AccountAssetListUpdatedEvent, - AccountBalancesUpdatedEvent, - Balance, -} from '@metamask/keyring-api'; -import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; +import { + SNAPS_ASSETS_MIGRATION_FLAG_KEYS, + SnapsAssetsMigrationStage, + getSnapsAssetsMigrationNamespace, + parseSnapsAssetsMigrationStage, +} from '@metamask/assets-controller'; import type { FungibleAssetMarketData, FungibleAssetMetadata, } from '@metamask/snaps-sdk'; -import type { CaipAssetType } from '@metamask/utils'; -import { Duration, parseCaipAssetType } from '@metamask/utils'; -import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; -import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022'; -import type { - AccountInfoBase, - AccountInfoWithPubkey, - Address, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; +import type { CaipAssetType, CaipChainId, Json } from '@metamask/utils'; +import { parseCaipAssetType } from '@metamask/utils'; -import type { - AssetEntity, - NativeAsset, - SolanaKeyringAccount, - TokenAsset, -} from '../../../entities'; -import type { ICache } from '../../caching/ICache'; -import { useCache } from '../../caching/useCache'; +import type { AssetEntity, SolanaKeyringAccount } from '../../../entities'; +import type { CoreMessengerCaller } from '../../../types/core-messenger'; import type { NftApiClient } from '../../clients/nft-api/NftApiClient'; import type { TokenApiClient } from '../../clients/token-api-client/TokenApiClient'; import type { - Caip10Address, NativeCaipAssetType, NftCaipAssetType, TokenCaipAssetType, } from '../../constants/solana'; -import { Network, SolanaCaip19Tokens } from '../../constants/solana'; -import type { TokenAccountInfoWithJsonData } from '../../sdk-extensions/rpc-api'; -import type { Serializable } from '../../serialization/types'; -import { fromTokenUnits } from '../../utils/fromTokenUnit'; -import { getNetworkFromToken } from '../../utils/getNetworkFromToken'; +import { SolanaCaip19Tokens } from '../../constants/solana'; +import type { Caip10Address } from '../../constants/solana'; import { createPrefixedLogger } from '../../utils/logger'; import type { ILogger } from '../../utils/logger'; -import { tokenAddressToCaip19 } from '../../utils/tokenAddressToCaip19'; +import type { AccountsService } from '../accounts/AccountsService'; import type { ConfigProvider } from '../config'; -import type { SolanaConnection } from '../connection'; import type { TokenPricesService } from '../token-prices/TokenPrices'; -import type { AssetsRepository } from './AssetsRepository'; +import type { CoreAssetsAdapter } from './adapters/CoreAssetsAdapter'; +import { SnapAssetsAdapter } from './adapters/SnapAssetsAdapter'; +import { shouldTrackSnapAssets } from './shouldTrackSnapAssets'; +import { isSnapOwnedAsset } from './snapOwnedAssets'; import type { AssetMetadata, NonFungibleAssetMetadata } from './types'; +export { shouldTrackSnapAssets }; + /** - * Extends a token account as returned by the `getTokenAccountsByOwner` RPC method with the scope and the caip-19 asset type for convenience. + * Assets migration stage used when no remote feature flag is set for the chain. */ -type TokenAccountWithMetadata = { - token: AccountInfoWithPubkey; - scope: Network; - assetType: TokenCaipAssetType; - keyringAccount: SolanaKeyringAccount; -} & Serializable; +const ASSETS_MIGRATION_STAGE = SnapsAssetsMigrationStage.Off; export class AssetsService { readonly #logger: ILogger; - readonly #connection: SolanaConnection; - readonly #configProvider: ConfigProvider; - readonly #assetsRepository: AssetsRepository; + readonly #snapAdapter: SnapAssetsAdapter; + + readonly #coreAssetsAdapter: CoreAssetsAdapter; + + readonly #coreMessenger: CoreMessengerCaller; + + readonly #accountsService: AccountsService; readonly #tokenPricesService: TokenPricesService; readonly #tokenApiClient: TokenApiClient; - readonly #cache: ICache; - readonly #nftApiClient: NftApiClient; - public static readonly cacheTtlsMilliseconds = { - tokenAccountsByOwner: 5 * Duration.Second, - }; - constructor({ - connection, logger, configProvider, - assetsRepository, + snapAssetsAdapter, + coreAssetsAdapter, + coreMessenger, + accountsService, tokenApiClient, tokenPricesService, - cache, nftApiClient, }: { - connection: SolanaConnection; logger: ILogger; configProvider: ConfigProvider; - assetsRepository: AssetsRepository; + snapAssetsAdapter: SnapAssetsAdapter; + coreAssetsAdapter: CoreAssetsAdapter; + coreMessenger: CoreMessengerCaller; + accountsService: AccountsService; tokenApiClient: TokenApiClient; tokenPricesService: TokenPricesService; - cache: ICache; nftApiClient: NftApiClient; }) { this.#logger = createPrefixedLogger(logger, '[🪙 AssetsService]'); - this.#connection = connection; this.#configProvider = configProvider; - this.#assetsRepository = assetsRepository; + this.#snapAdapter = snapAssetsAdapter; + this.#coreAssetsAdapter = coreAssetsAdapter; + this.#coreMessenger = coreMessenger; + this.#accountsService = accountsService; this.#tokenApiClient = tokenApiClient; this.#tokenPricesService = tokenPricesService; - this.#cache = cache; this.#nftApiClient = nftApiClient; } + async #resolveMigrationStage( + chainId: string, + ): Promise { + const { remoteFeatureFlags } = await this.#coreMessenger.call( + 'RemoteFeatureFlagController:getState', + ); + + const namespace = getSnapsAssetsMigrationNamespace(chainId as CaipChainId); + + if (namespace) { + const flagKey = SNAPS_ASSETS_MIGRATION_FLAG_KEYS[namespace]; + + if (flagKey in remoteFeatureFlags) { + const remoteStage = parseSnapsAssetsMigrationStage( + remoteFeatureFlags[flagKey], + ); + + if (remoteStage !== undefined) { + return remoteStage; + } + } + } + + return ASSETS_MIGRATION_STAGE; + } + + async #solanaChainIds(): Promise { + return this.#configProvider.getActiveNetworks(); + } + + async #filterTrackableAssets(assets: AssetEntity[]): Promise { + const filtered: AssetEntity[] = []; + + for (const asset of assets) { + if (isSnapOwnedAsset(asset.assetType)) { + filtered.push(asset); + continue; + } + + if (await this.shouldTrackSnapAssetsForScope(asset.network)) { + filtered.push(asset); + } + } + + return filtered; + } + + async shouldTrackSnapAssetsForScope(scope: CaipChainId): Promise { + const stage = await this.#resolveMigrationStage(scope); + return shouldTrackSnapAssets(stage); + } + + async shouldTrackSnapAssetsForAccount(accountId: string): Promise { + const account = await this.#accountsService.findById(accountId); + if (!account) { + return false; + } + + for (const scope of account.scopes) { + if (await this.shouldTrackSnapAssetsForScope(scope)) { + return true; + } + } + + return false; + } + #splitAssetsByType(assetTypes: CaipAssetType[]) { const nativeAssetTypes = assetTypes.filter((assetType) => assetType.endsWith(SolanaCaip19Tokens.SOL), @@ -235,221 +283,9 @@ export class AssetsService { }; } - /** - * Matrix-fetches all token accounts owned by the given address on the specified networks and program ids, - * and merges the results into a single array. Each individual token is augmented with the scope and the caip-19 asset type for convenience. - * - * It caches the results for each pair of scope and program id. - * - * @param accounts - The owners of the token accounts. - * @param programIds - The program ids to fetch the token accounts for. - * @param scopes - The networks to fetch the token accounts for. - * @returns The token accounts augmented with the scope and the caip-19 asset type for convenience. - */ - async #fetchTokenAccountsMultiple( - accounts: SolanaKeyringAccount[], - programIds: Address[] = [TOKEN_PROGRAM_ADDRESS, TOKEN_2022_PROGRAM_ADDRESS], - scopes: Network[] = [Network.Mainnet], - ): Promise { - if (programIds.length === 0 || scopes.length === 0) { - return []; - } - - // Create all combinations of account, programId, and scope - const combinations = accounts.flatMap((account) => - programIds.flatMap((programId) => - scopes.map((scope) => ({ account, programId, scope })), - ), - ); - - const fetchTokenAccountsCached = useCache< - [SolanaKeyringAccount, Address, Network], - TokenAccountWithMetadata[] - >(this.#fetchTokenAccounts.bind(this), this.#cache, { - functionName: 'AssetsService:fetchTokenAccounts', - ttlMilliseconds: AssetsService.cacheTtlsMilliseconds.tokenAccountsByOwner, - generateCacheKey: (functionName, args) => { - const [account, programId, scope] = args; - return `${functionName}:${account.id}:${programId}:${scope}`; - }, - }); - - const responses = await Promise.allSettled( - combinations.map(async ({ account, programId, scope }) => { - const response = await fetchTokenAccountsCached( - account, - programId, - scope, - ); - return response; - }), - ); - - return responses.flatMap((item) => - item.status === 'fulfilled' ? item.value : [], - ); - } - - /** - * Fetches the token accounts for the given owner and program id on the specified scope. - * - * @param account - The owner of the token accounts. - * @param programId - The program id to fetch the token accounts for. - * @param scope - The scope to fetch the token accounts for. - * @returns The token accounts augmented with the scope and the caip-19 asset type for convenience. - */ - async #fetchTokenAccounts( - account: SolanaKeyringAccount, - programId: Address = TOKEN_PROGRAM_ADDRESS, - scope: Network = Network.Mainnet, - ): Promise { - const response = await this.#connection - .getRpc(scope) - .getTokenAccountsByOwner( - asAddress(account.address), - { programId }, - { encoding: 'jsonParsed' }, - ) - .send(); - - const tokens = response.value; - - // Attach the scope and the caip-19 asset type to each token account for easier future reference - return tokens.map( - (token) => - ({ - token, - scope, - assetType: tokenAddressToCaip19( - scope, - token.account.data.parsed.info.mint, - ), - keyringAccount: account, - }) as TokenAccountWithMetadata, - ); - } - - /** - * Fetches all assets for the given account. - * - * @param account - The account to get the balances for. - * @returns The balances and metadata of the account for the given assets. - */ async fetch(account: SolanaKeyringAccount): Promise { - const [nativeAssets, tokenAccounts] = await Promise.all([ - this.#fetchNativeAssets(account), - this.#fetchTokenAccountsMultiple( - [account], - [TOKEN_PROGRAM_ADDRESS, TOKEN_2022_PROGRAM_ADDRESS], - await this.#configProvider.getActiveNetworks(), - ), - ]); - - const assetTypes = tokenAccounts.map( - (tokenAccount) => tokenAccount.assetType, - ); - - const tokensMetadata = - await this.#tokenApiClient.getTokensMetadata(assetTypes); - - const tokenAssets: TokenAsset[] = tokenAccounts - .filter((tokenAccount) => tokenAccount.assetType.includes('/token:')) - .map((tokenAccount) => { - const { assetType } = tokenAccount; - const { decimals, amount, uiAmountString } = - tokenAccount.token.account.data.parsed.info.tokenAmount; - - return { - assetType, - keyringAccountId: tokenAccount.keyringAccount.id, - network: tokenAccount.scope, - mint: tokenAccount.token.account.data.parsed.info.mint, - pubkey: tokenAccount.token.pubkey, - symbol: tokensMetadata[assetType]?.symbol ?? 'UNKNOWN', - decimals, - rawAmount: amount, - uiAmount: uiAmountString ?? fromTokenUnits(amount, decimals), - }; - }); - - // const nftAssets = await this.#fetchNftAssets(account, tokenAccounts.filter( - // (token) => token.assetType.includes('/nft:'), - // )); - - return [ - ...nativeAssets, - ...tokenAssets, - // ...nftAssets, - ]; - } - - async getNativeAssetTypes(): Promise { - const activeNetworks = await this.#configProvider.getActiveNetworks(); - return activeNetworks.map( - (network) => `${network}/${SolanaCaip19Tokens.SOL}` as const, - ); - } - - async #fetchNativeAssets( - account: SolanaKeyringAccount, - ): Promise { - const nativeAssetsTypes = await this.getNativeAssetTypes(); - - const accountAddress = asAddress(account.address); - - const balancePromises = nativeAssetsTypes.map(async (assetType) => { - const balance = await this.#connection - .getRpc(getNetworkFromToken(assetType)) - .getBalance(accountAddress) - .send(); - - return { - assetType, - keyringAccountId: account.id, - network: getNetworkFromToken(assetType), - address: accountAddress, - symbol: 'SOL', - decimals: 9, - rawAmount: balance.value.toString(), - uiAmount: fromTokenUnits(balance.value, 9), - }; - }); - - const results = (await Promise.allSettled(balancePromises)).flatMap( - (item) => (item.status === 'fulfilled' ? item.value : []), - ); - - return results; - } - - async #fetchNftAssets( - account: SolanaKeyringAccount, - assetIds: NftCaipAssetType[], - ): Promise> { - const accountAddress = asAddress(account.address); - - const nftAssets = - await this.#nftApiClient.listAddressSolanaNfts(accountAddress); - const balances: Record = {}; - - for (const assetId of assetIds) { - const { assetReference } = parseCaipAssetType(assetId); - - const nftAsset = nftAssets.find( - (nft) => nft.tokenAddress === assetReference, - ); - - if (!nftAsset) { - continue; - } - - balances[assetId] = { - unit: nftAsset.nftToken.name, - amount: nftAsset.balance.toString(), - }; - } - - return balances; + const assets = await this.#snapAdapter.fetch(account); + return this.#filterTrackableAssets(assets); } async fetchAssetsMarketData( @@ -472,207 +308,246 @@ export class AssetsService { } async saveMany(assets: AssetEntity[]): Promise { - this.#logger.info('Saving assets', assets); + const trackableAssets = await this.#filterTrackableAssets(assets); - /** - * Should we save the assets incrementally? - * - If true, only saves and emits events for the assets that have changed (new or balance changed). Better performance because it only informs the client of what has changed. - * - If false, saves all assets. More reliable because it enforces that the client has the same state of assets as the snap. - */ - const isIncremental = false; + if (trackableAssets.length === 0) { + return; + } + + await this.#snapAdapter.saveMany(trackableAssets); + } - const hasZeroAmount = (asset: AssetEntity) => - asset.rawAmount === '0' || asset.uiAmount === '0'; + /** + * Checks if the asset has changed compared to passed assets lookup. + * + * @param asset - The asset to check. + * @param assetsLookup - The lookup table to check against. + * @returns True if the asset has changed, false otherwise. + */ + static hasChanged(asset: AssetEntity, assetsLookup: AssetEntity[]): boolean { + return SnapAssetsAdapter.hasChanged(asset, assetsLookup); + } - const hasNonZeroAmount = (asset: AssetEntity) => !hasZeroAmount(asset); + async getAll(): Promise { + return this.#snapAdapter.getAll(); + } - const savedAssets = await this.getAll(); + /** + * Returns a single account asset by CAIP-19 ID, or `null` if missing. + * + * @param accountId - Keyring account ID. + * @param assetId - CAIP-19 asset ID. + */ + async getAccountAssetByID( + accountId: string, + assetId: CaipAssetType, + ): Promise { + if (isSnapOwnedAsset(assetId)) { + return this.#snapAdapter.getAccountAssetByID(accountId, assetId); + } - // Save assets using repository - await this.#assetsRepository.saveMany(assets); + const { chainId } = parseCaipAssetType(assetId); + const stage = await this.#resolveMigrationStage(chainId); - // Notify the extension about the new assets in a single event - const isNew = (asset: AssetEntity) => - !savedAssets.find( - (item) => - item.keyringAccountId === asset.keyringAccountId && - item.assetType === asset.assetType, - ); + if (stage === SnapsAssetsMigrationStage.Off) { + return this.#snapAdapter.getAccountAssetByID(accountId, assetId); + } - const wasSavedWithZeroAmount = (asset: AssetEntity) => { - const savedAsset = savedAssets.find( - (item) => - item.keyringAccountId === asset.keyringAccountId && - item.assetType === asset.assetType, - ); + const account = await this.#accountsService.findById(accountId); + if (!account) { + return null; + } - return savedAsset && hasZeroAmount(savedAsset); - }; + if (stage === SnapsAssetsMigrationStage.ReadAssetsControllerWithFallback) { + try { + return await this.#coreAssetsAdapter.getAccountAssetByID( + accountId, + assetId, + account.address, + ); + } catch { + return this.#snapAdapter.getAccountAssetByID(accountId, assetId); + } + } - const isNativeAsset = (asset: AssetEntity) => - asset.assetType.includes(SolanaCaip19Tokens.SOL); - - const shouldBeInRemovedList = (asset: AssetEntity) => - hasZeroAmount(asset) && !isNativeAsset(asset); // Never remove native assets from the account asset list - - const shouldBeInAddedList = (asset: AssetEntity) => - !shouldBeInRemovedList(asset) && - (!isIncremental || - ((isNew(asset) || wasSavedWithZeroAmount(asset)) && - hasNonZeroAmount(asset))); - - const assetListUpdatedPayload = assets.reduce< - AccountAssetListUpdatedEvent['params']['assets'] - >( - (acc, asset) => ({ - ...acc, - [asset.keyringAccountId]: { - added: [ - ...(acc[asset.keyringAccountId]?.added ?? []), - ...(shouldBeInAddedList(asset) ? [asset.assetType] : []), - ], - removed: [ - ...(acc[asset.keyringAccountId]?.removed ?? []), - ...(shouldBeInRemovedList(asset) ? [asset.assetType] : []), - ], - }, - }), - {}, + return this.#coreAssetsAdapter.getAccountAssetByID( + accountId, + assetId, + account.address, ); + } + + /** + * Returns account assets for the given CAIP-19 IDs, keyed by asset ID. + * Missing assets are `null`. + * + * @param accountId - Keyring account ID. + * @param assetIds - CAIP-19 asset IDs to resolve. + */ + async getAccountAssetsByIDs( + accountId: string, + assetIds: string[], + ): Promise> { + if (assetIds.length === 0) { + return {}; + } - // If no assets were added or removed, don't emit the event. - const isEmptyAccountAssetListUpdatedPayload = Object.values( - assetListUpdatedPayload, - ) - .map((item) => item.added.length + item.removed.length) - .every((item) => item === 0); + const result: Record = {}; + const fungibleIds: string[] = []; + const snapOwnedIds: string[] = []; - if (!isEmptyAccountAssetListUpdatedPayload) { - await emitSnapKeyringEvent(snap, KeyringEvent.AccountAssetListUpdated, { - assets: assetListUpdatedPayload, - }); + for (const assetId of assetIds) { + if (isSnapOwnedAsset(assetId)) { + snapOwnedIds.push(assetId); + } else { + fungibleIds.push(assetId); + } } - // Notify the extension about the changed balances in a single event - - const hasChanged = (asset: AssetEntity) => - AssetsService.hasChanged(asset, savedAssets); - - /** - * Build the event payload for snap keyring event `AccountBalancesUpdated`. - * - * @example - * { - * "balances": { - * "keyringAccountId0": { - * "assetType00": { - * "unit": "XYZ", - * "amount": "1234" - * }, - * "assetType01": { - * "unit": "ABC", - * "amount": "5678" - * } - * }, - * "keyringAccountId1": { - * "assetType10": { - * "unit": "XYZ", - * "amount": "42" - * } - * } - * } - * } - */ - const balancesUpdatedPayload = assets - .filter(isIncremental ? hasChanged : () => true) - .reduce( - (acc, asset) => ({ - ...acc, - [asset.keyringAccountId]: { - ...(acc[asset.keyringAccountId] ?? {}), - [asset.assetType]: { - unit: asset.symbol, - amount: asset.uiAmount, - }, - }, - }), - {}, + if (snapOwnedIds.length > 0) { + const snapResults = await this.#snapAdapter.getAccountAssetsByIDs( + accountId, + snapOwnedIds, ); + Object.assign(result, snapResults); + } - // Traverse the balancesUpdatedPayload object to check if we have at least 1 account that has at least 1 balance updated. - const isSomeBalanceChanged = Object.values(balancesUpdatedPayload) - .map((accountAssets) => Object.keys(accountAssets).length) // To each accountAssets object, map the number of assetTypes - .some((count) => count > 0); + if (fungibleIds.length === 0) { + return result; + } + + const { chainId } = parseCaipAssetType(fungibleIds[0] as CaipAssetType); + const stage = await this.#resolveMigrationStage(chainId); + const account = await this.#accountsService.findById(accountId); - // Only emit the event if some balance was changed. - if (isSomeBalanceChanged) { - await emitSnapKeyringEvent(snap, KeyringEvent.AccountBalancesUpdated, { - balances: balancesUpdatedPayload, + if (!account) { + fungibleIds.forEach((assetId) => { + result[assetId] = null; }); + return result; + } + + let fungibleResults: Record; + + if (stage === SnapsAssetsMigrationStage.Off) { + fungibleResults = await this.#snapAdapter.getAccountAssetsByIDs( + accountId, + fungibleIds, + ); + } else if ( + stage === SnapsAssetsMigrationStage.ReadAssetsControllerWithFallback + ) { + try { + fungibleResults = await this.#coreAssetsAdapter.getAccountAssetsByIDs( + accountId, + fungibleIds, + account.address, + ); + } catch { + fungibleResults = await this.#snapAdapter.getAccountAssetsByIDs( + accountId, + fungibleIds, + ); + } + } else { + fungibleResults = await this.#coreAssetsAdapter.getAccountAssetsByIDs( + accountId, + fungibleIds, + account.address, + ); } + + Object.assign(result, fungibleResults); + + return result; } /** - * Checks if the asset has changed compared to passed assets lookup. + * Returns controller-backed assets for an account on the given Solana scope. * - * @param asset - The asset to check. - * @param assetsLookup - The lookup table to check against. - * @returns True if the asset has changed, false otherwise. + * @param scope - CAIP-2 chain ID to filter results. + * @param accountId - Keyring account ID. */ - static hasChanged(asset: AssetEntity, assetsLookup: AssetEntity[]): boolean { - const savedAsset = assetsLookup.find( - (item) => - item.keyringAccountId === asset.keyringAccountId && - item.assetType === asset.assetType, + async getAccountAssetsByScope( + scope: CaipChainId, + accountId: string, + ): Promise { + const stage = await this.#resolveMigrationStage(scope); + const snapAssets = await this.#snapAdapter.getAccountAssetsByScope( + scope, + accountId, + ); + const nftAssets = snapAssets.filter((asset) => + isSnapOwnedAsset(asset.assetType), ); - if (!savedAsset) { - return true; + if (stage === SnapsAssetsMigrationStage.Off) { + const fungibleAssets = snapAssets.filter( + (asset) => !isSnapOwnedAsset(asset.assetType), + ); + return [...fungibleAssets, ...nftAssets]; } - const rawAmountChanged = savedAsset.rawAmount !== asset.rawAmount; - const uiAmountChanged = savedAsset.uiAmount !== asset.uiAmount; + const account = await this.#accountsService.findById(accountId); + if (!account) { + return nftAssets; + } - return rawAmountChanged || uiAmountChanged; - } + let fungibleAssets: AssetEntity[]; - async getAll(): Promise { - return this.#assetsRepository.getAll(); - } + if (stage === SnapsAssetsMigrationStage.ReadAssetsControllerWithFallback) { + try { + fungibleAssets = await this.#coreAssetsAdapter.getAccountAssetsByScope( + scope, + accountId, + account.address, + ); + } catch { + fungibleAssets = snapAssets.filter( + (asset) => !isSnapOwnedAsset(asset.assetType), + ); + } + } else { + fungibleAssets = await this.#coreAssetsAdapter.getAccountAssetsByScope( + scope, + accountId, + account.address, + ); + } - async findByAccount(account: SolanaKeyringAccount): Promise { - const { id: keyringAccountId, address } = account; + return [...fungibleAssets, ...nftAssets]; + } - const savedAssets = - await this.#assetsRepository.findByKeyringAccountId(keyringAccountId); + /** + * Returns assets for an account across all active Solana networks. + * + * @param accountId - Keyring account ID. + */ + async getAccountAssetsForAllActiveScopes( + accountId: string, + ): Promise { + const account = await this.#accountsService.findById(accountId); + if (!account) { + return []; + } - // Every account must have at least the native assets. Ensure that they are always present, even if not yet fetched/saved. - const nativeAssetTypes = await this.getNativeAssetTypes(); - const missingNativeAssets: NativeAsset[] = []; + const chainIds = (await this.#solanaChainIds()) as CaipChainId[]; + const relevantChainIds = chainIds.filter((chainId) => + account.scopes.includes(chainId), + ); - for (const nativeAssetType of nativeAssetTypes) { - const hasNativeAsset = savedAssets.some( - (asset) => asset.assetType === nativeAssetType, - ); + const assetsByScope = await Promise.all( + relevantChainIds.map((scope) => + this.getAccountAssetsByScope(scope, accountId), + ), + ); - if (!hasNativeAsset) { - // Create a placeholder native asset with zero balance - // This will be updated when assets are actually fetched - const network = getNetworkFromToken(nativeAssetType); - - missingNativeAssets.push({ - assetType: nativeAssetType, - keyringAccountId: account.id, - network, - address, - symbol: 'SOL', - decimals: 9, - rawAmount: '0', - uiAmount: '0', - }); - } - } + return assetsByScope.flat(); + } - return [...savedAssets, ...missingNativeAssets]; + async findByAccount(account: SolanaKeyringAccount): Promise { + return this.#snapAdapter.findByAccount(account); } } + +export { SnapsAssetsMigrationStage }; diff --git a/packages/snap/src/core/services/assets/adapters/CoreAssetsAdapter.test.ts b/packages/snap/src/core/services/assets/adapters/CoreAssetsAdapter.test.ts new file mode 100644 index 00000000..d264ff57 --- /dev/null +++ b/packages/snap/src/core/services/assets/adapters/CoreAssetsAdapter.test.ts @@ -0,0 +1,255 @@ +import type { AssetsProviderMessenger } from '@metamask-previews/snap-networks-utils'; +import type { Asset } from '@metamask/assets-controller'; +import { + findAssociatedTokenPda, + TOKEN_PROGRAM_ADDRESS, +} from '@solana-program/token'; +import { address as asAddress } from '@solana/kit'; + +import { KnownCaip19Id, Network } from '../../../constants/solana'; +import { MOCK_SOLANA_KEYRING_ACCOUNT_0 } from '../../../test/mocks/solana-keyring-accounts'; +import { CoreAssetsAdapter } from './CoreAssetsAdapter'; + +function createMockAsset( + assetId: string, + overrides: Partial = {}, +): Asset { + const chainId = assetId.split('/')[0] as Network; + return { + id: assetId as Asset['id'], + chainId, + balance: { amount: '1000000000' }, + metadata: { + type: assetId.endsWith('/slip44:501') ? 'native' : 'spl', + symbol: assetId.endsWith('/slip44:501') ? 'SOL' : 'USDC', + name: assetId.endsWith('/slip44:501') ? 'Solana' : 'USD Coin', + decimals: assetId.endsWith('/slip44:501') ? 9 : 6, + }, + price: { + assetPriceType: 'fungible', + price: 1, + usdPrice: 1, + lastUpdated: 0, + }, + fiatValue: 1, + ...overrides, + }; +} + +describe('CoreAssetsAdapter', () => { + let adapter: CoreAssetsAdapter; + let mockCall: jest.Mock; + let mockMessenger: AssetsProviderMessenger; + + const accountId = MOCK_SOLANA_KEYRING_ACCOUNT_0.id; + const accountAddress = MOCK_SOLANA_KEYRING_ACCOUNT_0.address; + const solAssetId = KnownCaip19Id.SolMainnet; + const usdcAssetId = KnownCaip19Id.UsdcMainnet; + const scope = Network.Mainnet; + + beforeEach(async () => { + jest.clearAllMocks(); + + mockCall = jest.fn(); + mockMessenger = { call: mockCall }; + adapter = new CoreAssetsAdapter(mockMessenger); + }); + + describe('getAccountAssetByID', () => { + it('calls AssetsController:getAccountAssetByID and maps native SOL', async () => { + const mockAsset = createMockAsset(solAssetId); + mockCall.mockResolvedValue(mockAsset); + + const result = await adapter.getAccountAssetByID( + accountId, + solAssetId, + accountAddress, + ); + + expect(mockCall).toHaveBeenCalledWith( + 'AssetsController:getAccountAssetByID', + accountId, + solAssetId, + ); + expect(result).toStrictEqual({ + assetType: solAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + address: accountAddress, + symbol: 'SOL', + decimals: 9, + rawAmount: '1000000000', + uiAmount: '1', + }); + }); + + it('returns null when controller returns undefined', async () => { + mockCall.mockResolvedValue(undefined); + + const result = await adapter.getAccountAssetByID( + accountId, + solAssetId, + accountAddress, + ); + + expect(result).toBeNull(); + }); + + it('falls back when controller metadata fields are missing', async () => { + const mockAsset = createMockAsset(solAssetId, { + metadata: { + type: 'native', + symbol: undefined as unknown as string, + name: 'Solana', + decimals: undefined as unknown as number, + }, + }); + mockCall.mockResolvedValue(mockAsset); + + const result = await adapter.getAccountAssetByID( + accountId, + solAssetId, + accountAddress, + ); + + expect(result).toMatchObject({ + symbol: 'UNKNOWN', + decimals: 0, + rawAmount: '1000000000', + uiAmount: '1000000000', + }); + }); + }); + + describe('getAccountAssetsByIDs', () => { + it('calls getAccountAssetsByIDs once and returns keyed results with null gaps', async () => { + const mockSolAsset = createMockAsset(solAssetId); + mockCall.mockResolvedValue({ + [solAssetId]: mockSolAsset, + }); + + const missingAssetId = KnownCaip19Id.Ai16zMainnet; + const result = await adapter.getAccountAssetsByIDs( + accountId, + [solAssetId, missingAssetId, usdcAssetId], + accountAddress, + ); + + expect(mockCall).toHaveBeenCalledTimes(1); + expect(mockCall).toHaveBeenCalledWith( + 'AssetsController:getAccountAssetsByIDs', + accountId, + [solAssetId, missingAssetId, usdcAssetId], + ); + expect(result).toStrictEqual({ + [solAssetId]: { + assetType: solAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + address: accountAddress, + symbol: 'SOL', + decimals: 9, + rawAmount: '1000000000', + uiAmount: '1', + }, + [missingAssetId]: null, + [usdcAssetId]: null, + }); + }); + + it('returns null for snap-owned NFT IDs without calling the provider', async () => { + const nftAssetId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:abc123'; + + const result = await adapter.getAccountAssetsByIDs( + accountId, + [nftAssetId], + accountAddress, + ); + + expect(mockCall).not.toHaveBeenCalled(); + expect(result).toStrictEqual({ [nftAssetId]: null }); + }); + }); + + describe('getAccountAssetsByScope', () => { + it('calls getAccountAssetsByScope once and maps supported assets', async () => { + const mockSolAsset = createMockAsset(solAssetId); + const mockUsdcAsset = createMockAsset(usdcAssetId, { + balance: { amount: '1234560' }, + }); + mockCall.mockResolvedValue({ + [solAssetId]: mockSolAsset, + [usdcAssetId]: mockUsdcAsset, + }); + + const [expectedUsdcPubkey] = await findAssociatedTokenPda({ + mint: asAddress('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), + owner: asAddress(accountAddress), + tokenProgram: TOKEN_PROGRAM_ADDRESS, + }); + + const result = await adapter.getAccountAssetsByScope( + scope, + accountId, + accountAddress, + ); + + expect(mockCall).toHaveBeenCalledTimes(1); + expect(mockCall).toHaveBeenCalledWith( + 'AssetsController:getAccountAssetsByScope', + accountId, + scope, + ); + expect(result).toHaveLength(2); + expect(result).toStrictEqual( + expect.arrayContaining([ + { + assetType: solAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + address: accountAddress, + symbol: 'SOL', + decimals: 9, + rawAmount: '1000000000', + uiAmount: '1', + }, + { + assetType: usdcAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + pubkey: expectedUsdcPubkey, + symbol: 'USDC', + decimals: 6, + rawAmount: '1234560', + uiAmount: '1.23456', + }, + ]), + ); + }); + + it('skips NFT assets returned by the provider', async () => { + const nftAssetId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:abc123'; + mockCall.mockResolvedValue({ + [solAssetId]: createMockAsset(solAssetId), + [nftAssetId]: createMockAsset(nftAssetId, { + metadata: { + type: 'nft', + symbol: 'NFT', + name: 'NFT', + decimals: 0, + }, + }), + }); + + const result = await adapter.getAccountAssetsByScope( + scope, + accountId, + accountAddress, + ); + + expect(result).toHaveLength(1); + expect(result[0]?.assetType).toBe(solAssetId); + }); + }); +}); diff --git a/packages/snap/src/core/services/assets/adapters/CoreAssetsAdapter.ts b/packages/snap/src/core/services/assets/adapters/CoreAssetsAdapter.ts new file mode 100644 index 00000000..438c9969 --- /dev/null +++ b/packages/snap/src/core/services/assets/adapters/CoreAssetsAdapter.ts @@ -0,0 +1,150 @@ +import { AssetsProvider } from '@metamask-previews/snap-networks-utils'; +import type { AssetsProviderMessenger } from '@metamask-previews/snap-networks-utils'; +import type { Asset, Caip19AssetId } from '@metamask/assets-controller'; +import type { CaipAssetType, CaipChainId } from '@metamask/utils'; +import { parseCaipAssetType } from '@metamask/utils'; +import { + findAssociatedTokenPda, + TOKEN_PROGRAM_ADDRESS, +} from '@solana-program/token'; +import { address as asAddress } from '@solana/kit'; + +import type { AssetEntity } from '../../../../entities'; +import type { + NativeCaipAssetType, + Network, + TokenCaipAssetType, +} from '../../../constants/solana'; +import { SolanaCaip19Tokens } from '../../../constants/solana'; +import { fromTokenUnits } from '../../../utils/fromTokenUnit'; + +function isSupportedProviderAsset(assetId: string): boolean { + return !assetId.includes('/nft:'); +} + +async function mapProviderAsset( + accountId: string, + assetId: CaipAssetType, + accountAddress: string, + asset: Asset, +): Promise { + const { chainId, assetReference } = parseCaipAssetType(assetId); + const decimals = asset.metadata.decimals ?? 0; + const symbol = asset.metadata.symbol ?? 'UNKNOWN'; + const rawAmount = asset.balance.amount; + const uiAmount = fromTokenUnits(rawAmount, decimals); + const network = chainId as Network; + + if (assetId.endsWith(SolanaCaip19Tokens.SOL)) { + return { + assetType: assetId as NativeCaipAssetType, + keyringAccountId: accountId, + network, + address: accountAddress, + symbol, + decimals, + rawAmount, + uiAmount, + }; + } + + const mint = assetReference; + const [pubkey] = await findAssociatedTokenPda({ + mint: asAddress(mint), + owner: asAddress(accountAddress), + tokenProgram: TOKEN_PROGRAM_ADDRESS, + }); + + return { + assetType: assetId as TokenCaipAssetType, + keyringAccountId: accountId, + network, + mint, + pubkey, + symbol, + decimals, + rawAmount, + uiAmount, + }; +} + +export class CoreAssetsAdapter { + readonly #provider: AssetsProvider; + + constructor(messenger: AssetsProviderMessenger) { + this.#provider = new AssetsProvider({ messenger }); + } + + async getAccountAssetByID( + accountId: string, + assetId: CaipAssetType, + accountAddress: string, + ): Promise { + const result = await this.#provider.getAccountAssetByID(accountId, assetId); + if (!result) { + return null; + } + return mapProviderAsset(accountId, assetId, accountAddress, result); + } + + async getAccountAssetsByIDs( + accountId: string, + assetIds: string[], + accountAddress: string, + ): Promise> { + const fungibleAssetIds = assetIds.filter(isSupportedProviderAsset); + const providerAssets = fungibleAssetIds.length + ? await this.#provider.getAccountAssetsByIDs( + accountId, + fungibleAssetIds as Caip19AssetId[], + ) + : {}; + + const entries = await Promise.all( + assetIds.map(async (assetId) => { + if (!isSupportedProviderAsset(assetId)) { + return [assetId, null] as const; + } + const asset = providerAssets[assetId as Caip19AssetId]; + if (!asset) { + return [assetId, null] as const; + } + const entity = await mapProviderAsset( + accountId, + assetId as CaipAssetType, + accountAddress, + asset, + ); + return [assetId, entity] as const; + }), + ); + + return Object.fromEntries(entries); + } + + async getAccountAssetsByScope( + scope: CaipChainId, + accountId: string, + accountAddress: string, + ): Promise { + const providerAssets = await this.#provider.getAccountAssetsByScope( + scope, + accountId, + ); + + const supportedEntries = Object.entries(providerAssets).filter( + ([assetId]) => isSupportedProviderAsset(assetId), + ); + + return Promise.all( + supportedEntries.map(([assetId, asset]) => + mapProviderAsset( + accountId, + assetId as CaipAssetType, + accountAddress, + asset, + ), + ), + ); + } +} diff --git a/packages/snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts b/packages/snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts new file mode 100644 index 00000000..c1cad8f2 --- /dev/null +++ b/packages/snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts @@ -0,0 +1,105 @@ +import { cloneDeep } from 'lodash'; + +import type { ICache } from '../../../caching/ICache'; +import { InMemoryCache } from '../../../caching/InMemoryCache'; +import { MOCK_NFTS_LIST_RESPONSE_MAPPED } from '../../../clients/nft-api/mocks/mockNftsListResponseMapped'; +import type { NftApiClient } from '../../../clients/nft-api/NftApiClient'; +import type { TokenApiClient } from '../../../clients/token-api-client/TokenApiClient'; +import type { Serializable } from '../../../serialization/types'; +import { + MOCK_ASSET_ENTITY_0, + MOCK_ASSET_ENTITY_1, + MOCK_ASSET_ENTITY_2, +} from '../../../test/mocks/asset-entities'; +import type { AccountsService } from '../../accounts/AccountsService'; +import type { ConfigProvider } from '../../config'; +import type { SolanaConnection } from '../../connection'; +import { mockLogger } from '../../mocks/logger'; +import { createMockConnection } from '../../mocks/mockConnection'; +import type { AssetsRepository } from '../AssetsRepository'; +import { SnapAssetsAdapter } from './SnapAssetsAdapter'; + +describe('SnapAssetsAdapter', () => { + let snapAssetsAdapter: SnapAssetsAdapter; + let mockConnection: SolanaConnection; + let mockConfigProvider: ConfigProvider; + let mockAssetsRepository: AssetsRepository; + let mockAccountsService: AccountsService; + let mockTokenApiClient: TokenApiClient; + let mockNftApiClient: NftApiClient; + let mockCache: ICache; + + beforeEach(() => { + jest.clearAllMocks(); + mockConnection = createMockConnection(); + + mockConfigProvider = { + getActiveNetworks: jest.fn().mockResolvedValue([]), + } as unknown as ConfigProvider; + + mockTokenApiClient = { + getTokensMetadata: jest.fn().mockResolvedValue({}), + } as unknown as TokenApiClient; + + mockCache = new InMemoryCache(mockLogger); + + mockNftApiClient = { + listAddressSolanaNfts: jest + .fn() + .mockResolvedValue(MOCK_NFTS_LIST_RESPONSE_MAPPED.items), + } as unknown as NftApiClient; + + mockAssetsRepository = { + findByKeyringAccountId: jest.fn(), + getAll: jest.fn(), + saveMany: jest.fn(), + } as unknown as AssetsRepository; + + mockAccountsService = { + findById: jest.fn(), + } as unknown as AccountsService; + + snapAssetsAdapter = new SnapAssetsAdapter({ + connection: mockConnection, + logger: mockLogger, + configProvider: mockConfigProvider, + assetsRepository: mockAssetsRepository, + accountsService: mockAccountsService, + tokenApiClient: mockTokenApiClient, + cache: mockCache, + nftApiClient: mockNftApiClient, + }); + }); + + describe('hasChanged', () => { + it('returns true if the raw amount has changed', () => { + const asset = cloneDeep(MOCK_ASSET_ENTITY_0); + asset.rawAmount = '123'; + const assetsLookup = [MOCK_ASSET_ENTITY_0]; + + expect(SnapAssetsAdapter.hasChanged(asset, assetsLookup)).toBe(true); + }); + + it('returns true if the ui amount has changed', () => { + const asset = cloneDeep(MOCK_ASSET_ENTITY_0); + asset.uiAmount = '123'; + const assetsLookup = [MOCK_ASSET_ENTITY_0]; + + expect(SnapAssetsAdapter.hasChanged(asset, assetsLookup)).toBe(true); + }); + + it('returns true if the asset does not exist in the lookup', () => { + const asset = cloneDeep(MOCK_ASSET_ENTITY_0); + const assetsLookup = [MOCK_ASSET_ENTITY_1, MOCK_ASSET_ENTITY_2]; + + expect(SnapAssetsAdapter.hasChanged(asset, assetsLookup)).toBe(true); + }); + + it('returns false if the asset has not changed', () => { + const asset = cloneDeep(MOCK_ASSET_ENTITY_0); + const assetsLookup = [MOCK_ASSET_ENTITY_0]; + + expect(SnapAssetsAdapter.hasChanged(asset, assetsLookup)).toBe(false); + }); + }); +}); diff --git a/packages/snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts b/packages/snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts new file mode 100644 index 00000000..d24a04a8 --- /dev/null +++ b/packages/snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts @@ -0,0 +1,622 @@ +/* eslint-disable jsdoc/require-returns */ +import { KeyringEvent } from '@metamask/keyring-api'; +import type { + AccountAssetListUpdatedEvent, + AccountBalancesUpdatedEvent, + Balance, +} from '@metamask/keyring-api'; +import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; +import type { CaipAssetType, CaipChainId } from '@metamask/utils'; +import { Duration, parseCaipAssetType } from '@metamask/utils'; +import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; +import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022'; +import type { + AccountInfoBase, + AccountInfoWithPubkey, + Address, +} from '@solana/kit'; +import { address as asAddress } from '@solana/kit'; + +import type { + AssetEntity, + NativeAsset, + SolanaKeyringAccount, + TokenAsset, +} from '../../../../entities'; +import type { ICache } from '../../../caching/ICache'; +import { useCache } from '../../../caching/useCache'; +import type { NftApiClient } from '../../../clients/nft-api/NftApiClient'; +import type { TokenApiClient } from '../../../clients/token-api-client/TokenApiClient'; +import { Network, SolanaCaip19Tokens } from '../../../constants/solana'; +import type { + NativeCaipAssetType, + NftCaipAssetType, + TokenCaipAssetType, +} from '../../../constants/solana'; +import type { TokenAccountInfoWithJsonData } from '../../../sdk-extensions/rpc-api'; +import type { Serializable } from '../../../serialization/types'; +import { fromTokenUnits } from '../../../utils/fromTokenUnit'; +import { getNetworkFromToken } from '../../../utils/getNetworkFromToken'; +import { createPrefixedLogger } from '../../../utils/logger'; +import type { ILogger } from '../../../utils/logger'; +import { tokenAddressToCaip19 } from '../../../utils/tokenAddressToCaip19'; +import type { AccountsService } from '../../accounts/AccountsService'; +import type { ConfigProvider } from '../../config'; +import type { SolanaConnection } from '../../connection'; +import type { AssetsRepository } from '../AssetsRepository'; + +/** + * Extends a token account as returned by the `getTokenAccountsByOwner` RPC method with the scope and the caip-19 asset type for convenience. + */ +type TokenAccountWithMetadata = { + token: AccountInfoWithPubkey; + scope: Network; + assetType: TokenCaipAssetType; + keyringAccount: SolanaKeyringAccount; +} & Serializable; + +export class SnapAssetsAdapter { + readonly #logger: ILogger; + + readonly #connection: SolanaConnection; + + readonly #configProvider: ConfigProvider; + + readonly #assetsRepository: AssetsRepository; + + readonly #accountsService: AccountsService; + + readonly #tokenApiClient: TokenApiClient; + + readonly #cache: ICache; + + readonly #nftApiClient: NftApiClient; + + public static readonly cacheTtlsMilliseconds = { + tokenAccountsByOwner: 5 * Duration.Second, + }; + + constructor({ + connection, + logger, + configProvider, + assetsRepository, + accountsService, + tokenApiClient, + cache, + nftApiClient, + }: { + connection: SolanaConnection; + logger: ILogger; + configProvider: ConfigProvider; + assetsRepository: AssetsRepository; + accountsService: AccountsService; + tokenApiClient: TokenApiClient; + cache: ICache; + nftApiClient: NftApiClient; + }) { + this.#logger = createPrefixedLogger(logger, '[🪙 SnapAssetsAdapter]'); + this.#connection = connection; + this.#configProvider = configProvider; + this.#assetsRepository = assetsRepository; + this.#accountsService = accountsService; + this.#tokenApiClient = tokenApiClient; + this.#cache = cache; + this.#nftApiClient = nftApiClient; + } + + /** + * Matrix-fetches all token accounts owned by the given address on the specified networks and program ids, + * and merges the results into a single array. Each individual token is augmented with the scope and the caip-19 asset type for convenience. + * + * It caches the results for each pair of scope and program id. + * + * @param accounts - The owners of the token accounts. + * @param programIds - The program ids to fetch the token accounts for. + * @param scopes - The networks to fetch the token accounts for. + * @returns The token accounts augmented with the scope and the caip-19 asset type for convenience. + */ + async #fetchTokenAccountsMultiple( + accounts: SolanaKeyringAccount[], + programIds: Address[] = [TOKEN_PROGRAM_ADDRESS, TOKEN_2022_PROGRAM_ADDRESS], + scopes: Network[] = [Network.Mainnet], + ): Promise { + if (programIds.length === 0 || scopes.length === 0) { + return []; + } + + // Create all combinations of account, programId, and scope + const combinations = accounts.flatMap((account) => + programIds.flatMap((programId) => + scopes.map((scope) => ({ account, programId, scope })), + ), + ); + + const fetchTokenAccountsCached = useCache< + [SolanaKeyringAccount, Address, Network], + TokenAccountWithMetadata[] + >(this.#fetchTokenAccounts.bind(this), this.#cache, { + functionName: 'SnapAssetsAdapter:fetchTokenAccounts', + ttlMilliseconds: + SnapAssetsAdapter.cacheTtlsMilliseconds.tokenAccountsByOwner, + generateCacheKey: (functionName, args) => { + const [account, programId, scope] = args; + return `${functionName}:${account.id}:${programId}:${scope}`; + }, + }); + + const responses = await Promise.allSettled( + combinations.map(async ({ account, programId, scope }) => { + const response = await fetchTokenAccountsCached( + account, + programId, + scope, + ); + return response; + }), + ); + + return responses.flatMap((item) => + item.status === 'fulfilled' ? item.value : [], + ); + } + + /** + * Fetches the token accounts for the given owner and program id on the specified scope. + * + * @param account - The owner of the token accounts. + * @param programId - The program id to fetch the token accounts for. + * @param scope - The scope to fetch the token accounts for. + * @returns The token accounts augmented with the scope and the caip-19 asset type for convenience. + */ + async #fetchTokenAccounts( + account: SolanaKeyringAccount, + programId: Address = TOKEN_PROGRAM_ADDRESS, + scope: Network = Network.Mainnet, + ): Promise { + const response = await this.#connection + .getRpc(scope) + .getTokenAccountsByOwner( + asAddress(account.address), + { programId }, + { encoding: 'jsonParsed' }, + ) + .send(); + + const tokens = response.value; + + // Attach the scope and the caip-19 asset type to each token account for easier future reference + return tokens.map( + (token) => + ({ + token, + scope, + assetType: tokenAddressToCaip19( + scope, + token.account.data.parsed.info.mint, + ), + keyringAccount: account, + }) as TokenAccountWithMetadata, + ); + } + + /** + * Fetches all assets for the given account. + * + * @param account - The account to get the balances for. + * @returns The balances and metadata of the account for the given assets. + */ + async fetch(account: SolanaKeyringAccount): Promise { + const [nativeAssets, tokenAccounts] = await Promise.all([ + this.#fetchNativeAssets(account), + this.#fetchTokenAccountsMultiple( + [account], + [TOKEN_PROGRAM_ADDRESS, TOKEN_2022_PROGRAM_ADDRESS], + await this.#configProvider.getActiveNetworks(), + ), + ]); + + const assetTypes = tokenAccounts.map( + (tokenAccount) => tokenAccount.assetType, + ); + + const tokensMetadata = + await this.#tokenApiClient.getTokensMetadata(assetTypes); + + const tokenAssets: TokenAsset[] = tokenAccounts + .filter((tokenAccount) => tokenAccount.assetType.includes('/token:')) + .map((tokenAccount) => { + const { assetType } = tokenAccount; + const { decimals, amount, uiAmountString } = + tokenAccount.token.account.data.parsed.info.tokenAmount; + + return { + assetType, + keyringAccountId: tokenAccount.keyringAccount.id, + network: tokenAccount.scope, + mint: tokenAccount.token.account.data.parsed.info.mint, + pubkey: tokenAccount.token.pubkey, + symbol: tokensMetadata[assetType]?.symbol ?? 'UNKNOWN', + decimals, + rawAmount: amount, + uiAmount: uiAmountString ?? fromTokenUnits(amount, decimals), + }; + }); + + // const nftAssets = await this.#fetchNftAssets(account, tokenAccounts.filter( + // (token) => token.assetType.includes('/nft:'), + // )); + + return [ + ...nativeAssets, + ...tokenAssets, + // ...nftAssets, + ]; + } + + async getNativeAssetTypes(): Promise { + const activeNetworks = await this.#configProvider.getActiveNetworks(); + return activeNetworks.map( + (network) => `${network}/${SolanaCaip19Tokens.SOL}` as const, + ); + } + + async #fetchNativeAssets( + account: SolanaKeyringAccount, + ): Promise { + const nativeAssetsTypes = await this.getNativeAssetTypes(); + + const accountAddress = asAddress(account.address); + + const balancePromises = nativeAssetsTypes.map(async (assetType) => { + const balance = await this.#connection + .getRpc(getNetworkFromToken(assetType)) + .getBalance(accountAddress) + .send(); + + return { + assetType, + keyringAccountId: account.id, + network: getNetworkFromToken(assetType), + address: accountAddress, + symbol: 'SOL', + decimals: 9, + rawAmount: balance.value.toString(), + uiAmount: fromTokenUnits(balance.value, 9), + }; + }); + + const results = (await Promise.allSettled(balancePromises)).flatMap( + (item) => (item.status === 'fulfilled' ? item.value : []), + ); + + return results; + } + + async #fetchNftAssets( + account: SolanaKeyringAccount, + assetIds: NftCaipAssetType[], + ): Promise> { + const accountAddress = asAddress(account.address); + + const nftAssets = + await this.#nftApiClient.listAddressSolanaNfts(accountAddress); + const balances: Record = {}; + + for (const assetId of assetIds) { + const { assetReference } = parseCaipAssetType(assetId); + + const nftAsset = nftAssets.find( + (nft) => nft.tokenAddress === assetReference, + ); + + if (!nftAsset) { + continue; + } + + balances[assetId] = { + unit: nftAsset.nftToken.name, + amount: nftAsset.balance.toString(), + }; + } + + return balances; + } + + async save(asset: AssetEntity): Promise { + await this.saveMany([asset]); + } + + async saveMany(assets: AssetEntity[]): Promise { + this.#logger.info('Saving assets', assets); + + /** + * Should we save the assets incrementally? + * - If true, only saves and emits events for the assets that have changed (new or balance changed). Better performance because it only informs the client of what has changed. + * - If false, saves all assets. More reliable because it enforces that the client has the same state of assets as the snap. + */ + const isIncremental = false; + + const hasZeroAmount = (asset: AssetEntity) => + asset.rawAmount === '0' || asset.uiAmount === '0'; + + const hasNonZeroAmount = (asset: AssetEntity) => !hasZeroAmount(asset); + + const savedAssets = await this.getAll(); + + // Save assets using repository + await this.#assetsRepository.saveMany(assets); + + // Notify the extension about the new assets in a single event + const isNew = (asset: AssetEntity) => + !savedAssets.find( + (item) => + item.keyringAccountId === asset.keyringAccountId && + item.assetType === asset.assetType, + ); + + const wasSavedWithZeroAmount = (asset: AssetEntity) => { + const savedAsset = savedAssets.find( + (item) => + item.keyringAccountId === asset.keyringAccountId && + item.assetType === asset.assetType, + ); + + return savedAsset && hasZeroAmount(savedAsset); + }; + + const isNativeAsset = (asset: AssetEntity) => + asset.assetType.includes(SolanaCaip19Tokens.SOL); + + const shouldBeInRemovedList = (asset: AssetEntity) => + hasZeroAmount(asset) && !isNativeAsset(asset); // Never remove native assets from the account asset list + + const shouldBeInAddedList = (asset: AssetEntity) => + !shouldBeInRemovedList(asset) && + (!isIncremental || + ((isNew(asset) || wasSavedWithZeroAmount(asset)) && + hasNonZeroAmount(asset))); + + const assetListUpdatedPayload = assets.reduce< + AccountAssetListUpdatedEvent['params']['assets'] + >( + (acc, asset) => ({ + ...acc, + [asset.keyringAccountId]: { + added: [ + ...(acc[asset.keyringAccountId]?.added ?? []), + ...(shouldBeInAddedList(asset) ? [asset.assetType] : []), + ], + removed: [ + ...(acc[asset.keyringAccountId]?.removed ?? []), + ...(shouldBeInRemovedList(asset) ? [asset.assetType] : []), + ], + }, + }), + {}, + ); + + // If no assets were added or removed, don't emit the event. + const isEmptyAccountAssetListUpdatedPayload = Object.values( + assetListUpdatedPayload, + ) + .map((item) => item.added.length + item.removed.length) + .every((item) => item === 0); + + if (!isEmptyAccountAssetListUpdatedPayload) { + await emitSnapKeyringEvent(snap, KeyringEvent.AccountAssetListUpdated, { + assets: assetListUpdatedPayload, + }); + } + + // Notify the extension about the changed balances in a single event + + const hasChanged = (asset: AssetEntity) => + SnapAssetsAdapter.hasChanged(asset, savedAssets); + + /** + * Build the event payload for snap keyring event `AccountBalancesUpdated`. + * + * @example + * { + * "balances": { + * "keyringAccountId0": { + * "assetType00": { + * "unit": "XYZ", + * "amount": "1234" + * }, + * "assetType01": { + * "unit": "ABC", + * "amount": "5678" + * } + * }, + * "keyringAccountId1": { + * "assetType10": { + * "unit": "XYZ", + * "amount": "42" + * } + * } + * } + * } + */ + const balancesUpdatedPayload = assets + .filter(isIncremental ? hasChanged : () => true) + .reduce( + (acc, asset) => ({ + ...acc, + [asset.keyringAccountId]: { + ...(acc[asset.keyringAccountId] ?? {}), + [asset.assetType]: { + unit: asset.symbol, + amount: asset.uiAmount, + }, + }, + }), + {}, + ); + + // Traverse the balancesUpdatedPayload object to check if we have at least 1 account that has at least 1 balance updated. + const isSomeBalanceChanged = Object.values(balancesUpdatedPayload) + .map((accountAssets) => Object.keys(accountAssets).length) // To each accountAssets object, map the number of assetTypes + .some((count) => count > 0); + + // Only emit the event if some balance was changed. + if (isSomeBalanceChanged) { + await emitSnapKeyringEvent(snap, KeyringEvent.AccountBalancesUpdated, { + balances: balancesUpdatedPayload, + }); + } + } + + /** + * Checks if the asset has changed compared to passed assets lookup. + * + * @param asset - The asset to check. + * @param assetsLookup - The lookup table to check against. + * @returns True if the asset has changed, false otherwise. + */ + static hasChanged(asset: AssetEntity, assetsLookup: AssetEntity[]): boolean { + const savedAsset = assetsLookup.find( + (item) => + item.keyringAccountId === asset.keyringAccountId && + item.assetType === asset.assetType, + ); + + if (!savedAsset) { + return true; + } + + const rawAmountChanged = savedAsset.rawAmount !== asset.rawAmount; + const uiAmountChanged = savedAsset.uiAmount !== asset.uiAmount; + + return rawAmountChanged || uiAmountChanged; + } + + async getAll(): Promise { + return this.#assetsRepository.getAll(); + } + + /** + * Returns a single account asset by CAIP-19 ID, or `null` if missing. + * + * @param accountId - Keyring account ID. + * @param assetId - CAIP-19 asset ID. + */ + async getAccountAssetByID( + accountId: string, + assetId: string, + ): Promise { + const { chainId } = parseCaipAssetType(assetId as CaipAssetType); + + const assets = await this.getAccountAssetsByScope(chainId, accountId); + + return assets.find((asset) => asset.assetType === assetId) ?? null; + } + + /** + * Returns account assets for the given CAIP-19 IDs, keyed by asset ID. + * Missing assets are `null`. + * + * @param accountId - Keyring account ID. + * @param assetIds - CAIP-19 asset IDs to resolve. + */ + async getAccountAssetsByIDs( + accountId: string, + assetIds: string[], + ): Promise> { + if (assetIds.length === 0) { + return {}; + } + + const account = await this.#accountsService.findById(accountId); + + if (!account) { + return Object.fromEntries(assetIds.map((assetId) => [assetId, null])); + } + + const accountAssets = await this.findByAccount(account); + + return Object.fromEntries( + assetIds.map((assetId) => [ + assetId, + accountAssets.find((asset) => asset.assetType === assetId) ?? null, + ]), + ); + } + + /** + * Returns controller-backed assets for an account on the given Solana scope. + * + * @param scope - CAIP-2 chain ID to filter results. + * @param accountId - Keyring account ID. + */ + async getAccountAssetsByScope( + scope: CaipChainId, + accountId: string, + ): Promise { + const account = await this.#accountsService.findById(accountId); + + if (!account) { + return []; + } + + const accountAssets = await this.findByAccount(account); + + return accountAssets.filter((asset) => asset.assetType.startsWith(scope)); + } + + /** + * Returns assets for an account across all active Solana networks. + * + * @param accountId - Keyring account ID. + */ + async getAccountAssetsForAllActiveScopes( + accountId: string, + ): Promise { + const activeNetworks = await this.#configProvider.getActiveNetworks(); + + const assetsByScope = await Promise.all( + activeNetworks.map((network) => + this.getAccountAssetsByScope(network, accountId), + ), + ); + + return assetsByScope.flat(); + } + + async findByAccount(account: SolanaKeyringAccount): Promise { + const { id: keyringAccountId, address } = account; + + const savedAssets = + await this.#assetsRepository.findByKeyringAccountId(keyringAccountId); + + // Every account must have at least the native assets. Ensure that they are always present, even if not yet fetched/saved. + const nativeAssetTypes = await this.getNativeAssetTypes(); + const missingNativeAssets: NativeAsset[] = []; + + for (const nativeAssetType of nativeAssetTypes) { + const hasNativeAsset = savedAssets.some( + (asset) => asset.assetType === nativeAssetType, + ); + + if (!hasNativeAsset) { + // Create a placeholder native asset with zero balance + // This will be updated when assets are actually fetched + const network = getNetworkFromToken(nativeAssetType); + + missingNativeAssets.push({ + assetType: nativeAssetType, + keyringAccountId: account.id, + network, + address, + symbol: 'SOL', + decimals: 9, + rawAmount: '0', + uiAmount: '0', + }); + } + } + + return [...savedAssets, ...missingNativeAssets]; + } +} diff --git a/packages/snap/src/core/services/assets/index.ts b/packages/snap/src/core/services/assets/index.ts index cfec7e81..b6055f7a 100644 --- a/packages/snap/src/core/services/assets/index.ts +++ b/packages/snap/src/core/services/assets/index.ts @@ -1,3 +1,6 @@ +export * from './adapters/CoreAssetsAdapter'; +export * from './adapters/SnapAssetsAdapter'; +export * from './snapOwnedAssets'; export * from './AssetsRepository'; export * from './AssetsService'; export * from './TokenHelper'; diff --git a/packages/snap/src/core/services/assets/shouldTrackSnapAssets.test.ts b/packages/snap/src/core/services/assets/shouldTrackSnapAssets.test.ts new file mode 100644 index 00000000..8d8110dc --- /dev/null +++ b/packages/snap/src/core/services/assets/shouldTrackSnapAssets.test.ts @@ -0,0 +1,14 @@ +import { SnapsAssetsMigrationStage } from '@metamask/assets-controller'; + +import { shouldTrackSnapAssets } from './shouldTrackSnapAssets'; + +describe('shouldTrackSnapAssets', () => { + it.each([ + [SnapsAssetsMigrationStage.Off, true], + [SnapsAssetsMigrationStage.ReadAssetsControllerWithFallback, true], + [SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, true], + [SnapsAssetsMigrationStage.ReadAssetsControllerOnly, false], + ])('returns %s for stage %s', (stage, expected) => { + expect(shouldTrackSnapAssets(stage)).toBe(expected); + }); +}); diff --git a/packages/snap/src/core/services/assets/shouldTrackSnapAssets.ts b/packages/snap/src/core/services/assets/shouldTrackSnapAssets.ts new file mode 100644 index 00000000..cd15b341 --- /dev/null +++ b/packages/snap/src/core/services/assets/shouldTrackSnapAssets.ts @@ -0,0 +1,14 @@ +import { SnapsAssetsMigrationStage } from '@metamask/assets-controller'; + +/** + * Returns whether the Snap should persist fungible asset balances for the given + * migration stage. NFT assets are always tracked by the Snap regardless of stage. + * + * @param stage - Assets migration stage for the chain. + * @returns Whether Snap-side fungible asset tracking is enabled. + */ +export function shouldTrackSnapAssets( + stage: SnapsAssetsMigrationStage, +): boolean { + return stage < SnapsAssetsMigrationStage.ReadAssetsControllerOnly; +} diff --git a/packages/snap/src/core/services/assets/snapOwnedAssets.test.ts b/packages/snap/src/core/services/assets/snapOwnedAssets.test.ts new file mode 100644 index 00000000..3b249e59 --- /dev/null +++ b/packages/snap/src/core/services/assets/snapOwnedAssets.test.ts @@ -0,0 +1,17 @@ +import { KnownCaip19Id } from '../../constants/solana'; +import { isSnapOwnedAsset } from './snapOwnedAssets'; + +describe('isSnapOwnedAsset', () => { + it('returns true for NFT CAIP-19 asset IDs', () => { + expect( + isSnapOwnedAsset( + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/nft:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + ), + ).toBe(true); + }); + + it('returns false for fungible native and token asset IDs', () => { + expect(isSnapOwnedAsset(KnownCaip19Id.SolMainnet)).toBe(false); + expect(isSnapOwnedAsset(KnownCaip19Id.UsdcMainnet)).toBe(false); + }); +}); diff --git a/packages/snap/src/core/services/assets/snapOwnedAssets.ts b/packages/snap/src/core/services/assets/snapOwnedAssets.ts new file mode 100644 index 00000000..872deb88 --- /dev/null +++ b/packages/snap/src/core/services/assets/snapOwnedAssets.ts @@ -0,0 +1,13 @@ +/** + * Returns whether an asset remains exclusively managed by the Snap. + * + * AssetsController does not persist Solana NFT balances. NFT assets must always + * be read, synchronized, persisted, and published by the Snap, regardless of + * the assets migration stage. + * + * @param assetId - CAIP-19 asset ID. + * @returns Whether the asset is exclusively managed by the Snap. + */ +export function isSnapOwnedAsset(assetId: string): boolean { + return assetId.includes('/nft:'); +} diff --git a/packages/snap/src/core/services/send/SendService.test.ts b/packages/snap/src/core/services/send/SendService.test.ts index bf47c833..7ef7660c 100644 --- a/packages/snap/src/core/services/send/SendService.test.ts +++ b/packages/snap/src/core/services/send/SendService.test.ts @@ -108,7 +108,7 @@ describe('SendService', () => { } as unknown as SendSplTokenBuilder; mockAssetsService = { - findByAccount: jest.fn(), + getAccountAssetsByIDs: jest.fn(), } as unknown as AssetsService; (fromTransactionToBase64String as jest.Mock).mockReturnValue( @@ -291,8 +291,16 @@ describe('SendService', () => { beforeEach(() => { jest - .spyOn(mockAssetsService, 'findByAccount') - .mockResolvedValue(mockAssetBalances); + .spyOn(mockAssetsService, 'getAccountAssetsByIDs') + .mockImplementation(async (_accountId, assetIds) => + Object.fromEntries( + assetIds.map((assetId) => [ + assetId, + mockAssetBalances.find((asset) => asset.assetType === assetId) ?? + null, + ]), + ), + ); jest.spyOn(mockConnection, 'getRpc').mockReturnValue({ getMinimumBalanceForRentExemption: jest.fn().mockReturnValue({ @@ -325,7 +333,10 @@ describe('SendService', () => { }); it('rejects when asset balance not found', async () => { - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([]); + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [mockRequest.params.assetId]: null, + [Networks[Network.Mainnet].nativeToken.caip19Id]: null, + }); await expect(sendService.onAmountInput(mockRequest)).rejects.toThrow( `Balance not found for asset ${mockRequest.params.assetId} and account ${mockAccount.id}`, @@ -338,8 +349,8 @@ describe('SendService', () => { params: { ...mockRequest.params, value: '0.000001' }, }; - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [Networks[Network.Mainnet].nativeToken.caip19Id]: { assetType: Networks[Network.Mainnet].nativeToken.caip19Id, uiAmount: '0.00001', keyringAccountId: mockAccount.id, @@ -349,7 +360,17 @@ describe('SendService', () => { decimals: Networks[Network.Mainnet].nativeToken.decimals, rawAmount: '999999999999999999', }, - ]); + [mockRequest.params.assetId]: { + assetType: Networks[Network.Mainnet].nativeToken.caip19Id, + uiAmount: '0.00001', + keyringAccountId: mockAccount.id, + network: Network.Mainnet, + address: mockAccount.address, + symbol: Networks[Network.Mainnet].nativeToken.symbol, + decimals: Networks[Network.Mainnet].nativeToken.decimals, + rawAmount: '999999999999999999', + }, + }); const result = await sendService.onAmountInput(lowBalanceRequest); @@ -397,8 +418,8 @@ describe('SendService', () => { }, }; - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [Networks[Network.Mainnet].nativeToken.caip19Id]: { assetType: Networks[Network.Mainnet].nativeToken.caip19Id, uiAmount: '0.1', keyringAccountId: mockAccount.id, @@ -408,7 +429,7 @@ describe('SendService', () => { decimals: Networks[Network.Mainnet].nativeToken.decimals, rawAmount: '10000000000', }, - { + [KnownCaip19Id.UsdcMainnet]: { assetType: KnownCaip19Id.UsdcMainnet, uiAmount: '0.001', keyringAccountId: mockAccount.id, @@ -419,7 +440,7 @@ describe('SendService', () => { decimals: 6, rawAmount: '1000000', }, - ]); + }); const result = await sendService.onAmountInput(zeroBalanceRequest); @@ -435,8 +456,18 @@ describe('SendService', () => { params: { ...mockRequest.params, value: '0.1' }, }; - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [Networks[Network.Mainnet].nativeToken.caip19Id]: { + assetType: Networks[Network.Mainnet].nativeToken.caip19Id, + uiAmount: '0', + keyringAccountId: mockAccount.id, + network: Network.Mainnet, + address: mockAccount.address, + symbol: Networks[Network.Mainnet].nativeToken.symbol, + decimals: Networks[Network.Mainnet].nativeToken.decimals, + rawAmount: '0', + }, + [mockRequest.params.assetId]: { assetType: Networks[Network.Mainnet].nativeToken.caip19Id, uiAmount: '0', keyringAccountId: mockAccount.id, @@ -446,7 +477,7 @@ describe('SendService', () => { decimals: Networks[Network.Mainnet].nativeToken.decimals, rawAmount: '0', }, - ]); + }); const result = await sendService.onAmountInput(zeroSolRequest); @@ -465,8 +496,8 @@ describe('SendService', () => { }, }; - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [KnownCaip19Id.UsdcMainnet]: { assetType: KnownCaip19Id.UsdcMainnet, uiAmount: '100.0', keyringAccountId: mockAccount.id, @@ -477,7 +508,7 @@ describe('SendService', () => { decimals: 6, rawAmount: '100000000000', }, - { + [Networks[Network.Mainnet].nativeToken.caip19Id]: { assetType: Networks[Network.Mainnet].nativeToken.caip19Id, uiAmount: '1.0', keyringAccountId: mockAccount.id, @@ -487,7 +518,7 @@ describe('SendService', () => { decimals: Networks[Network.Mainnet].nativeToken.decimals, rawAmount: '10000000000', }, - ]); + }); const result = await sendService.onAmountInput(tokenRequest); @@ -506,8 +537,8 @@ describe('SendService', () => { }, }; - jest.spyOn(mockAssetsService, 'findByAccount').mockResolvedValue([ - { + jest.spyOn(mockAssetsService, 'getAccountAssetsByIDs').mockResolvedValue({ + [KnownCaip19Id.UsdcMainnet]: { assetType: KnownCaip19Id.UsdcMainnet, uiAmount: '100.0', keyringAccountId: mockAccount.id, @@ -518,7 +549,7 @@ describe('SendService', () => { decimals: 6, rawAmount: '100000000000', }, - { + [Networks[Network.Mainnet].nativeToken.caip19Id]: { assetType: Networks[Network.Mainnet].nativeToken.caip19Id, uiAmount: '0.0001', keyringAccountId: mockAccount.id, @@ -528,7 +559,7 @@ describe('SendService', () => { decimals: Networks[Network.Mainnet].nativeToken.decimals, rawAmount: '10000000000', }, - ]); + }); const result = await sendService.onAmountInput(tokenRequest); @@ -549,7 +580,9 @@ describe('SendService', () => { it('handles errors if balances are not found', async () => { const error = new Error('Failed to fetch balances'); - jest.spyOn(mockAssetsService, 'findByAccount').mockRejectedValue(error); + jest + .spyOn(mockAssetsService, 'getAccountAssetsByIDs') + .mockRejectedValue(error); await expect(sendService.onAmountInput(mockRequest)).rejects.toThrow( 'Failed to fetch balances', diff --git a/packages/snap/src/core/services/send/SendService.ts b/packages/snap/src/core/services/send/SendService.ts index cbfea69f..14593aaf 100644 --- a/packages/snap/src/core/services/send/SendService.ts +++ b/packages/snap/src/core/services/send/SendService.ts @@ -225,15 +225,13 @@ export class SendService { const isNativeToken = assetId === nativeAssetType; - const accountBalances = await this.#assetsService.findByAccount(account); - - const assetEntry = accountBalances.find( - (asset) => asset.assetType === assetId, + const assetsById = await this.#assetsService.getAccountAssetsByIDs( + accountId, + [assetId, nativeAssetType], ); - const nativeAsset = accountBalances.find( - (asset) => asset.assetType === nativeAssetType, - ); + const assetEntry = assetsById[assetId]; + const nativeAsset = assetsById[nativeAssetType]; if (!assetEntry) { throw new Error( diff --git a/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.test.ts b/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.test.ts index 578e2dca..6005be58 100644 --- a/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.test.ts +++ b/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.test.ts @@ -16,28 +16,20 @@ import type { } from '../../../entities'; import { KnownCaip19Id, Network } from '../../constants/solana'; import { MOCK_SOLANA_KEYRING_ACCOUNTS } from '../../test/mocks/solana-keyring-accounts'; -import { trackError } from '../../utils/errors'; import type { AccountsSynchronizer } from '../accounts'; import type { AccountsService } from '../accounts/AccountsService'; -import type { AssetsService, TokenHelper } from '../assets'; import type { ConfigProvider } from '../config'; import { mockLogger } from '../mocks/logger'; import type { TransactionsService } from '../transactions'; import { KeyringAccountMonitor } from './KeyringAccountMonitor'; import type { SubscriptionService } from './SubscriptionService'; -jest.mock('../../utils/errors', () => ({ - trackError: jest.fn().mockResolvedValue('tracked-error-id'), -})); - describe('KeyringAccountMonitor', () => { let keyringAccountMonitor: KeyringAccountMonitor; let mockSubscriptionService: SubscriptionService; let mockAccountService: AccountsService; - let mockAssetsService: AssetsService; let mockTransactionsService: TransactionsService; let mockAccountsSynchronizer: AccountsSynchronizer; - let mockTokenHelper: TokenHelper; let mockConfigProvider: ConfigProvider; const account = MOCK_SOLANA_KEYRING_ACCOUNTS[0]; @@ -124,17 +116,6 @@ describe('KeyringAccountMonitor', () => { findByAddress: jest.fn(), } as unknown as AccountsService; - mockAssetsService = { - getTokenAccountsByOwnerMultiple: jest.fn(), - save: jest.fn(), - getAssetsMetadata: jest.fn().mockImplementation((assetType) => ({ - [assetType]: { - symbol: 'USDC', - decimals: 6, - }, - })), - } as unknown as AssetsService; - mockTransactionsService = { fetchLatestSignatures: jest.fn(), fetchBySignature: jest.fn(), @@ -145,11 +126,6 @@ describe('KeyringAccountMonitor', () => { synchronize: jest.fn(), } as unknown as AccountsSynchronizer; - mockTokenHelper = { - uiAmountToAmountForMint: jest.fn(), - amountToUiAmountForMint: jest.fn(), - } as unknown as TokenHelper; - mockConfigProvider = { getActiveNetworks: jest .fn() @@ -159,10 +135,8 @@ describe('KeyringAccountMonitor', () => { keyringAccountMonitor = new KeyringAccountMonitor( mockSubscriptionService, mockAccountService, - mockAssetsService, mockTransactionsService, mockAccountsSynchronizer, - mockTokenHelper, mockConfigProvider, mockLogger, ); @@ -361,24 +335,13 @@ describe('KeyringAccountMonitor', () => { params: [account.address, { commitment: 'confirmed' as const }], } as unknown as Subscription; - it('saves the new balance of the native asset', async () => { + it('persists the causing transaction without saving asset balance', async () => { await keyringAccountMonitor.setMonitoredAccounts([account.id]); // Send the notification by manually calling the handler const handler = accountNotificationHandlers[0]!; await handler(mockNotification, mockSubscription); - expect(mockAssetsService.save).toHaveBeenCalledWith({ - assetType: KnownCaip19Id.SolMainnet, - keyringAccountId: account.id, - network: Network.Mainnet, - address: account.address, - symbol: 'SOL', - decimals: 9, - rawAmount: '1000000000', - uiAmount: '1', - }); - expect(mockTransactionsService.save).toHaveBeenCalledWith( mockCausingTransaction, ); @@ -424,35 +387,6 @@ describe('KeyringAccountMonitor', () => { expect(mockTransactionsService.save).not.toHaveBeenCalled(); }); - - it('throws an error when lamports is missing', async () => { - const mockNotificationWithMissingLamports: AccountNotification = { - jsonrpc: '2.0', - method: 'accountNotification', - params: { - subscription: 1, - result: { - context: { - slot: 1, - }, - value: { - data: {}, - executable: false, - lamports: undefined as unknown as number, // Lamports is missing - owner: '11111111111111111111111111111111', - rentEpoch: null, - }, - }, - }, - }; - - await keyringAccountMonitor.setMonitoredAccounts([account.id]); - - const handler = accountNotificationHandlers[0]!; - await expect( - handler(mockNotificationWithMissingLamports, mockSubscription), - ).rejects.toThrow('Expected a number, but received: undefined'); - }); }); describe('when a token asset changed', () => { @@ -503,44 +437,12 @@ describe('KeyringAccountMonitor', () => { params: [TOKEN_PROGRAM_ADDRESS, { commitment: 'confirmed' as const }], } as unknown as Subscription; - beforeEach(() => { - jest - .spyOn(mockTokenHelper, 'amountToUiAmountForMint') - .mockResolvedValue('123.456789'); - }); - - it('tracks ui amount fallback errors and keeps the raw value', async () => { - const error = new Error('Conversion failed'); - - jest - .spyOn(mockTokenHelper, 'amountToUiAmountForMint') - .mockRejectedValue(error); - - await keyringAccountMonitor.setMonitoredAccounts([account.id]); - - const handler = programNotificationHandlers[0]!; - await handler(mockNotification, mockSubscription); - - expect(trackError).toHaveBeenCalledWith(error); - }); - - it('saves the new balance of the token asset and the transaction that caused it', async () => { + it('persists the causing transaction without saving token balance', async () => { await keyringAccountMonitor.setMonitoredAccounts([account.id]); const handler = programNotificationHandlers[0]!; await handler(mockNotification, mockSubscription); - expect(mockAssetsService.save).toHaveBeenCalledWith({ - assetType: KnownCaip19Id.UsdcMainnet, - keyringAccountId: account.id, - network: Network.Mainnet, - mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - pubkey: '9wt9PfjPD3JCy5r7o4K1cTGiuTG7fq2pQhdDCdQALKjg', - symbol: 'USDC', - decimals: 6, - rawAmount: '123456789', - uiAmount: '123.456789', - }); expect(mockTransactionsService.save).toHaveBeenCalledWith( mockCausingTransaction, ); @@ -557,59 +459,8 @@ describe('KeyringAccountMonitor', () => { ); }); - it('throws an error when mint address is missing', async () => { - const mockNotificationWithMissingMint: ProgramNotification = { - jsonrpc: '2.0', - method: 'programNotification', - params: { - subscription: 1, - result: { - context: { - slot: 1, - }, - value: { - pubkey: '9wt9PfjPD3JCy5r7o4K1cTGiuTG7fq2pQhdDCdQALKjg', - account: { - data: { - parsed: { - info: { - isNative: false, - mint: undefined as unknown as string, // Mint is missing - owner: account.address, - state: 'initialized', - tokenAmount: { - amount: '20011079', - decimals: 6, - uiAmount: 20.011079, - uiAmountString: '20.011079', - }, - }, - type: 'account', - }, - program: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', - space: 165, - }, - executable: true, - lamports: 1000000000, - owner: account.address, - rentEpoch: 1, - }, - }, - }, - }, - }; - - await keyringAccountMonitor.setMonitoredAccounts([account.id]); - const handler = programNotificationHandlers[0]!; - - await expect( - handler(mockNotificationWithMissingMint, mockSubscription), - ).rejects.toThrow('Expected a string, but received: undefined'); - expect(mockAssetsService.save).not.toHaveBeenCalled(); - }); - - it('throws an error when uiAmountString is missing', async () => { - const mockNotificationWithMissingUiAmountString: ProgramNotification = { + it('throws an error when owner address is missing', async () => { + const mockNotificationWithMissingOwner: ProgramNotification = { jsonrpc: '2.0', method: 'programNotification', params: { @@ -626,13 +477,13 @@ describe('KeyringAccountMonitor', () => { info: { isNative: false, mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - owner: account.address, + owner: undefined as unknown as string, // Owner is missing state: 'initialized', tokenAmount: { amount: '20011079', decimals: 6, uiAmount: 20.011079, - uiAmountString: undefined as unknown as string, // uiAmountString is missing + uiAmountString: '20.011079', }, }, type: 'account', @@ -654,9 +505,9 @@ describe('KeyringAccountMonitor', () => { const handler = programNotificationHandlers[0]!; await expect( - handler(mockNotificationWithMissingUiAmountString, mockSubscription), + handler(mockNotificationWithMissingOwner, mockSubscription), ).rejects.toThrow('Expected a string, but received: undefined'); - expect(mockAssetsService.save).not.toHaveBeenCalled(); + expect(mockTransactionsService.save).not.toHaveBeenCalled(); }); describe('when #saveCausingTransaction encounters errors', () => { diff --git a/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.ts b/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.ts index 38eb4c32..34dc2ba1 100644 --- a/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.ts +++ b/packages/snap/src/core/services/subscriptions/KeyringAccountMonitor.ts @@ -1,8 +1,8 @@ -import { assert, number, string } from '@metamask/superstruct'; +import { assert, string } from '@metamask/superstruct'; import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022'; import type { Base58EncodedBytes } from '@solana/kit'; -import { address as asAddress, lamports } from '@solana/kit'; +import { address as asAddress } from '@solana/kit'; import { get, uniq } from 'lodash'; import type { SubscriptionService } from '.'; @@ -13,15 +13,10 @@ import type { Subscription, } from '../../../entities'; import type { Network } from '../../constants/solana'; -import { SolanaCaip19Tokens } from '../../constants/solana'; -import { trackError } from '../../utils/errors'; -import { fromTokenUnits } from '../../utils/fromTokenUnit'; import { createPrefixedLogger } from '../../utils/logger'; import type { ILogger } from '../../utils/logger'; -import { tokenAddressToCaip19 } from '../../utils/tokenAddressToCaip19'; import type { AccountsSynchronizer } from '../accounts'; import type { AccountsService } from '../accounts/AccountsService'; -import type { AssetsService, TokenHelper } from '../assets'; import type { ConfigProvider } from '../config'; import { SUPPORTED_NETWORKS } from '../config/ConfigProvider'; import type { TransactionsService } from '../transactions'; @@ -34,7 +29,6 @@ import { isSpam } from '../transactions/utils/isSpam'; * - It gets updates when the balance of token assets change by subscribing to each RPC token account. * * On each update: - * - It saves the new balance. Under the hood, AssetsService also notifies the extension. * - It fetches the transaction that caused the native asset or token asset to change and saves it. Under the hood, TransactionsService also notifies the extension. */ export class KeyringAccountMonitor { @@ -42,14 +36,10 @@ export class KeyringAccountMonitor { readonly #accountService: AccountsService; - readonly #assetsService: AssetsService; - readonly #transactionsService: TransactionsService; readonly #accountsSynchronizer: AccountsSynchronizer; - readonly #tokenHelper: TokenHelper; - readonly #configProvider: ConfigProvider; readonly #logger: ILogger; @@ -62,19 +52,15 @@ export class KeyringAccountMonitor { constructor( subscriptionService: SubscriptionService, accountService: AccountsService, - assetsService: AssetsService, transactionsService: TransactionsService, accountsSynchronizer: AccountsSynchronizer, - tokenHelper: TokenHelper, configProvider: ConfigProvider, logger: ILogger, ) { this.#subscriptionService = subscriptionService; this.#accountService = accountService; - this.#assetsService = assetsService; this.#transactionsService = transactionsService; this.#accountsSynchronizer = accountsSynchronizer; - this.#tokenHelper = tokenHelper; this.#configProvider = configProvider; this.#logger = createPrefixedLogger(logger, '[🗝️ KeyringAccountMonitor]'); @@ -322,25 +308,7 @@ export class KeyringAccountMonitor { throw new Error(`No keyring account found for address: ${address}`); } - // Handle the notification with clean data - const { lamports: accountLamports } = notification.params.result.value; - assert(accountLamports, number()); - - const decimals = 9; - - await Promise.all([ - this.#assetsService.save({ - assetType: `${network}/${SolanaCaip19Tokens.SOL}`, - keyringAccountId: keyringAccount.id, - network, - address, - symbol: 'SOL', - decimals, - rawAmount: accountLamports.toString(), - uiAmount: fromTokenUnits(accountLamports, decimals), - }), - this.#saveCausingTransaction(keyringAccount, network, address), - ]); + await this.#saveCausingTransaction(keyringAccount, network, address); } async #handleProgramNotification( @@ -367,60 +335,15 @@ export class KeyringAccountMonitor { const { owner } = notification.params.result.value.account.data.parsed.info; assert(owner, string()); - const { mint } = notification.params.result.value.account.data.parsed.info; - assert(mint, string()); - - const { amount, decimals, uiAmountString } = - notification.params.result.value.account.data.parsed.info.tokenAmount; - assert(amount, string()); - assert(decimals, number()); - assert(uiAmountString, string()); - const { pubkey } = notification.params.result.value; assert(pubkey, string()); - const assetType = tokenAddressToCaip19(network, mint); - const keyringAccount = await this.#accountService.findByAddress(owner); if (!keyringAccount) { throw new Error(`No keyring account found with address: ${owner}`); } - /** - * WARNING: This is to compensate for the fact that the notification returned by Infura's programSubscribe - * includes a uiAmount/uiAmountString that does not take into account the mint's multiplier (if any). - * In theory, it should; because the regular Solana RPC (wss://api.mainnet-beta.solana.com) does. - * - * So this needs to be removed once Infura fixes their programSubscribe notification. - */ - const uiAmount = await this.#tokenHelper - .amountToUiAmountForMint(mint, network, lamports(BigInt(amount))) - .catch(async (error) => { - await trackError(error); - this.#logger.error('Error converting amount to uiAmount', error); - return uiAmountString; - }); - - const metadata = (await this.#assetsService.getAssetsMetadata([assetType]))[ - assetType - ]; - - await Promise.all([ - // Update the balance of the token asset - this.#assetsService.save({ - assetType, - keyringAccountId: keyringAccount.id, - network, - mint, - pubkey, - symbol: metadata?.symbol ?? 'UNKNOWN', - decimals, - rawAmount: amount, - uiAmount, - }), - // Fetch and save the transaction that caused the token asset change. - this.#saveCausingTransaction(keyringAccount, network, pubkey), - ]); + await this.#saveCausingTransaction(keyringAccount, network, pubkey); } /** diff --git a/packages/snap/src/core/test/helpers/registerCoreAssetsControllerHandlers.ts b/packages/snap/src/core/test/helpers/registerCoreAssetsControllerHandlers.ts new file mode 100644 index 00000000..232388b4 --- /dev/null +++ b/packages/snap/src/core/test/helpers/registerCoreAssetsControllerHandlers.ts @@ -0,0 +1,115 @@ +import type { Asset } from '@metamask/assets-controller'; +import type { SimulationUserOptions } from '@metamask/snaps-simulation'; +import type { CaipAssetType } from '@metamask/utils'; + +type ControllerMessenger = { + registerActionHandler: ( + action: string, + handler: (...args: unknown[]) => unknown, + ) => void; +}; + +const DEFAULT_RAW_AMOUNT = '123456789'; + +function buildMockAsset( + assetId: CaipAssetType, + metadata?: { symbol: string; name: string }, +): Asset { + const chainId = assetId.split('/')[0] as Asset['chainId']; + const isNative = assetId.endsWith('/slip44:501'); + + return { + id: assetId, + chainId, + balance: { amount: DEFAULT_RAW_AMOUNT }, + metadata: { + type: isNative ? 'native' : 'spl', + symbol: metadata?.symbol ?? (isNative ? 'SOL' : 'TOKEN'), + name: metadata?.name ?? (isNative ? 'Solana' : 'Token'), + decimals: 9, + }, + price: { + assetPriceType: 'fungible', + price: 1, + usdPrice: 1, + lastUpdated: 0, + }, + fiatValue: 1, + }; +} + +function buildAssetsForAccount( + accountId: string, + options: SimulationUserOptions, +): Record { + const account = options.accounts?.find((entry) => entry.id === accountId); + if (!account?.assets?.length) { + return {}; + } + + const assets: Record = {}; + for (const assetId of account.assets) { + assets[assetId] = buildMockAsset(assetId, options.assets?.[assetId]); + } + return assets; +} + +/** + * Registers AssetsController messenger handlers for snaps-jest simulation. + * Maps installSnap `accounts` / `assets` options to Core AssetsController reads. + * + * @param controllerMessenger - Controller messenger used to register simulation handlers. + * @param options - installSnap simulation options (`accounts` / `assets`). + */ +export function registerCoreAssetsControllerHandlers( + controllerMessenger: ControllerMessenger, + options: SimulationUserOptions, +): void { + controllerMessenger.registerActionHandler( + 'AssetsController:getAccountAssetByID', + (...args: unknown[]) => { + const accountId = args[0] as string; + const assetId = args[1] as string; + const assets = buildAssetsForAccount(accountId, options); + return assets[assetId]; + }, + ); + + controllerMessenger.registerActionHandler( + 'AssetsController:getAccountAssetsByIDs', + (...args: unknown[]) => { + const accountId = args[0] as string; + const assetIds = args[1] as string[]; + const assets = buildAssetsForAccount(accountId, options); + const result: Record = {}; + for (const assetId of assetIds) { + const asset = assets[assetId]; + if (asset) { + result[assetId] = asset; + } + } + return result; + }, + ); + + controllerMessenger.registerActionHandler( + 'AssetsController:getAccountAssetsByScope', + (...args: unknown[]) => { + const accountId = args[0] as string; + const scope = args[1] as string; + const assets = buildAssetsForAccount(accountId, options); + const result: Record = {}; + for (const [assetId, asset] of Object.entries(assets)) { + if (assetId.startsWith(`${scope}/`)) { + result[assetId] = asset; + } + } + return result; + }, + ); + + controllerMessenger.registerActionHandler( + 'RemoteFeatureFlagController:getState', + () => ({ remoteFeatureFlags: {} }), + ); +} diff --git a/packages/snap/src/features/send/render.test.tsx b/packages/snap/src/features/send/render.test.tsx index 89bba751..15a7a73f 100644 --- a/packages/snap/src/features/send/render.test.tsx +++ b/packages/snap/src/features/send/render.test.tsx @@ -29,6 +29,7 @@ import { TEST_ORIGIN } from '../../core/test/utils'; import type { Preferences } from '../../core/types/snap'; import { buildUrl } from '../../core/utils/buildUrl'; import { trackError } from '../../core/utils/errors'; +import type { AssetEntity } from '../../entities'; import { accountsService, assetsService, @@ -529,21 +530,27 @@ describe('Send tracking', () => { throw new Error(`Unexpected snap.request call: ${method}`); }); + const mockSendAssets: AssetEntity[] = [ + { + assetType: KnownCaip19Id.SolMainnet, + keyringAccountId: MOCK_SOLANA_KEYRING_ACCOUNT_0.id, + network: Network.Mainnet, + address: MOCK_SOLANA_KEYRING_ACCOUNT_0.address, + symbol: 'SOL', + decimals: 9, + rawAmount: '1', + uiAmount: '1', + }, + ]; + jest .spyOn(assetsService, 'getAll') .mockImplementation() - .mockResolvedValue([ - { - assetType: KnownCaip19Id.SolMainnet, - keyringAccountId: MOCK_SOLANA_KEYRING_ACCOUNT_0.id, - network: Network.Mainnet, - address: MOCK_SOLANA_KEYRING_ACCOUNT_0.address, - symbol: 'SOL', - decimals: 9, - rawAmount: '1', - uiAmount: '1', - }, - ]); + .mockResolvedValue(mockSendAssets); + jest + .spyOn(assetsService, 'getAccountAssetsByScope') + .mockImplementation() + .mockResolvedValue(mockSendAssets); jest.spyOn(assetsService, 'getAssetsMetadata').mockImplementation(); jest .spyOn(accountsService, 'getAll') diff --git a/packages/snap/src/features/send/render.tsx b/packages/snap/src/features/send/render.tsx index dc1524d9..2974b8fa 100644 --- a/packages/snap/src/features/send/render.tsx +++ b/packages/snap/src/features/send/render.tsx @@ -91,13 +91,19 @@ export const renderSend: OnRpcRequestHandler = async ({ request }) => { loading: true, }; - const [assetEntities, keyringAccounts, tokenPrices, preferences] = - await Promise.all([ - assetsService.getAll(), - accountsService.getAll(), - state.getKey('tokenPrices'), - getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), - ]); + const [keyringAccounts, tokenPrices, preferences] = await Promise.all([ + accountsService.getAll(), + state.getKey('tokenPrices'), + getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), + ]); + + const assetEntities = ( + await Promise.all( + keyringAccounts.map((keyringAccount) => + assetsService.getAccountAssetsByScope(scope, keyringAccount.id), + ), + ) + ).flat(); context.balances = getBalancesInScope(scope, assetEntities); context.assets = assetEntities.map((asset) => asset.assetType); diff --git a/packages/snap/src/snapContext.ts b/packages/snap/src/snapContext.ts index 58e48856..16138d44 100644 --- a/packages/snap/src/snapContext.ts +++ b/packages/snap/src/snapContext.ts @@ -1,3 +1,12 @@ +import { ASSETS_PROVIDER_NAME } from '@metamask-previews/snap-networks-utils'; +import type { + AssetsControllerGetAccountAssetByIDAction, + AssetsControllerGetAccountAssetsByIDsAction, + AssetsControllerGetAccountAssetsByScopeAction, +} from '@metamask/assets-controller'; +import type { Messenger } from '@metamask/messenger'; +import { getMessenger } from '@metamask/snaps-sdk'; + import type { ICache } from './core/caching/ICache'; import { InMemoryCache } from './core/caching/InMemoryCache'; import { StateCache } from './core/caching/StateCache'; @@ -13,8 +22,10 @@ import { AccountsService, AccountsSynchronizer, ApproveTokenService, + SnapAssetsAdapter, AssetsRepository, AssetsService, + CoreAssetsAdapter, KeyringAccountMonitor, MonitoredAccountsInitializer, RecipientClassifier, @@ -46,6 +57,7 @@ import { TransactionScanService } from './core/services/transaction-scan/Transac import { WalletService } from './core/services/wallet/WalletService'; import logger, { noOpLogger } from './core/utils/logger'; import { EventEmitter } from './infrastructure'; +import type { CoreMessenger } from './types/core-messenger'; /** * Initializes all the services using dependency injection. @@ -144,19 +156,44 @@ const tokenPricesService = new TokenPricesService({ const nameResolutionService = new NameResolutionService(connection, logger); const assetsRepository = new AssetsRepository(state); -const assetsService = new AssetsService({ + +const accountsRepository = new AccountsRepository(state); +const accountsService = new AccountsService(accountsRepository); + +const snapAssetsAdapter = new SnapAssetsAdapter({ connection, logger, configProvider, assetsRepository, + accountsService, tokenApiClient, cache: inMemoryCache, - tokenPricesService, nftApiClient, }); -const accountsRepository = new AccountsRepository(state); -const accountsService = new AccountsService(accountsRepository); +type CoreAssetsMessenger = Messenger< + typeof ASSETS_PROVIDER_NAME, + | AssetsControllerGetAccountAssetByIDAction + | AssetsControllerGetAccountAssetsByIDsAction + | AssetsControllerGetAccountAssetsByScopeAction +>; + +const coreAssetsMessenger = getMessenger(); +const coreAssetsAdapter = new CoreAssetsAdapter(coreAssetsMessenger); + +const featureFlagMessenger = getMessenger(); + +const assetsService = new AssetsService({ + logger, + configProvider, + snapAssetsAdapter, + coreAssetsAdapter, + coreMessenger: featureFlagMessenger, + accountsService, + tokenApiClient, + tokenPricesService, + nftApiClient, +}); const transactionsRepository = new TransactionsRepository(state); const transactionMapper = new TransactionMapper( @@ -201,10 +238,8 @@ const signatureMonitor = new SignatureMonitor( const keyringAccountMonitor = new KeyringAccountMonitor( subscriptionService, accountsService, - assetsService, transactionsService, accountsSynchronizer, - tokenHelper, configProvider, logger, ); diff --git a/packages/snap/src/types/core-messenger.ts b/packages/snap/src/types/core-messenger.ts new file mode 100644 index 00000000..39518af3 --- /dev/null +++ b/packages/snap/src/types/core-messenger.ts @@ -0,0 +1,8 @@ +import type { Messenger } from '@metamask/messenger'; +import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; +import type { AsyncMessenger } from '@metamask/snaps-sdk'; + +export type CoreMessengerActions = RemoteFeatureFlagControllerGetStateAction; + +export type CoreMessenger = Messenger; +export type CoreMessengerCaller = Pick, 'call'>; diff --git a/yarn.lock b/yarn.lock index 8e9bf1b7..ee1e3122 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2619,7 +2619,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:^5.7.0, @ethersproject/abi@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/abi@npm:5.8.0" dependencies: @@ -2664,7 +2664,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:^5.8.0": +"@ethersproject/address@npm:^5.7.0, @ethersproject/address@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/address@npm:5.8.0" dependencies: @@ -2686,7 +2686,17 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bignumber@npm:^5.8.0": +"@ethersproject/basex@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/basex@npm:5.8.0" + dependencies: + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + checksum: 10/1a8d48a9397461ea42ec43b69a15a0d13ba0b9192695713750d9d391503c55b258cca435fa78a4014d23a813053f1a471593b89c7c0d89351639a78d50a12ef2 + languageName: node + linkType: hard + +"@ethersproject/bignumber@npm:^5.7.0, @ethersproject/bignumber@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/bignumber@npm:5.8.0" dependencies: @@ -2715,6 +2725,24 @@ __metadata: languageName: node linkType: hard +"@ethersproject/contracts@npm:^5.7.0": + version: 5.8.0 + resolution: "@ethersproject/contracts@npm:5.8.0" + dependencies: + "@ethersproject/abi": "npm:^5.8.0" + "@ethersproject/abstract-provider": "npm:^5.8.0" + "@ethersproject/abstract-signer": "npm:^5.8.0" + "@ethersproject/address": "npm:^5.8.0" + "@ethersproject/bignumber": "npm:^5.8.0" + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/constants": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + "@ethersproject/transactions": "npm:^5.8.0" + checksum: 10/839f8211f5e560f15468ae843ba316ffeacab5cebcece1eec76bc5714472ebfe3453484f283d3e46b9d3faaffef1e17cc3583cf24e01638a1fd52f69012cf8d4 + languageName: node + linkType: hard + "@ethersproject/hash@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/hash@npm:5.8.0" @@ -2732,6 +2760,47 @@ __metadata: languageName: node linkType: hard +"@ethersproject/hdnode@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/hdnode@npm:5.8.0" + dependencies: + "@ethersproject/abstract-signer": "npm:^5.8.0" + "@ethersproject/basex": "npm:^5.8.0" + "@ethersproject/bignumber": "npm:^5.8.0" + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + "@ethersproject/pbkdf2": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + "@ethersproject/sha2": "npm:^5.8.0" + "@ethersproject/signing-key": "npm:^5.8.0" + "@ethersproject/strings": "npm:^5.8.0" + "@ethersproject/transactions": "npm:^5.8.0" + "@ethersproject/wordlists": "npm:^5.8.0" + checksum: 10/55b35cf30f0dd40e2d5ecd4b2f005ebea82a85a440717a61d4a483074f652d2c7063e9c704272b894bfdd500f7883aa36692931c6808591f702c1da7107ebb61 + languageName: node + linkType: hard + +"@ethersproject/json-wallets@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/json-wallets@npm:5.8.0" + dependencies: + "@ethersproject/abstract-signer": "npm:^5.8.0" + "@ethersproject/address": "npm:^5.8.0" + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/hdnode": "npm:^5.8.0" + "@ethersproject/keccak256": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + "@ethersproject/pbkdf2": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + "@ethersproject/random": "npm:^5.8.0" + "@ethersproject/strings": "npm:^5.8.0" + "@ethersproject/transactions": "npm:^5.8.0" + aes-js: "npm:3.0.0" + scrypt-js: "npm:3.0.1" + checksum: 10/5cbf7e698ee7f26f54fceb672d9824b01816cd785182e638cb5cd1eaed5d80d8a4576e3cad92af46ac6d23404a806a47a72d5dee908af42322d091553a0d8da6 + languageName: node + linkType: hard + "@ethersproject/keccak256@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/keccak256@npm:5.8.0" @@ -2758,6 +2827,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/pbkdf2@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/pbkdf2@npm:5.8.0" + dependencies: + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/sha2": "npm:^5.8.0" + checksum: 10/203bb992eec3042256702f4c8259a37202af7b341cc6e370614cdc52541042fc3b795fb040592bd6be8b67376a798c45312ca1e6d5d179c3e8eb7431882f1fd1 + languageName: node + linkType: hard + "@ethersproject/properties@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/properties@npm:5.8.0" @@ -2767,6 +2846,44 @@ __metadata: languageName: node linkType: hard +"@ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.2": + version: 5.8.0 + resolution: "@ethersproject/providers@npm:5.8.0" + dependencies: + "@ethersproject/abstract-provider": "npm:^5.8.0" + "@ethersproject/abstract-signer": "npm:^5.8.0" + "@ethersproject/address": "npm:^5.8.0" + "@ethersproject/base64": "npm:^5.8.0" + "@ethersproject/basex": "npm:^5.8.0" + "@ethersproject/bignumber": "npm:^5.8.0" + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/constants": "npm:^5.8.0" + "@ethersproject/hash": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + "@ethersproject/networks": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + "@ethersproject/random": "npm:^5.8.0" + "@ethersproject/rlp": "npm:^5.8.0" + "@ethersproject/sha2": "npm:^5.8.0" + "@ethersproject/strings": "npm:^5.8.0" + "@ethersproject/transactions": "npm:^5.8.0" + "@ethersproject/web": "npm:^5.8.0" + bech32: "npm:1.1.4" + ws: "npm:8.18.0" + checksum: 10/7d40fc0abb78fc9e69b71cb560beb2a93cf1da2cf978a061031a34c0ed76c2f5936ed8c0bdb9aa1307fe5308d0159e429b83b779dbd550639a886a88d6d17817 + languageName: node + linkType: hard + +"@ethersproject/random@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/random@npm:5.8.0" + dependencies: + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + checksum: 10/47c34a72c81183ac13a1b4635bb9d5cf1456e6329276f50c9e12711f404a9eb4536db824537ed05ef8839a0a358883dc3342d3ea83147b8bafeb767dc8f57e23 + languageName: node + linkType: hard + "@ethersproject/rlp@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/rlp@npm:5.8.0" @@ -2777,6 +2894,17 @@ __metadata: languageName: node linkType: hard +"@ethersproject/sha2@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/sha2@npm:5.8.0" + dependencies: + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + hash.js: "npm:1.1.7" + checksum: 10/ef8916e3033502476fba9358ba1993722ac3bb99e756d5681e4effa3dfa0f0bf0c29d3fa338662830660b45dd359cccb06ba40bc7b62cfd44f4a177b25829404 + languageName: node + linkType: hard + "@ethersproject/signing-key@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/signing-key@npm:5.8.0" @@ -2819,6 +2947,29 @@ __metadata: languageName: node linkType: hard +"@ethersproject/wallet@npm:^5.7.0": + version: 5.8.0 + resolution: "@ethersproject/wallet@npm:5.8.0" + dependencies: + "@ethersproject/abstract-provider": "npm:^5.8.0" + "@ethersproject/abstract-signer": "npm:^5.8.0" + "@ethersproject/address": "npm:^5.8.0" + "@ethersproject/bignumber": "npm:^5.8.0" + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/hash": "npm:^5.8.0" + "@ethersproject/hdnode": "npm:^5.8.0" + "@ethersproject/json-wallets": "npm:^5.8.0" + "@ethersproject/keccak256": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + "@ethersproject/random": "npm:^5.8.0" + "@ethersproject/signing-key": "npm:^5.8.0" + "@ethersproject/transactions": "npm:^5.8.0" + "@ethersproject/wordlists": "npm:^5.8.0" + checksum: 10/354c8985a74b1bb0a8ba80f374c1af882f7657716b974dda235184ee98151e30741b24f58a93c84693aa6e72a8a5c3ae62143966967f40f52f62093559388e6a + languageName: node + linkType: hard + "@ethersproject/web@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/web@npm:5.8.0" @@ -2832,6 +2983,19 @@ __metadata: languageName: node linkType: hard +"@ethersproject/wordlists@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/wordlists@npm:5.8.0" + dependencies: + "@ethersproject/bytes": "npm:^5.8.0" + "@ethersproject/hash": "npm:^5.8.0" + "@ethersproject/logger": "npm:^5.8.0" + "@ethersproject/properties": "npm:^5.8.0" + "@ethersproject/strings": "npm:^5.8.0" + checksum: 10/b8e6aa7d2195bb568847f360f6525ddc3d145404fbd4553e2e05daf4a95f58167591feb69e16e3398a28114ea85e1895fc8f5bd1c0cbf8b578123d7c1d21c32d + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.6.0": version: 1.6.8 resolution: "@floating-ui/core@npm:1.6.8" @@ -3955,6 +4119,142 @@ __metadata: languageName: node linkType: hard +"@metamask-previews/snap-networks-utils@npm:0.0.0-preview-8589132": + version: 0.0.0-preview-8589132 + resolution: "@metamask-previews/snap-networks-utils@npm:0.0.0-preview-8589132" + dependencies: + "@metamask/assets-controller": "npm:^13.0.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/snaps-sdk": "npm:^11.2.0" + "@metamask/utils": "npm:^11.9.0" + checksum: 10/b222dde615ee99357777cef14d72106f13b161ecf569d68efd023231d0fa25f1f2e47e56c92eb712663c68fdf131246e107f2610b8d149565bb4648960b803c0 + languageName: node + linkType: hard + +"@metamask/abi-utils@npm:^2.0.3": + version: 2.0.4 + resolution: "@metamask/abi-utils@npm:2.0.4" + dependencies: + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^9.0.0" + checksum: 10/3d32d42c6e98fc4719b2b53597e573764b80936c7cc31d884c87729c4c4f74a30e93096db87aaa7cbcec9d3bb7d22b1adfc98a8bcb4c7c2f17bfbddaa4367d34 + languageName: node + linkType: hard + +"@metamask/abi-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "@metamask/abi-utils@npm:3.0.0" + dependencies: + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.0.1" + checksum: 10/068b98185148b9e185b4af4392c6a6f82f1d4b1ff60013c57679c618f37afe9030e3ccc940e1a8b690be6f62ea91115ab18b73f3c3c09f4eff1794e31ababb9b + languageName: node + linkType: hard + +"@metamask/account-api@npm:^1.0.4": + version: 1.1.1 + resolution: "@metamask/account-api@npm:1.1.1" + dependencies: + "@metamask/keyring-api": "npm:^23.6.0" + "@metamask/keyring-utils": "npm:^4.0.0" + uuid: "npm:^9.0.1" + checksum: 10/2991dbfbbbe12437c2267c8be7f52f2eea8f6c3a021bcc21c30a07506a02894b207f29775be5e4e81a7453fc23dbafe94013fe1f48a3d6044d4527f95ae91827 + languageName: node + linkType: hard + +"@metamask/account-tree-controller@npm:^7.5.4, @metamask/account-tree-controller@npm:^7.5.5": + version: 7.5.5 + resolution: "@metamask/account-tree-controller@npm:7.5.5" + dependencies: + "@metamask/accounts-controller": "npm:^39.0.5" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/keyring-api": "npm:^23.5.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/multichain-account-service": "npm:^13.0.0" + "@metamask/profile-sync-controller": "npm:^28.3.0" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/snaps-utils": "npm:^12.1.2" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.11.0" + fast-deep-equal: "npm:^3.1.3" + lodash: "npm:^4.17.21" + peerDependencies: + "@metamask/providers": ^22.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/18913a9cdf65a5ab640d2cb608f6fb46b0f4ebce1537d4ed3e6502392efb15acb45b0648319a861421e6f527a40c7574e9e505fc4cefdc4f9fde0a06df1e428e + languageName: node + linkType: hard + +"@metamask/accounts-controller@npm:^39.0.5": + version: 39.0.5 + resolution: "@metamask/accounts-controller@npm:39.0.5" + dependencies: + "@ethereumjs/util": "npm:^9.1.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/eth-snap-keyring": "npm:^23.0.0" + "@metamask/keyring-api": "npm:^23.5.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-api": "npm:^11.0.1" + "@metamask/keyring-sdk": "npm:^2.2.0" + "@metamask/keyring-utils": "npm:^3.3.1" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^34.0.0" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.11.0" + deepmerge: "npm:^4.2.2" + ethereum-cryptography: "npm:^2.1.2" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/providers": ^22.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/5d505931fa24d9d091b4822fe3d640d712b0a65d97886ca4b50b7842613326e160d6b48ac23dcf5ac5a1507bf4c8207748d238fca1998ac90e86f5eaa02f74fc + languageName: node + linkType: hard + +"@metamask/accounts-controller@npm:^39.0.6": + version: 39.0.6 + resolution: "@metamask/accounts-controller@npm:39.0.6" + dependencies: + "@ethereumjs/util": "npm:^9.1.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/eth-snap-keyring": "npm:^23.0.0" + "@metamask/keyring-api": "npm:^23.7.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-api": "npm:^11.0.2" + "@metamask/keyring-sdk": "npm:^2.2.0" + "@metamask/keyring-utils": "npm:^3.3.1" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.11.0" + deepmerge: "npm:^4.2.2" + ethereum-cryptography: "npm:^2.1.2" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/providers": ^22.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/2ca978388382fc11f8555687c10f14ef500f119dd01aba095381cf9ddf49aa1269b0705b49e2b9b2c2b8b0e94a2d197fc40356727c562269af466a9a6ebca978 + languageName: node + linkType: hard + +"@metamask/address-book-controller@npm:^7.1.2": + version: 7.1.2 + resolution: "@metamask/address-book-controller@npm:7.1.2" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.0.0" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/utils": "npm:^11.9.0" + checksum: 10/e15140418452a80d51fec783003144add72a50a4fe362377481de83c7858860f641a3cfb1575f4b802b1eb1e873b3efd03bb4183afcbee77f95c7525e05aaed4 + languageName: node + linkType: hard + "@metamask/analytics-controller@npm:^1.1.1": version: 1.2.1 resolution: "@metamask/analytics-controller@npm:1.2.1" @@ -3968,6 +4268,20 @@ __metadata: languageName: node linkType: hard +"@metamask/analytics-controller@npm:^2.0.0": + version: 2.0.0 + resolution: "@metamask/analytics-controller@npm:2.0.0" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/geolocation-controller": "npm:^1.0.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/utils": "npm:^11.11.0" + lodash: "npm:^4.17.21" + uuid: "npm:^8.3.2" + checksum: 10/ec15f5d4ef5e9d6405f5ba6366760714e52f514fdba6913523b40848259e503637339f4a73fa9d6579b44cf15b2a3b4bdc9484b50c371f40f33f0054422c0411 + languageName: node + linkType: hard + "@metamask/api-specs@npm:^0.15.0": version: 0.15.0 resolution: "@metamask/api-specs@npm:0.15.0" @@ -4001,42 +4315,186 @@ __metadata: languageName: node linkType: hard -"@metamask/auto-changelog@npm:4.0.0": - version: 4.0.0 - resolution: "@metamask/auto-changelog@npm:4.0.0" +"@metamask/assets-controller@npm:13.0.0": + version: 13.0.0 + resolution: "@metamask/assets-controller@npm:13.0.0" dependencies: - diff: "npm:^5.0.0" - execa: "npm:^5.1.1" - semver: "npm:^7.3.5" - yargs: "npm:^17.0.1" - peerDependencies: - prettier: ">=3.0.0" - bin: - auto-changelog: dist/cli.js - checksum: 10/4968d316411e5f4820092391f79078839f49e0e26ecd0c48266b5a1e0e32614b75dad5f36b76e632a7a22a2f2eeeb762a693ae390b0373bff29081ff2a7eefda + "@ethereumjs/util": "npm:^9.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/account-tree-controller": "npm:^7.5.5" + "@metamask/accounts-controller": "npm:^39.0.6" + "@metamask/assets-controllers": "npm:^110.0.3" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/client-controller": "npm:^1.0.1" + "@metamask/config-registry-controller": "npm:^2.0.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/core-backend": "npm:^8.1.0" + "@metamask/keyring-api": "npm:^23.7.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-api": "npm:^11.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.1" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/network-enablement-controller": "npm:^6.0.2" + "@metamask/permission-controller": "npm:^13.1.1" + "@metamask/phishing-controller": "npm:^17.3.0" + "@metamask/polling-controller": "npm:^16.0.9" + "@metamask/preferences-controller": "npm:^23.1.0" + "@metamask/remote-feature-flag-controller": "npm:^5.0.0" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-utils": "npm:^12.1.2" + "@metamask/transaction-controller": "npm:^69.4.0" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + bignumber.js: "npm:^9.1.2" + lodash: "npm:^4.17.21" + p-limit: "npm:^3.1.0" + checksum: 10/8e0071a355a95f36ccf88ed76d8552a7c16f5b95ab82a6e3fa36dc71b97fea1f8b156071ade3f4a605d3f6d1cae5c43829366045303fb5d76c4c5d25b238f236 languageName: node linkType: hard -"@metamask/base-controller@npm:^9.0.0": - version: 9.0.0 - resolution: "@metamask/base-controller@npm:9.0.0" +"@metamask/assets-controller@npm:^13.0.0": + version: 13.1.0 + resolution: "@metamask/assets-controller@npm:13.1.0" dependencies: - "@metamask/messenger": "npm:^0.3.0" - "@metamask/utils": "npm:^11.8.1" - immer: "npm:^9.0.6" - checksum: 10/27554d34ec85c4b585b87850c90dfeaaf9c7e6430f2ab2fa80a1ec06ccc17641e118afab7ad765a0b7255ffef37bc9f6ca5065d459228a2dc660bc463293310d + "@ethereumjs/util": "npm:^9.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/account-tree-controller": "npm:^7.5.5" + "@metamask/accounts-controller": "npm:^39.0.6" + "@metamask/assets-controllers": "npm:^110.1.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/client-controller": "npm:^1.0.1" + "@metamask/config-registry-controller": "npm:^2.0.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/core-backend": "npm:^8.1.0" + "@metamask/keyring-api": "npm:^23.7.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-api": "npm:^11.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.1" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/network-enablement-controller": "npm:^6.0.2" + "@metamask/permission-controller": "npm:^13.1.1" + "@metamask/phishing-controller": "npm:^17.3.0" + "@metamask/polling-controller": "npm:^16.0.9" + "@metamask/preferences-controller": "npm:^23.1.0" + "@metamask/remote-feature-flag-controller": "npm:^5.0.0" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-utils": "npm:^12.1.2" + "@metamask/transaction-controller": "npm:^69.4.0" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + bignumber.js: "npm:^9.1.2" + lodash: "npm:^4.17.21" + p-limit: "npm:^3.1.0" + checksum: 10/b363552da406e01869eb9ed602222af07da68acb392f88f11e8d6d9a20a1ddd5ce4a969411d9f6a3c112c4a50a6e3100f64056d5282ea0d5bb7bac1032b26d1c languageName: node linkType: hard -"@metamask/base-controller@npm:^9.1.0": - version: 9.1.0 - resolution: "@metamask/base-controller@npm:9.1.0" +"@metamask/assets-controllers@npm:^110.0.3, @metamask/assets-controllers@npm:^110.1.0": + version: 110.1.0 + resolution: "@metamask/assets-controllers@npm:110.1.0" dependencies: - "@metamask/messenger": "npm:^1.1.1" - "@metamask/utils": "npm:^11.9.0" - immer: "npm:^9.0.6" - checksum: 10/752b70b35026fdf31ea8feef638f06286dbbde8d17e1ba804085fdbedbb076d900d2d7b6db3d2af284b3aa8fa84c95a0ceea6a3a46ee7b7e29433f037e9afc69 - languageName: node + "@ethereumjs/util": "npm:^9.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/abi-utils": "npm:^2.0.3" + "@metamask/account-tree-controller": "npm:^7.5.5" + "@metamask/accounts-controller": "npm:^39.0.6" + "@metamask/approval-controller": "npm:^9.0.2" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/contract-metadata": "npm:^2.4.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/core-backend": "npm:^8.1.0" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/keyring-api": "npm:^23.7.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/multichain-account-service": "npm:^13.0.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/network-enablement-controller": "npm:^6.0.2" + "@metamask/permission-controller": "npm:^13.1.1" + "@metamask/phishing-controller": "npm:^17.3.0" + "@metamask/polling-controller": "npm:^16.0.9" + "@metamask/preferences-controller": "npm:^23.1.0" + "@metamask/profile-sync-controller": "npm:^28.3.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/snaps-utils": "npm:^12.1.2" + "@metamask/storage-service": "npm:^1.0.2" + "@metamask/transaction-controller": "npm:^69.4.0" + "@metamask/utils": "npm:^11.11.0" + "@tanstack/query-core": "npm:^5.62.16" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + async-mutex: "npm:^0.5.0" + bitcoin-address-validation: "npm:^2.2.3" + bn.js: "npm:^5.2.1" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + multiformats: "npm:^9.9.0" + reselect: "npm:^5.1.1" + single-call-balance-checker-abi: "npm:^1.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/providers": ^22.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/0b62ee921d60003288504e8aa7079feb59c948c69784821c42498800ace057071e75a3472f23aca03a123d26792190b0efb025baf3d5056d7841ca0e57e19cb9 + languageName: node + linkType: hard + +"@metamask/auto-changelog@npm:4.0.0": + version: 4.0.0 + resolution: "@metamask/auto-changelog@npm:4.0.0" + dependencies: + diff: "npm:^5.0.0" + execa: "npm:^5.1.1" + semver: "npm:^7.3.5" + yargs: "npm:^17.0.1" + peerDependencies: + prettier: ">=3.0.0" + bin: + auto-changelog: dist/cli.js + checksum: 10/4968d316411e5f4820092391f79078839f49e0e26ecd0c48266b5a1e0e32614b75dad5f36b76e632a7a22a2f2eeeb762a693ae390b0373bff29081ff2a7eefda + languageName: node + linkType: hard + +"@metamask/base-controller@npm:^9.0.0": + version: 9.0.0 + resolution: "@metamask/base-controller@npm:9.0.0" + dependencies: + "@metamask/messenger": "npm:^0.3.0" + "@metamask/utils": "npm:^11.8.1" + immer: "npm:^9.0.6" + checksum: 10/27554d34ec85c4b585b87850c90dfeaaf9c7e6430f2ab2fa80a1ec06ccc17641e118afab7ad765a0b7255ffef37bc9f6ca5065d459228a2dc660bc463293310d + languageName: node + linkType: hard + +"@metamask/base-controller@npm:^9.0.1, @metamask/base-controller@npm:^9.1.0": + version: 9.1.0 + resolution: "@metamask/base-controller@npm:9.1.0" + dependencies: + "@metamask/messenger": "npm:^1.1.1" + "@metamask/utils": "npm:^11.9.0" + immer: "npm:^9.0.6" + checksum: 10/752b70b35026fdf31ea8feef638f06286dbbde8d17e1ba804085fdbedbb076d900d2d7b6db3d2af284b3aa8fa84c95a0ceea6a3a46ee7b7e29433f037e9afc69 + languageName: node + linkType: hard + +"@metamask/browser-passworder@npm:^6.0.0": + version: 6.0.0 + resolution: "@metamask/browser-passworder@npm:6.0.0" + dependencies: + "@metamask/utils": "npm:^11.0.1" + checksum: 10/44b31729ccd52e62df27a948fbea2320a11861d6465128b1626a723fc47e25f9e3a8c3bdc09329f3f270b5d9e023107cb439cfb6961a3614c6daa58ea4524f00 + languageName: node linkType: hard "@metamask/chain-agnostic-permission@npm:^1.6.2": @@ -4053,6 +4511,62 @@ __metadata: languageName: node linkType: hard +"@metamask/client-controller@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/client-controller@npm:1.0.1" + dependencies: + "@metamask/base-controller": "npm:^9.0.1" + "@metamask/messenger": "npm:^1.0.0" + checksum: 10/7a3db2c30c6217a018a7cc36cb40b0be21ec66d1816327619ca91e843657ea684a2194fd27a6d58cb2f9e6f5736e44277c9eb026e9606dd2ae522be68affc912 + languageName: node + linkType: hard + +"@metamask/config-registry-controller@npm:^2.0.0": + version: 2.0.0 + resolution: "@metamask/config-registry-controller@npm:2.0.0" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/polling-controller": "npm:^16.0.9" + "@metamask/profile-sync-controller": "npm:^28.3.0" + "@metamask/remote-feature-flag-controller": "npm:^5.0.0" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.11.0" + reselect: "npm:^5.1.1" + checksum: 10/ad2c4759ac143655b025a0b7c448992873bed05242b01c88fec5c00b3d8e5161cff21474021600ebca36e5ae221691d39d5c3a1e77619a72042bb49967a8855d + languageName: node + linkType: hard + +"@metamask/connectivity-controller@npm:^0.2.0": + version: 0.2.0 + resolution: "@metamask/connectivity-controller@npm:0.2.0" + dependencies: + "@metamask/base-controller": "npm:^9.0.1" + "@metamask/messenger": "npm:^1.0.0" + checksum: 10/586c80931341375c713aa5a474725ec88174e6e885d5f75d23b6458b734ec0e912a8c47996bea94a82f9549c9598ba982ac99aaeccac1fd92002c35a38f5397f + languageName: node + linkType: hard + +"@metamask/connectivity-controller@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/connectivity-controller@npm:0.3.0" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/messenger": "npm:^2.0.0" + reselect: "npm:^5.1.1" + checksum: 10/4751d3e4725f6d27d0e91e9213edc5855ab10ddd794200af6095a6c4b9db1c2d5b5ed8c4ec812002a29e0e141fa809e042c8002039f02a64c4dc33b53c2c9945 + languageName: node + linkType: hard + +"@metamask/contract-metadata@npm:^2.4.0": + version: 2.5.0 + resolution: "@metamask/contract-metadata@npm:2.5.0" + checksum: 10/462b3ec7b32311ff5e6eec001abf25143e67ee667c70e0b9a3dee7545e7fc245b8098856ae79fef9758ef1db4930fa1d1d7681410995520935f2885bdb9641d2 + languageName: node + linkType: hard + "@metamask/controller-utils@npm:^11.17.0": version: 11.18.0 resolution: "@metamask/controller-utils@npm:11.18.0" @@ -4074,6 +4588,27 @@ __metadata: languageName: node linkType: hard +"@metamask/controller-utils@npm:^11.19.0": + version: 11.20.0 + resolution: "@metamask/controller-utils@npm:11.20.0" + dependencies: + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/ethjs-unit": "npm:^0.3.0" + "@metamask/utils": "npm:^11.9.0" + "@spruceid/siwe-parser": "npm:2.1.0" + "@types/bn.js": "npm:^5.1.5" + bignumber.js: "npm:^9.1.2" + bn.js: "npm:^5.2.1" + cockatiel: "npm:^3.1.2" + eth-ens-namehash: "npm:^2.0.8" + fast-deep-equal: "npm:^3.1.3" + lodash: "npm:^4.17.21" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/a2ba778d00508a606ef4657cd6591a89f8f54136b3dd41f4f96f8effa2e9ad20de347e3c554f643cb248a002a17b478f95eafd4e3b4c3eca4bb40ae2e6bf7fdc + languageName: node + linkType: hard + "@metamask/controller-utils@npm:^12.0.0": version: 12.2.0 resolution: "@metamask/controller-utils@npm:12.2.0" @@ -4096,7 +4631,7 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^12.3.0": +"@metamask/controller-utils@npm:^12.1.0, @metamask/controller-utils@npm:^12.3.0": version: 12.3.0 resolution: "@metamask/controller-utils@npm:12.3.0" dependencies: @@ -4118,6 +4653,42 @@ __metadata: languageName: node linkType: hard +"@metamask/core-backend@npm:^7.0.0": + version: 7.0.0 + resolution: "@metamask/core-backend@npm:7.0.0" + dependencies: + "@metamask/account-tree-controller": "npm:^7.5.5" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/profile-sync-controller": "npm:^28.3.0" + "@metamask/remote-feature-flag-controller": "npm:^4.2.2" + "@metamask/utils": "npm:^11.11.0" + "@tanstack/query-core": "npm:^5.62.16" + async-mutex: "npm:^0.5.0" + uuid: "npm:^8.3.2" + checksum: 10/306ccde8856832b236984dcd6ae7044b8664c6319a80f199b23a2cd91883188d921968387bb515defe8d36cd3d06d3f9db0144e821248a844ad1907ca9ec902b + languageName: node + linkType: hard + +"@metamask/core-backend@npm:^8.1.0": + version: 8.1.0 + resolution: "@metamask/core-backend@npm:8.1.0" + dependencies: + "@metamask/account-tree-controller": "npm:^7.5.5" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/profile-sync-controller": "npm:^28.3.0" + "@metamask/remote-feature-flag-controller": "npm:^5.0.0" + "@metamask/utils": "npm:^11.11.0" + "@tanstack/query-core": "npm:^5.62.16" + async-mutex: "npm:^0.5.0" + uuid: "npm:^8.3.2" + checksum: 10/788ca5c8ea6e1208e51b27913d6554188d4c4a871e73f7a0b30062b8b64e1177d8b5ac4cc5c291f105c9fc8226cd15adb230e0fff23e7cc71c5251018705872c + languageName: node + linkType: hard + "@metamask/eslint-config-browser@npm:^15.0.0": version: 15.0.0 resolution: "@metamask/eslint-config-browser@npm:15.0.0" @@ -4194,25 +4765,299 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-query@npm:^4.0.0": - version: 4.0.0 - resolution: "@metamask/eth-query@npm:4.0.0" +"@metamask/eth-block-tracker@npm:^15.0.1": + version: 15.0.1 + resolution: "@metamask/eth-block-tracker@npm:15.0.1" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^6.0.0" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^11.9.0" + json-rpc-random-id: "npm:^1.0.1" + checksum: 10/8c0e0f5bde00fb40aa240fc562c29d0ce84156402132705d387c95453c6f265b3b738c9b649510771965d8b5b8036c2bdc6406142ff634383bf60df3741bdbfe + languageName: node + linkType: hard + +"@metamask/eth-hd-keyring@npm:^14.1.1": + version: 14.1.2 + resolution: "@metamask/eth-hd-keyring@npm:14.1.2" + dependencies: + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/key-tree": "npm:^10.0.2" + "@metamask/keyring-api": "npm:^23.6.0" + "@metamask/keyring-sdk": "npm:^2.3.0" + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/scure-bip39": "npm:^2.1.1" + "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.11.0" + ethereum-cryptography: "npm:^2.2.1" + checksum: 10/077d6aacc674903d737948dfb5a3215e912e73afabad14429614ca643269150604b8ded8cbd48601f99b532c07e51c8d6b1036bad304a21a5b2ded3ea7d06c02 + languageName: node + linkType: hard + +"@metamask/eth-json-rpc-infura@npm:^10.3.0": + version: 10.3.0 + resolution: "@metamask/eth-json-rpc-infura@npm:10.3.0" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^5.0.0" + "@metamask/json-rpc-engine": "npm:^10.1.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.0.1" + checksum: 10/164c9ef1285a0895db668f3949d9258df721d348c857c4337b964f147000221d69fd103851c6483d8dd6bfe851c76cc5299155a8818eac0b92334469c40ccaef + languageName: node + linkType: hard + +"@metamask/eth-json-rpc-middleware@npm:^23.1.3": + version: 23.1.3 + resolution: "@metamask/eth-json-rpc-middleware@npm:23.1.3" + dependencies: + "@metamask/eth-block-tracker": "npm:^15.0.1" + "@metamask/eth-json-rpc-provider": "npm:^6.0.1" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/json-rpc-engine": "npm:^10.2.4" + "@metamask/message-manager": "npm:^14.1.1" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.9.0" + klona: "npm:^2.0.6" + pify: "npm:^5.0.0" + safe-stable-stringify: "npm:^2.4.3" + checksum: 10/aee4866afa78cb21aed9daa1211ee9a3515cb9549d9b325d5904b8769fb54790296224b58ec3555eb34a7125d2fd5180d6cba915bb1ca8dc3f04bf75e502c6b0 + languageName: node + linkType: hard + +"@metamask/eth-json-rpc-provider@npm:^5.0.0": + version: 5.0.1 + resolution: "@metamask/eth-json-rpc-provider@npm:5.0.1" + dependencies: + "@metamask/json-rpc-engine": "npm:^10.1.1" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^11.8.1" + uuid: "npm:^8.3.2" + checksum: 10/1b874b04a0d8c53edf7036c8dbf4403101afba548cfd3bfc1fa969b893905a72e44c36a17b60ccb202f14a8e012b08031b4649c2ee77d604dc84dfbc6c88b812 + languageName: node + linkType: hard + +"@metamask/eth-json-rpc-provider@npm:^6.0.0, @metamask/eth-json-rpc-provider@npm:^6.0.1": + version: 6.0.1 + resolution: "@metamask/eth-json-rpc-provider@npm:6.0.1" + dependencies: + "@metamask/json-rpc-engine": "npm:^10.2.4" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.9.0" + nanoid: "npm:^3.3.8" + checksum: 10/06078a9e43b02f35387a3ccfe09733c7eeac2a732dee1f1be53254fc05719e230776b8512b13702a178fb692088fc7da46f727c5064550d65f51ac59d44f9d83 + languageName: node + linkType: hard + +"@metamask/eth-query@npm:^4.0.0": + version: 4.0.0 + resolution: "@metamask/eth-query@npm:4.0.0" + dependencies: + json-rpc-random-id: "npm:^1.0.0" + xtend: "npm:^4.0.1" + checksum: 10/c7a2ff14c4fb7be5de0ffc914068ef69a4af3d8840b51595219c673e6c7001305f742f18b393c9812be1b80866f4105af3ae41e39232eaba7f44e64b386d6651 + languageName: node + linkType: hard + +"@metamask/eth-sig-util@npm:^8.2.0": + version: 8.2.0 + resolution: "@metamask/eth-sig-util@npm:8.2.0" + dependencies: + "@ethereumjs/rlp": "npm:^4.0.1" + "@ethereumjs/util": "npm:^8.1.0" + "@metamask/abi-utils": "npm:^3.0.0" + "@metamask/utils": "npm:^11.0.1" + "@scure/base": "npm:~1.1.3" + ethereum-cryptography: "npm:^2.1.2" + tweetnacl: "npm:^1.0.3" + checksum: 10/385df1ec541116e1bd725a1df1a519996bad167f99d1b2677126e398cdfda6fc3f03d2ff8f1ca523966bc0aae3ea92a9050953a45d5a7711f4128aacf9242bfc + languageName: node + linkType: hard + +"@metamask/eth-simple-keyring@npm:^12.0.2": + version: 12.0.2 + resolution: "@metamask/eth-simple-keyring@npm:12.0.2" + dependencies: + "@ethereumjs/util": "npm:^9.1.0" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/keyring-api": "npm:^23.1.0" + "@metamask/keyring-sdk": "npm:^2.0.2" + "@metamask/utils": "npm:^11.11.0" + ethereum-cryptography: "npm:^2.2.1" + randombytes: "npm:^2.1.0" + checksum: 10/ac8a3a5871fb1b7503ae8714beaad07102ad473522f1c65cf09786a3f3498564763d96a51569ed712702f3a62dfaada4c9342def4d48e2c5a1f0985b5cd49725 + languageName: node + linkType: hard + +"@metamask/eth-snap-keyring@npm:^23.0.0": + version: 23.0.2 + resolution: "@metamask/eth-snap-keyring@npm:23.0.2" + dependencies: + "@ethereumjs/tx": "npm:^5.4.0" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/keyring-internal-api": "npm:^11.0.2" + "@metamask/keyring-internal-snap-client": "npm:^10.0.6" + "@metamask/keyring-sdk": "npm:^2.3.0" + "@metamask/keyring-snap-sdk": "npm:^9.2.1" + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/messenger": "npm:^1.1.1" + "@metamask/snaps-controllers": "npm:^19.0.1" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/snaps-utils": "npm:^12.2.1" + "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.11.0" + "@types/uuid": "npm:^9.0.8" + async-mutex: "npm:^0.5.0" + uuid: "npm:^9.0.1" + peerDependencies: + "@metamask/keyring-api": ^23.0.0 + checksum: 10/a8763d5ec3c8863a6a623bd58b3328b06adba6435ea73b5f0b987f6ae2b8fa7a95a9464cc9d9a9ffec11d2183d36b1947251761328d15fb10038f368f6f7ffc5 + languageName: node + linkType: hard + +"@metamask/ethjs-contract@npm:^0.4.1": + version: 0.4.1 + resolution: "@metamask/ethjs-contract@npm:0.4.1" + dependencies: + "@metamask/ethjs-filter": "npm:^0.3.0" + "@metamask/ethjs-util": "npm:^0.3.0" + ethjs-abi: "npm:^0.2.0" + js-sha3: "npm:^0.9.2" + promise-to-callback: "npm:^1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/2187f62336c2afd46b51bda87b96e1537dd7dfe0f621defc3cb5ccb68add482004674f3c11ced95c65b7ecbe845592541f1b268c97b0b42c939d8d5677523097 + languageName: node + linkType: hard + +"@metamask/ethjs-filter@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-filter@npm:0.3.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/1b7565045e638f60d39ae8d8ca020b6e3ac2e4e91f220f343e499b09a7adeb9e4273249e3c3f7366acc2e44bc0b7fe3dd3ac84c66e9a0853352bfb0e116c1d87 + languageName: node + linkType: hard + +"@metamask/ethjs-format@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-format@npm:0.3.0" + dependencies: + "@metamask/ethjs-util": "npm:^0.3.0" + "@metamask/number-to-bn": "npm:^1.7.1" + bn.js: "npm:^5.2.1" + ethjs-schema: "npm:0.2.1" + is-hex-prefixed: "npm:1.0.0" + strip-hex-prefix: "npm:1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/4ae73716e0dd76f7b780ede79b3989d241ee8654b7aa9ec23678529a1916f77ab101552f7aabe56c8e26506a33f34181f40624a68a317e7cd3ae45fb0da2fa73 + languageName: node + linkType: hard + +"@metamask/ethjs-query@npm:^0.7.1": + version: 0.7.1 + resolution: "@metamask/ethjs-query@npm:0.7.1" + dependencies: + "@metamask/ethjs-format": "npm:^0.3.0" + "@metamask/ethjs-rpc": "npm:^0.4.0" + promise-to-callback: "npm:^1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/a9d54677cf062267f7e781996edfadfc2a5a2f3589266b7f20386e8109b7cecdfdc47c6aba4bb68d0c4cb387cfdb463d6316d506c06138531ca1e81ab201e5dc + languageName: node + linkType: hard + +"@metamask/ethjs-rpc@npm:^0.4.0": + version: 0.4.0 + resolution: "@metamask/ethjs-rpc@npm:0.4.0" + dependencies: + promise-to-callback: "npm:^1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/e126535ff7599ef584b0084dae846294d505b69141a143543d7580cdce4b297646e796fbd0cecc5a824dce8b5e3b179bb177ceff06fd50ad584e7fce00ab081f + languageName: node + linkType: hard + +"@metamask/ethjs-unit@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-unit@npm:0.3.0" + dependencies: + "@metamask/number-to-bn": "npm:^1.7.1" + bn.js: "npm:^5.2.1" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/a027d69d66b7eb45666c3bffbedd76dc25dbdd11b177a6b0a7c6735a06d1f123cd4c4c2e301149f6068450a49d41c24c6f93d665552757408fafefbd8720346e + languageName: node + linkType: hard + +"@metamask/ethjs-util@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-util@npm:0.3.0" + dependencies: + is-hex-prefixed: "npm:1.0.0" + strip-hex-prefix: "npm:1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/aaecae091dd16362f048cbba27629d67d10530c704facf80f430db546c8ea32fa627cd9560bd3eca1e6ce651d2b7a7df9fdc3677e0ae2814954e3c950cee70da + languageName: node + linkType: hard + +"@metamask/gas-fee-controller@npm:^26.2.4": + version: 26.3.0 + resolution: "@metamask/gas-fee-controller@npm:26.3.0" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/ethjs-unit": "npm:^0.3.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^34.0.0" + "@metamask/polling-controller": "npm:^16.0.8" + "@metamask/utils": "npm:^11.11.0" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + bn.js: "npm:^5.2.1" + uuid: "npm:^8.3.2" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/a09363e61f84287596eb31af6bc9bc77361a4f3aab4f7751aa85e7c94996bdfb088ecb2620404f1bd405129077043c9c2efd9d27f5a79a5dd5f7a7435fb02b82 + languageName: node + linkType: hard + +"@metamask/gas-fee-controller@npm:^26.3.1": + version: 26.3.1 + resolution: "@metamask/gas-fee-controller@npm:26.3.1" dependencies: - json-rpc-random-id: "npm:^1.0.0" - xtend: "npm:^4.0.1" - checksum: 10/c7a2ff14c4fb7be5de0ffc914068ef69a4af3d8840b51595219c673e6c7001305f742f18b393c9812be1b80866f4105af3ae41e39232eaba7f44e64b386d6651 + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/ethjs-unit": "npm:^0.3.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/polling-controller": "npm:^16.0.9" + "@metamask/utils": "npm:^11.11.0" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + bn.js: "npm:^5.2.1" + uuid: "npm:^8.3.2" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/62ed3e4b90fe22237886b57f7d8e8cc2847d60e225e2b6d47dd3be0ca15d9099ae36b8edb49b8a1a3307be8da2a705f288c0cdabba4e1d02dda6db45de9d0e69 languageName: node linkType: hard -"@metamask/ethjs-unit@npm:^0.3.0": - version: 0.3.0 - resolution: "@metamask/ethjs-unit@npm:0.3.0" +"@metamask/geolocation-controller@npm:^1.0.0": + version: 1.0.0 + resolution: "@metamask/geolocation-controller@npm:1.0.0" dependencies: - "@metamask/number-to-bn": "npm:^1.7.1" - bn.js: "npm:^5.2.1" - peerDependencies: - "@babel/runtime": ^7.0.0 - checksum: 10/a027d69d66b7eb45666c3bffbedd76dc25dbdd11b177a6b0a7c6735a06d1f123cd4c4c2e301149f6068450a49d41c24c6f93d665552757408fafefbd8720346e + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/messenger": "npm:^2.0.0" + checksum: 10/84dab8e1ffa732fdaac75bfc9288c3ca4f1e340125450c444d9362710f6c9d9cf299926aa54b3eaa9fc8d01e7ee15261472b4a43e3726b1d1c1b161d503df804 languageName: node linkType: hard @@ -4230,7 +5075,7 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^10.5.0": +"@metamask/json-rpc-engine@npm:^10.1.0, @metamask/json-rpc-engine@npm:^10.2.4, @metamask/json-rpc-engine@npm:^10.5.0": version: 10.5.0 resolution: "@metamask/json-rpc-engine@npm:10.5.0" dependencies: @@ -4270,7 +5115,7 @@ __metadata: languageName: node linkType: hard -"@metamask/key-tree@npm:^10.1.1": +"@metamask/key-tree@npm:^10.0.2, @metamask/key-tree@npm:^10.1.1": version: 10.1.1 resolution: "@metamask/key-tree@npm:10.1.1" dependencies: @@ -4295,6 +5140,18 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.6.0, @metamask/keyring-api@npm:^23.7.0": + version: 23.7.0 + resolution: "@metamask/keyring-api@npm:23.7.0" + dependencies: + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.11.0" + bitcoin-address-validation: "npm:^2.2.3" + checksum: 10/3c1aea064e017be0b99202a4e59b7ed9e1965aeb732b04ad5cb252dfa6b443487284f8b4253986ca325d8b45fd7977cef93f470f95f0342d377551d4b93738c3 + languageName: node + linkType: hard + "@metamask/keyring-api@npm:^23.5.0": version: 23.5.0 resolution: "@metamask/keyring-api@npm:23.5.0" @@ -4307,6 +5164,87 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-controller@npm:^27.1.0": + version: 27.1.0 + resolution: "@metamask/keyring-controller@npm:27.1.0" + dependencies: + "@ethereumjs/util": "npm:^9.1.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/browser-passworder": "npm:^6.0.0" + "@metamask/eth-hd-keyring": "npm:^14.1.1" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/eth-simple-keyring": "npm:^12.0.2" + "@metamask/keyring-api": "npm:^23.1.0" + "@metamask/keyring-internal-api": "npm:^11.0.1" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + ethereumjs-wallet: "npm:^1.0.1" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + ulid: "npm:^2.3.0" + checksum: 10/72c3cd205c88ac0becff32f1d22dae841d13a6847fc165a16782d1cd8daafb434b472ef867b94833d2f8bcb5f98a9663b630c8bed231332305ba1a395385c241 + languageName: node + linkType: hard + +"@metamask/keyring-internal-api@npm:^11.0.1, @metamask/keyring-internal-api@npm:^11.0.2": + version: 11.0.2 + resolution: "@metamask/keyring-internal-api@npm:11.0.2" + dependencies: + "@metamask/keyring-api": "npm:^23.6.0" + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/superstruct": "npm:^3.4.1" + checksum: 10/7a5d817b93d3f0bd54860d7747833ace48e46d079e700d98f87c76a79b4da25acc1ba0aec3fdc4ff49c36fb3dc0a3378d27c7b970cdb31c88f8caa13c8199086 + languageName: node + linkType: hard + +"@metamask/keyring-internal-snap-client@npm:^10.0.5, @metamask/keyring-internal-snap-client@npm:^10.0.6": + version: 10.0.6 + resolution: "@metamask/keyring-internal-snap-client@npm:10.0.6" + dependencies: + "@metamask/keyring-api": "npm:^23.6.0" + "@metamask/keyring-internal-api": "npm:^11.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.1" + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/messenger": "npm:^1.1.1" + checksum: 10/b2325f9e9dfae8289574799b6b3cf4aa0a6c4d83a94baae48e1b47948977ad1444adba848ef06e7eec36e966b479485be82c6f9210b3dfbb3c2cdfc2a1e81ef4 + languageName: node + linkType: hard + +"@metamask/keyring-sdk@npm:^2.0.2, @metamask/keyring-sdk@npm:^2.2.0, @metamask/keyring-sdk@npm:^2.3.0": + version: 2.3.0 + resolution: "@metamask/keyring-sdk@npm:2.3.0" + dependencies: + "@ethereumjs/tx": "npm:^5.4.0" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/keyring-api": "npm:^23.6.0" + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/scure-bip39": "npm:^2.1.1" + "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + ethereum-cryptography: "npm:^2.2.1" + uuid: "npm:^9.0.1" + checksum: 10/38402f3834e0e364e1b17fe30ff64c1a050549239e204260081ddd0fc9228e9632d586819c551f91793d78207fb33f81e06ec3ac24537d511e8b5f1fabe57d21 + languageName: node + linkType: hard + +"@metamask/keyring-snap-client@npm:^9.2.0, @metamask/keyring-snap-client@npm:^9.2.1": + version: 9.2.1 + resolution: "@metamask/keyring-snap-client@npm:9.2.1" + dependencies: + "@metamask/keyring-api": "npm:^23.6.0" + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/superstruct": "npm:^3.4.1" + "@types/uuid": "npm:^9.0.8" + uuid: "npm:^9.0.1" + webextension-polyfill: "npm:^0.12.0" + peerDependencies: + "@metamask/providers": ^19.0.0 + checksum: 10/bd4f006737598d44992e2a6283448a9cb4321fd0b5bbe59d37d8e28b1e31ad28cce997100e5af5a04c661faba1ee9f320df59ce86ef4c2a0fcbc653091b52af2 + languageName: node + linkType: hard + "@metamask/keyring-snap-sdk@npm:^9.2.0": version: 9.2.0 resolution: "@metamask/keyring-snap-sdk@npm:9.2.0" @@ -4323,6 +5261,22 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-snap-sdk@npm:^9.2.1": + version: 9.2.1 + resolution: "@metamask/keyring-snap-sdk@npm:9.2.1" + dependencies: + "@metamask/keyring-utils": "npm:^4.0.0" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.11.0" + webextension-polyfill: "npm:^0.12.0" + peerDependencies: + "@metamask/keyring-api": ^23.0.0 + "@metamask/providers": ^19.0.0 + checksum: 10/90b01fca9cc1d055b3830e8a7d2503b40421c9ddc4e20b0db25fade73c430ac56bb482f340cd82a08a5013d76e0b8d8265c953b71f5e29be2c5e538d5cedd9e4 + languageName: node + linkType: hard + "@metamask/keyring-utils@npm:^3.2.0": version: 3.2.0 resolution: "@metamask/keyring-utils@npm:3.2.0" @@ -4347,6 +5301,34 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-utils@npm:^4.0.0": + version: 4.0.0 + resolution: "@metamask/keyring-utils@npm:4.0.0" + dependencies: + "@ethereumjs/tx": "npm:^5.4.0" + "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.11.0" + bitcoin-address-validation: "npm:^2.2.3" + checksum: 10/a4299fafadd4a4f2f1a4475a7f4e6c4d268ccfa82e505cdd4cf4a1ff5cc6ca485edd7422650c498409db93d4fe32429db7bd02276ce6fee80aac5f6a64439578 + languageName: node + linkType: hard + +"@metamask/message-manager@npm:^14.1.1": + version: 14.1.2 + resolution: "@metamask/message-manager@npm:14.1.2" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.0.0" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/utils": "npm:^11.9.0" + "@types/uuid": "npm:^8.3.0" + jsonschema: "npm:^1.4.1" + uuid: "npm:^8.3.2" + checksum: 10/82e3965069f0eb34e2141ca4dff452c118f54502d0b5d6b832af1906c6413ba1d923b13f1ed6af7f8a7b3087ef7f3b1a700bcffa56b10b06df53972ff2cf9a4f + languageName: node + linkType: hard + "@metamask/messenger@npm:^0.3.0": version: 0.3.0 resolution: "@metamask/messenger@npm:0.3.0" @@ -4354,7 +5336,7 @@ __metadata: languageName: node linkType: hard -"@metamask/messenger@npm:^1.1.1, @metamask/messenger@npm:^1.2.0": +"@metamask/messenger@npm:^1.0.0, @metamask/messenger@npm:^1.1.0, @metamask/messenger@npm:^1.1.1, @metamask/messenger@npm:^1.2.0": version: 1.2.0 resolution: "@metamask/messenger@npm:1.2.0" dependencies: @@ -4379,6 +5361,149 @@ __metadata: languageName: node linkType: hard +"@metamask/metamask-eth-abis@npm:^3.1.1": + version: 3.1.1 + resolution: "@metamask/metamask-eth-abis@npm:3.1.1" + checksum: 10/2d456882c5180eaf02d07f578ee8b8806d18639349d086a6b3b68c87e7ea294749efc332bfe7c1d5fb700933e71f31a46d3cd07b94c4e91c80faea5f9729bf0d + languageName: node + linkType: hard + +"@metamask/multichain-account-service@npm:^13.0.0": + version: 13.0.0 + resolution: "@metamask/multichain-account-service@npm:13.0.0" + dependencies: + "@ethereumjs/util": "npm:^9.1.0" + "@metamask/accounts-controller": "npm:^39.0.5" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/eth-snap-keyring": "npm:^23.0.0" + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/keyring-api": "npm:^23.5.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-api": "npm:^11.0.1" + "@metamask/keyring-snap-client": "npm:^9.2.0" + "@metamask/keyring-utils": "npm:^3.3.1" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/snap-account-service": "npm:^2.0.0" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/snaps-utils": "npm:^12.1.2" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + lodash: "npm:^4.17.21" + peerDependencies: + "@metamask/account-api": ^1.0.4 + "@metamask/providers": ^22.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/2ecb49a620420804ff74f1e6ce9a75303f2e41a5182b8570b6bef324c63e5be149ea1870815dbe8403a87c8fe17529322896fd9015e10766095a0d74c2aebafd + languageName: node + linkType: hard + +"@metamask/multichain-network-controller@npm:^3.2.2": + version: 3.2.2 + resolution: "@metamask/multichain-network-controller@npm:3.2.2" + dependencies: + "@metamask/accounts-controller": "npm:^39.0.6" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/keyring-api": "npm:^23.7.0" + "@metamask/keyring-internal-api": "npm:^11.0.2" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.11.0" + "@solana/addresses": "npm:^2.0.0" + lodash: "npm:^4.17.21" + checksum: 10/5e191f49a5f517d9e96f72be4f18f3806d8315f7e40f4a23af25832d4c42a8416e7d1dfc90e611a4fbada34596237078ebcc53ba3da5b9ece363cb5c050c45ec + languageName: node + linkType: hard + +"@metamask/network-controller@npm:^34.0.0": + version: 34.0.0 + resolution: "@metamask/network-controller@npm:34.0.0" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/connectivity-controller": "npm:^0.2.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/eth-block-tracker": "npm:^15.0.1" + "@metamask/eth-json-rpc-infura": "npm:^10.3.0" + "@metamask/eth-json-rpc-middleware": "npm:^23.1.3" + "@metamask/eth-json-rpc-provider": "npm:^6.0.1" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/json-rpc-engine": "npm:^10.5.0" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/remote-feature-flag-controller": "npm:^4.2.2" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/swappable-obj-proxy": "npm:^2.3.0" + "@metamask/utils": "npm:^11.11.0" + fast-deep-equal: "npm:^3.1.3" + immer: "npm:^9.0.6" + loglevel: "npm:^1.8.1" + reselect: "npm:^5.1.1" + uri-js: "npm:^4.4.1" + uuid: "npm:^8.3.2" + checksum: 10/6afb874ca5702e1be616bf6ea92b35332d1969878794ce118138ca8f3733a1521a266d00bb658cf7a37e126545ac942ef5bc09bbd64159612b5424d848dbdc67 + languageName: node + linkType: hard + +"@metamask/network-controller@npm:^35.0.0": + version: 35.0.0 + resolution: "@metamask/network-controller@npm:35.0.0" + dependencies: + "@metamask/analytics-controller": "npm:^2.0.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/connectivity-controller": "npm:^0.3.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/eth-block-tracker": "npm:^15.0.1" + "@metamask/eth-json-rpc-infura": "npm:^10.3.0" + "@metamask/eth-json-rpc-middleware": "npm:^23.1.3" + "@metamask/eth-json-rpc-provider": "npm:^6.0.1" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/json-rpc-engine": "npm:^10.5.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/remote-feature-flag-controller": "npm:^5.0.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/swappable-obj-proxy": "npm:^2.3.0" + "@metamask/utils": "npm:^11.11.0" + fast-deep-equal: "npm:^3.1.3" + immer: "npm:^9.0.6" + loglevel: "npm:^1.8.1" + reselect: "npm:^5.1.1" + uri-js: "npm:^4.4.1" + uuid: "npm:^8.3.2" + checksum: 10/6f5d247052844505b7877439d52434c25bc1fc0a6130cdae87ce34705953f7cdac6813adf275f40af7f630ce570b86788ef84a7c43a42c7fe68f093304f69ad6 + languageName: node + linkType: hard + +"@metamask/network-enablement-controller@npm:^6.0.2": + version: 6.0.2 + resolution: "@metamask/network-enablement-controller@npm:6.0.2" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/config-registry-controller": "npm:^2.0.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/keyring-api": "npm:^23.7.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/multichain-network-controller": "npm:^3.2.2" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/slip44": "npm:^4.3.0" + "@metamask/transaction-controller": "npm:^69.4.0" + "@metamask/utils": "npm:^11.11.0" + reselect: "npm:^5.1.1" + checksum: 10/a1b85ca5a37dd2a9fb324d82be2a1d1fdbff9247073e0c2facfe8be8f58bfef0bfd71a7e53c3358e6a043a6bf28e4e5cc50364662b5e4927f1b7a78d0f9eac6f + languageName: node + linkType: hard + +"@metamask/nonce-tracker@npm:^6.0.0": + version: 6.1.0 + resolution: "@metamask/nonce-tracker@npm:6.1.0" + dependencies: + "@ethersproject/providers": "npm:^5.7.2" + async-mutex: "npm:^0.5.0" + checksum: 10/a20cf7084348ef7bdcd9e57e873ca34bf8e04ee43a2ab08634a3ad47fddb632d0e170c1dff03b4ca5bac34a24c35c542fcec509f3c23ce483834082f41b50e4a + languageName: node + linkType: hard + "@metamask/number-to-bn@npm:^1.7.1": version: 1.7.1 resolution: "@metamask/number-to-bn@npm:1.7.1" @@ -4418,6 +5543,25 @@ __metadata: languageName: node linkType: hard +"@metamask/permission-controller@npm:^12.3.0": + version: 12.3.0 + resolution: "@metamask/permission-controller@npm:12.3.0" + dependencies: + "@metamask/approval-controller": "npm:^9.0.1" + "@metamask/base-controller": "npm:^9.0.1" + "@metamask/controller-utils": "npm:^11.19.0" + "@metamask/json-rpc-engine": "npm:^10.2.4" + "@metamask/messenger": "npm:^1.0.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.9.0" + "@types/deep-freeze-strict": "npm:^1.1.0" + deep-freeze-strict: "npm:^1.1.1" + immer: "npm:^9.0.6" + nanoid: "npm:^3.3.8" + checksum: 10/a5fe9f2bab8c2d41cd829cd6c1af970e71da97eac42de17071c10f90d975e9135a4e6987ed6b2f3ea2209b1c6c51b822508f800225fda2207cdc598c16ea77dd + languageName: node + linkType: hard + "@metamask/permission-controller@npm:^13.1.1": version: 13.1.1 resolution: "@metamask/permission-controller@npm:13.1.1" @@ -4437,6 +5581,52 @@ __metadata: languageName: node linkType: hard +"@metamask/phishing-controller@npm:^17.3.0": + version: 17.3.0 + resolution: "@metamask/phishing-controller@npm:17.3.0" + dependencies: + "@metamask/address-book-controller": "npm:^7.1.2" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/transaction-controller": "npm:^69.0.0" + "@noble/hashes": "npm:^1.8.0" + "@types/punycode": "npm:^2.1.0" + ethereum-cryptography: "npm:^2.1.2" + fastest-levenshtein: "npm:^1.0.16" + punycode: "npm:^2.1.1" + checksum: 10/5c9be5dfdd76c66197cf8eaa764da04d34e3f612aded6125c66d31a001430881ed00c59b5c04f30f05e9719a29332cfda0fbcf211b25b54c3ddaf0a812b8f70a + languageName: node + linkType: hard + +"@metamask/polling-controller@npm:^16.0.8": + version: 16.0.8 + resolution: "@metamask/polling-controller@npm:16.0.8" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/network-controller": "npm:^34.0.0" + "@metamask/utils": "npm:^11.11.0" + "@types/uuid": "npm:^8.3.0" + fast-json-stable-stringify: "npm:^2.1.0" + uuid: "npm:^8.3.2" + checksum: 10/3066522432099d9d69475f73b4468da5ce18eb92340cffba2581ed8f0c8dfdb517ae704448fb395bc9a7659409c5a94cfb8a809b56bd35d7cd87de1b2d948dd8 + languageName: node + linkType: hard + +"@metamask/polling-controller@npm:^16.0.9": + version: 16.0.9 + resolution: "@metamask/polling-controller@npm:16.0.9" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/utils": "npm:^11.11.0" + "@types/uuid": "npm:^8.3.0" + fast-json-stable-stringify: "npm:^2.1.0" + uuid: "npm:^8.3.2" + checksum: 10/997f409424f8daffead5a9e5376bc174a474e9cf9b021aee9279337e7ec537105239d19351663244bae89c7a6efefc703cfba3a524780d3527284fd3fe49610a + languageName: node + linkType: hard + "@metamask/post-message-stream@npm:^10.0.0": version: 10.0.0 resolution: "@metamask/post-message-stream@npm:10.0.0" @@ -4447,6 +5637,40 @@ __metadata: languageName: node linkType: hard +"@metamask/preferences-controller@npm:^23.1.0": + version: 23.1.0 + resolution: "@metamask/preferences-controller@npm:23.1.0" + dependencies: + "@metamask/base-controller": "npm:^9.0.1" + "@metamask/messenger": "npm:^1.0.0" + checksum: 10/61fe1115546ea0c1e45143c2227d7907930ae881c3f14fe6bfb260d9d4e6fdfc84e3f46af69454daeef7dcbb7d02fdb204baf0c29bf05e18212479b6e96721a0 + languageName: node + linkType: hard + +"@metamask/profile-sync-controller@npm:^28.3.0": + version: 28.3.0 + resolution: "@metamask/profile-sync-controller@npm:28.3.0" + dependencies: + "@metamask/address-book-controller": "npm:^7.1.2" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/snaps-utils": "npm:^12.1.2" + "@metamask/utils": "npm:^11.11.0" + "@noble/ciphers": "npm:^1.3.0" + "@noble/hashes": "npm:^1.8.0" + immer: "npm:^9.0.6" + loglevel: "npm:^1.8.1" + siwe: "npm:^2.3.2" + peerDependencies: + "@metamask/providers": ^22.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/a0fa6dad8a183bcd41907e7ab5d35fcb321bd1c7040125120deebc20b3d809a0e0f076feb2e80ef70b9ce1330138a8d1c8d8b5eea18086cd765ae4301a32e2ba + languageName: node + linkType: hard + "@metamask/providers@npm:^18.1.0": version: 18.1.0 resolution: "@metamask/providers@npm:18.1.0" @@ -4489,6 +5713,32 @@ __metadata: languageName: node linkType: hard +"@metamask/remote-feature-flag-controller@npm:5.0.0, @metamask/remote-feature-flag-controller@npm:^5.0.0": + version: 5.0.0 + resolution: "@metamask/remote-feature-flag-controller@npm:5.0.0" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/utils": "npm:^11.11.0" + uuid: "npm:^8.3.2" + checksum: 10/8d8b713def28721ade47a9106aef22d885c1d41006ad558b26ae38fffeda8a57e7a972fcbd87f73c2f1fdfdd2a53a9d9d716654770671aca245398684fcb7a52 + languageName: node + linkType: hard + +"@metamask/remote-feature-flag-controller@npm:^4.2.2": + version: 4.2.2 + resolution: "@metamask/remote-feature-flag-controller@npm:4.2.2" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.1.0" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/utils": "npm:^11.9.0" + uuid: "npm:^8.3.2" + checksum: 10/ed03ff1ba63c7a0f2b221c3514eb71a8be4200ec543b90676a44930dc731920ac2c92dcc2b13a32ed4e4e8e7e7b28a26db443af9f4bf30f5e2b5785580b286ab + languageName: node + linkType: hard + "@metamask/rpc-errors@npm:^7.0.0, @metamask/rpc-errors@npm:^7.0.2, @metamask/rpc-errors@npm:^7.0.3": version: 7.0.3 resolution: "@metamask/rpc-errors@npm:7.0.3" @@ -4516,6 +5766,13 @@ __metadata: languageName: node linkType: hard +"@metamask/slip44@npm:^4.3.0, @metamask/slip44@npm:^4.5.0": + version: 4.5.0 + resolution: "@metamask/slip44@npm:4.5.0" + checksum: 10/8450d4f5afa98e8f57c67a39f12aa413888c423505f22c05a3dc181be223f7742db017f54a99852acc3826abe74b22bf7247bccd78b810e509321c075e67516f + languageName: node + linkType: hard + "@metamask/slip44@npm:^4.4.0": version: 4.4.0 resolution: "@metamask/slip44@npm:4.4.0" @@ -4523,10 +5780,23 @@ __metadata: languageName: node linkType: hard -"@metamask/slip44@npm:^4.5.0": - version: 4.5.0 - resolution: "@metamask/slip44@npm:4.5.0" - checksum: 10/8450d4f5afa98e8f57c67a39f12aa413888c423505f22c05a3dc181be223f7742db017f54a99852acc3826abe74b22bf7247bccd78b810e509321c075e67516f +"@metamask/snap-account-service@npm:^2.0.0": + version: 2.0.0 + resolution: "@metamask/snap-account-service@npm:2.0.0" + dependencies: + "@metamask/account-api": "npm:^1.0.4" + "@metamask/account-tree-controller": "npm:^7.5.4" + "@metamask/eth-snap-keyring": "npm:^23.0.0" + "@metamask/keyring-api": "npm:^23.5.0" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-snap-client": "npm:^10.0.5" + "@metamask/keyring-snap-sdk": "npm:^9.2.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/snaps-controllers": "npm:^19.0.0" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/utils": "npm:^11.11.0" + lodash: "npm:^4.17.21" + checksum: 10/a0e860bc34933775c5271ef560efbf8b84eb60e460c7a7586a8169d54750cb9a59500c3c73e97cf0baebb8951830d136f8bb9a7322186ee46b0d6687d7203997 languageName: node linkType: hard @@ -4583,6 +5853,49 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-controllers@npm:^19.0.0, @metamask/snaps-controllers@npm:^19.0.1": + version: 19.0.1 + resolution: "@metamask/snaps-controllers@npm:19.0.1" + dependencies: + "@metamask/approval-controller": "npm:^9.0.1" + "@metamask/base-controller": "npm:^9.0.1" + "@metamask/json-rpc-engine": "npm:^10.2.4" + "@metamask/json-rpc-middleware-stream": "npm:^8.0.8" + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/messenger": "npm:^1.1.0" + "@metamask/object-multiplex": "npm:^2.1.0" + "@metamask/permission-controller": "npm:^12.3.0" + "@metamask/post-message-stream": "npm:^10.0.0" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/snaps-registry": "npm:^4.0.0" + "@metamask/snaps-rpc-methods": "npm:^15.0.2" + "@metamask/snaps-sdk": "npm:^11.0.0" + "@metamask/snaps-utils": "npm:^12.1.3" + "@metamask/storage-service": "npm:^1.0.1" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.10.0" + "@xstate/fsm": "npm:^2.0.0" + async-mutex: "npm:^0.5.0" + concat-stream: "npm:^2.0.0" + cron-parser: "npm:^4.5.0" + fast-deep-equal: "npm:^3.1.3" + get-npm-tarball-url: "npm:^2.0.3" + immer: "npm:^9.0.21" + luxon: "npm:^3.5.0" + nanoid: "npm:^3.3.10" + readable-stream: "npm:^3.6.2" + readable-web-to-node-stream: "npm:^3.0.2" + semver: "npm:^7.5.4" + tar-stream: "npm:^3.1.7" + peerDependencies: + "@metamask/snaps-execution-environments": ^11.0.2 + peerDependenciesMeta: + "@metamask/snaps-execution-environments": + optional: true + checksum: 10/e7cee816b1af1e2c4cbd25a81afff779427e6d738c1fe283a1ca9b23f67c62b5a983d6ed35e91acf0b15016e6825074557f3376d322a518c1acfc36728d36509 + languageName: node + linkType: hard + "@metamask/snaps-controllers@npm:^21.0.0": version: 21.0.0 resolution: "@metamask/snaps-controllers@npm:21.0.0" @@ -4694,6 +6007,23 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-rpc-methods@npm:^15.0.2": + version: 15.1.1 + resolution: "@metamask/snaps-rpc-methods@npm:15.1.1" + dependencies: + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/permission-controller": "npm:^12.3.0" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/snaps-sdk": "npm:^11.1.0" + "@metamask/snaps-utils": "npm:^12.2.0" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + "@noble/hashes": "npm:^1.7.1" + async-mutex: "npm:^0.5.0" + checksum: 10/906eafa8a2d4944e50d73b8aa4d32a7070c33367ad3d58843e892e62b3bd8facb0a02e1d88a9245c18a281637435d97266212b7706ef67ee6a68db85a5991ea3 + languageName: node + linkType: hard + "@metamask/snaps-rpc-methods@npm:^17.0.0": version: 17.0.0 resolution: "@metamask/snaps-rpc-methods@npm:17.0.0" @@ -4735,17 +6065,18 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^11.1.1": - version: 11.1.1 - resolution: "@metamask/snaps-sdk@npm:11.1.1" +"@metamask/snaps-sdk@npm:^11.2.0": + version: 11.2.0 + resolution: "@metamask/snaps-sdk@npm:11.2.0" dependencies: "@metamask/key-tree": "npm:^10.1.1" + "@metamask/messenger": "npm:^2.0.0" "@metamask/providers": "npm:^22.1.1" "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/superstruct": "npm:^3.2.1" + "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" luxon: "npm:^3.5.0" - checksum: 10/8419625775b83045d98c4ed920002ad884471e2303f6650104c97457c238f095ff35030663ddf111ebb6ce8f4cd915f57b7b9a5597d00288093701dbb79e787c + checksum: 10/ce5bb2c6427283b14121996e4d32c4aa95a6a9372419bb0d77998f697f61c76c45aa165d5f949d40797a9dbfe65d8d5c9bb9deaad2a2d5c7ad33d5e6329f7473 languageName: node linkType: hard @@ -4809,7 +6140,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^12.3.0, @metamask/snaps-utils@npm:^12.4.0": +"@metamask/snaps-utils@npm:^12.1.2, @metamask/snaps-utils@npm:^12.1.3, @metamask/snaps-utils@npm:^12.2.0, @metamask/snaps-utils@npm:^12.3.0, @metamask/snaps-utils@npm:^12.4.0": version: 12.4.0 resolution: "@metamask/snaps-utils@npm:12.4.0" dependencies: @@ -4860,13 +6191,17 @@ __metadata: resolution: "@metamask/solana-wallet-snap@workspace:packages/snap" dependencies: "@jest/globals": "npm:^29.5.0" + "@metamask-previews/snap-networks-utils": "npm:0.0.0-preview-8589132" + "@metamask/assets-controller": "npm:13.0.0" "@metamask/auto-changelog": "npm:4.0.0" "@metamask/key-tree": "npm:9.1.2" "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-snap-sdk": "npm:^9.2.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/remote-feature-flag-controller": "npm:5.0.0" "@metamask/snaps-cli": "npm:^8.4.1" "@metamask/snaps-jest": "npm:^10.2.0" - "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/snaps-sdk": "npm:^11.2.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.9.0" "@noble/ed25519": "npm:2.1.0" @@ -4931,7 +6266,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/storage-service@npm:^1.0.2": +"@metamask/storage-service@npm:^1.0.1, @metamask/storage-service@npm:^1.0.2": version: 1.0.2 resolution: "@metamask/storage-service@npm:1.0.2" dependencies: @@ -4955,6 +6290,94 @@ __metadata: languageName: node linkType: hard +"@metamask/superstruct@npm:^3.4.1": + version: 3.4.1 + resolution: "@metamask/superstruct@npm:3.4.1" + checksum: 10/d37b5662dc9bbe0d99e06eb951167fa745829ae3abfa103423e9d8c05e8712f1451376f8479d484722e1e1c1d881732da1efddccaca8868b530a786264b903b5 + languageName: node + linkType: hard + +"@metamask/swappable-obj-proxy@npm:^2.3.0": + version: 2.3.0 + resolution: "@metamask/swappable-obj-proxy@npm:2.3.0" + checksum: 10/1255c599de9237f06df2390719d6dfcb1f168873df61bbaad5ce376efbc057e2030260b94855569313faeb412b7df9b062d209f4b0b163a3dc02f29d42139e1f + languageName: node + linkType: hard + +"@metamask/transaction-controller@npm:^69.0.0": + version: 69.2.1 + resolution: "@metamask/transaction-controller@npm:69.2.1" + dependencies: + "@ethereumjs/common": "npm:^4.4.0" + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/wallet": "npm:^5.7.0" + "@metamask/accounts-controller": "npm:^39.0.5" + "@metamask/approval-controller": "npm:^9.0.2" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/core-backend": "npm:^7.0.0" + "@metamask/gas-fee-controller": "npm:^26.2.4" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/network-controller": "npm:^34.0.0" + "@metamask/nonce-tracker": "npm:^6.0.0" + "@metamask/remote-feature-flag-controller": "npm:^4.2.2" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + bignumber.js: "npm:^9.1.2" + bn.js: "npm:^5.2.1" + eth-method-registry: "npm:^4.0.0" + fast-json-patch: "npm:^3.1.1" + lodash: "npm:^4.17.21" + uuid: "npm:^8.3.2" + peerDependencies: + "@babel/runtime": ^7.0.0 + "@metamask/eth-block-tracker": ">=9" + checksum: 10/1ac5edf5e8b68369a177364945ce932ff736fc2f261eaaaca0a3cc4a36e758d929f20ef76ddf4a311dbeb7a5a7ebad8c4db4afe12cfa3737ce749ae6f30260b6 + languageName: node + linkType: hard + +"@metamask/transaction-controller@npm:^69.4.0": + version: 69.4.0 + resolution: "@metamask/transaction-controller@npm:69.4.0" + dependencies: + "@ethereumjs/common": "npm:^4.4.0" + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/wallet": "npm:^5.7.0" + "@metamask/accounts-controller": "npm:^39.0.6" + "@metamask/approval-controller": "npm:^9.0.2" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/core-backend": "npm:^8.1.0" + "@metamask/gas-fee-controller": "npm:^26.3.1" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/network-controller": "npm:^35.0.0" + "@metamask/nonce-tracker": "npm:^6.0.0" + "@metamask/remote-feature-flag-controller": "npm:^5.0.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/utils": "npm:^11.11.0" + async-mutex: "npm:^0.5.0" + bignumber.js: "npm:^9.1.2" + bn.js: "npm:^5.2.1" + eth-method-registry: "npm:^4.0.0" + fast-json-patch: "npm:^3.1.1" + lodash: "npm:^4.17.21" + uuid: "npm:^8.3.2" + peerDependencies: + "@babel/runtime": ^7.0.0 + "@metamask/eth-block-tracker": ">=9" + checksum: 10/e22b4072c238277f102439cc0fa6470008ac00598e09d1a0946a9e2e81e2df817cec7e9b3e79c4f845050356220057acbab2fa5eb428487e6a7df2c01633fbdf + languageName: node + linkType: hard + "@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0, @metamask/utils@npm:^11.4.0, @metamask/utils@npm:^11.4.2, @metamask/utils@npm:^11.8.1, @metamask/utils@npm:^11.9.0": version: 11.9.0 resolution: "@metamask/utils@npm:11.9.0" @@ -5084,6 +6507,13 @@ __metadata: languageName: node linkType: hard +"@noble/ciphers@npm:^1.3.0": + version: 1.3.0 + resolution: "@noble/ciphers@npm:1.3.0" + checksum: 10/051660051e3e9e2ca5fb9dece2885532b56b7e62946f89afa7284a0fb8bc02e2bd1c06554dba68162ff42d295b54026456084198610f63c296873b2f1cd7a586 + languageName: node + linkType: hard + "@noble/curves@npm:1.2.0": version: 1.2.0 resolution: "@noble/curves@npm:1.2.0" @@ -5139,7 +6569,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.7.1": +"@noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.7.1, @noble/hashes@npm:^1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e @@ -6353,6 +7783,21 @@ __metadata: languageName: node linkType: hard +"@solana/addresses@npm:^2.0.0": + version: 2.3.0 + resolution: "@solana/addresses@npm:2.3.0" + dependencies: + "@solana/assertions": "npm:2.3.0" + "@solana/codecs-core": "npm:2.3.0" + "@solana/codecs-strings": "npm:2.3.0" + "@solana/errors": "npm:2.3.0" + "@solana/nominal-types": "npm:2.3.0" + peerDependencies: + typescript: ">=5.3.3" + checksum: 10/11ee6774f938dd2fe5174375b02c4e52063173ce80a895dd35beb84635dcf3e2ba2e3fe7c74a614b2322da10906abceab758c91a13415a9cda53faf91a8ea722 + languageName: node + linkType: hard + "@solana/assertions@npm:2.1.0": version: 2.1.0 resolution: "@solana/assertions@npm:2.1.0" @@ -6364,6 +7809,17 @@ __metadata: languageName: node linkType: hard +"@solana/assertions@npm:2.3.0": + version: 2.3.0 + resolution: "@solana/assertions@npm:2.3.0" + dependencies: + "@solana/errors": "npm:2.3.0" + peerDependencies: + typescript: ">=5.3.3" + checksum: 10/3b430f01d5991569059fea3e6679a4548a624ff5da6db0c2eb2eba757a9ff774c9d90941a0a2335148599aaef8e8fc004a9c5996eb3627b25dca108b05591d02 + languageName: node + linkType: hard + "@solana/codecs-core@npm:2.1.0": version: 2.1.0 resolution: "@solana/codecs-core@npm:2.1.0" @@ -6375,6 +7831,17 @@ __metadata: languageName: node linkType: hard +"@solana/codecs-core@npm:2.3.0": + version: 2.3.0 + resolution: "@solana/codecs-core@npm:2.3.0" + dependencies: + "@solana/errors": "npm:2.3.0" + peerDependencies: + typescript: ">=5.3.3" + checksum: 10/d9bba1eaa3ee38fef04e1cbfa43defeea16729a1cf1628a71cb72340558a3f2296279899680e1dda4b1756ab2b280b5f1502330c21c35e167e554f3d0c9d193d + languageName: node + linkType: hard + "@solana/codecs-data-structures@npm:2.1.0": version: 2.1.0 resolution: "@solana/codecs-data-structures@npm:2.1.0" @@ -6400,6 +7867,18 @@ __metadata: languageName: node linkType: hard +"@solana/codecs-numbers@npm:2.3.0": + version: 2.3.0 + resolution: "@solana/codecs-numbers@npm:2.3.0" + dependencies: + "@solana/codecs-core": "npm:2.3.0" + "@solana/errors": "npm:2.3.0" + peerDependencies: + typescript: ">=5.3.3" + checksum: 10/e661338b5eb04268a104ff2189b5d001bd2f99e1a3726deaa7157d5acbc3b24740bc25ca03b2028c52ad21fd71d5d5aa64957411c895a9dc1c132aa3bc97b336 + languageName: node + linkType: hard + "@solana/codecs-strings@npm:2.1.0": version: 2.1.0 resolution: "@solana/codecs-strings@npm:2.1.0" @@ -6414,6 +7893,20 @@ __metadata: languageName: node linkType: hard +"@solana/codecs-strings@npm:2.3.0": + version: 2.3.0 + resolution: "@solana/codecs-strings@npm:2.3.0" + dependencies: + "@solana/codecs-core": "npm:2.3.0" + "@solana/codecs-numbers": "npm:2.3.0" + "@solana/errors": "npm:2.3.0" + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: ">=5.3.3" + checksum: 10/a553a891ce149a87f37ff338587980e81f9752e13039cb0d02f0fc6f21ce27af8548ad10f4c9e63f9ef74786ea0e498c91a2f5e3e996a930f581f2977bb1c215 + languageName: node + linkType: hard + "@solana/codecs@npm:2.1.0": version: 2.1.0 resolution: "@solana/codecs@npm:2.1.0" @@ -6443,6 +7936,20 @@ __metadata: languageName: node linkType: hard +"@solana/errors@npm:2.3.0": + version: 2.3.0 + resolution: "@solana/errors@npm:2.3.0" + dependencies: + chalk: "npm:^5.4.1" + commander: "npm:^14.0.0" + peerDependencies: + typescript: ">=5.3.3" + bin: + errors: bin/cli.mjs + checksum: 10/0e8a329790b7d38b4bfe1fa6ec2ac60be20562a610d992031395fe9886da28b578a9d0aebb318f5357ae0d4cbc8f3d323c12b9520da2cf6adc9038f96afc3fe1 + languageName: node + linkType: hard + "@solana/fast-stable-stringify@npm:2.1.0": version: 2.1.0 resolution: "@solana/fast-stable-stringify@npm:2.1.0" @@ -6515,6 +8022,15 @@ __metadata: languageName: node linkType: hard +"@solana/nominal-types@npm:2.3.0": + version: 2.3.0 + resolution: "@solana/nominal-types@npm:2.3.0" + peerDependencies: + typescript: ">=5.3.3" + checksum: 10/0594893661f4ff2f8587689cd4b61ee15c38c455fe5cbaa7ae7e416f3a483fac97cc3f5a5b3d0a7526bfb89d7da91bc2c72e7b1790bbe59b986579ef2f76689b + languageName: node + linkType: hard + "@solana/options@npm:2.1.0": version: 2.1.0 resolution: "@solana/options@npm:2.1.0" @@ -6844,6 +8360,51 @@ __metadata: languageName: node linkType: hard +"@spruceid/siwe-parser@npm:^2.1.2": + version: 2.1.2 + resolution: "@spruceid/siwe-parser@npm:2.1.2" + dependencies: + "@noble/hashes": "npm:^1.1.2" + apg-js: "npm:^4.3.0" + uri-js: "npm:^4.4.1" + valid-url: "npm:^1.0.9" + checksum: 10/48459fe3b4d4b3091375ee87af700864c9023d4a1271d34850c6d27475e5d93a45d1efe8a71da367ad838b6921ced60c387d54737edd0a7a0d8e4e0a3cc2b8b7 + languageName: node + linkType: hard + +"@stablelib/binary@npm:^1.0.1": + version: 1.0.1 + resolution: "@stablelib/binary@npm:1.0.1" + dependencies: + "@stablelib/int": "npm:^1.0.1" + checksum: 10/c5ed769e2b5d607a5cdb72d325fcf98db437627862fade839daad934bd9ccf02a6f6e34f9de8cb3b18d72fce2ba6cc019a5d22398187d7d69d2607165f27f8bf + languageName: node + linkType: hard + +"@stablelib/int@npm:^1.0.1": + version: 1.0.1 + resolution: "@stablelib/int@npm:1.0.1" + checksum: 10/65bfbf50a382eea70c68e05366bf379cfceff8fbc076f1c267ef2f2411d7aed64fd140c415cb6c29f19a3910d3b8b7805d4b32ad5721a5007a8e744a808c7ae3 + languageName: node + linkType: hard + +"@stablelib/random@npm:^1.0.1": + version: 1.0.2 + resolution: "@stablelib/random@npm:1.0.2" + dependencies: + "@stablelib/binary": "npm:^1.0.1" + "@stablelib/wipe": "npm:^1.0.1" + checksum: 10/f5ace0a588dc4c21f01cb85837892d4c872e994ae77a58a8eb7dd61aa0b26fb1e9b46b0445e71af57d963ef7d9f5965c64258fc0d04df7b2947bc48f2d3560c5 + languageName: node + linkType: hard + +"@stablelib/wipe@npm:^1.0.1": + version: 1.0.1 + resolution: "@stablelib/wipe@npm:1.0.1" + checksum: 10/287802eb146810a46ba72af70b82022caf83a8aeebde23605f5ee0decf64fe2b97a60c856e43b6617b5801287c30cfa863cfb0469e7fcde6f02d143cf0c6cbf4 + languageName: node + linkType: hard + "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" @@ -7169,6 +8730,13 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-core@npm:^5.62.16": + version: 5.101.4 + resolution: "@tanstack/query-core@npm:5.101.4" + checksum: 10/df86031035ac9a5cf54efece85718b90e93fca62068a44a176da232b6dd49b27168840f2da18c64a0eb25d171bc3fb1aab417db4643025cd194ff887833bd73f + languageName: node + linkType: hard + "@testing-library/dom@npm:^10.4.0": version: 10.4.0 resolution: "@testing-library/dom@npm:10.4.0" @@ -7353,6 +8921,15 @@ __metadata: languageName: node linkType: hard +"@types/bn.js@npm:^5.1.0": + version: 5.2.0 + resolution: "@types/bn.js@npm:5.2.0" + dependencies: + "@types/node": "npm:*" + checksum: 10/06c93841f74e4a5e5b81b74427d56303b223c9af36389b4cd3c562bda93f43c425c7e241aee1b0b881dde57238dc2e07f21d30d412b206a7dae4435af4c054e8 + languageName: node + linkType: hard + "@types/bn.js@npm:^5.1.5": version: 5.1.5 resolution: "@types/bn.js@npm:5.1.5" @@ -7741,6 +9318,15 @@ __metadata: languageName: node linkType: hard +"@types/pbkdf2@npm:^3.0.0": + version: 3.1.2 + resolution: "@types/pbkdf2@npm:3.1.2" + dependencies: + "@types/node": "npm:*" + checksum: 10/bebe1e596cbbe5f7d2726a58859e61986c5a42459048e29cb7f2d4d764be6bbb0844572fd5d70ca8955a8a17e8b4ed80984fc4903e165d9efb8807a3fbb051aa + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.5 resolution: "@types/prop-types@npm:15.7.5" @@ -7748,6 +9334,13 @@ __metadata: languageName: node linkType: hard +"@types/punycode@npm:^2.1.0": + version: 2.1.4 + resolution: "@types/punycode@npm:2.1.4" + checksum: 10/16637bdd8e4f830243072668125f83b93b728085a05140ccc3e7801528d78c62cce5426b07d5cdc75e4f797e1644807c762777f651d1cd071ad0128835cdce5e + languageName: node + linkType: hard + "@types/qs@npm:*": version: 6.9.17 resolution: "@types/qs@npm:6.9.17" @@ -7817,6 +9410,15 @@ __metadata: languageName: node linkType: hard +"@types/secp256k1@npm:^4.0.1": + version: 4.0.7 + resolution: "@types/secp256k1@npm:4.0.7" + dependencies: + "@types/node": "npm:*" + checksum: 10/bcf7adbd953d2cb829ae98a3cd2f8eeb1e2316f4f6de720ee3863da90bd98899c08fa8c42748e8701cad16ede0898df69a6e7f22660d1fb3cb276d5c771069db + languageName: node + linkType: hard + "@types/semver@npm:^7.3.12": version: 7.5.4 resolution: "@types/semver@npm:7.5.4" @@ -7884,6 +9486,20 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^8.3.0": + version: 8.3.4 + resolution: "@types/uuid@npm:8.3.4" + checksum: 10/6f11f3ff70f30210edaa8071422d405e9c1d4e53abbe50fdce365150d3c698fe7bbff65c1e71ae080cbfb8fded860dbb5e174da96fdbbdfcaa3fb3daa474d20f + languageName: node + linkType: hard + +"@types/uuid@npm:^9.0.8": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10/b8c60b7ba8250356b5088302583d1704a4e1a13558d143c549c408bf8920535602ffc12394ede77f8a8083511b023704bc66d1345792714002bfa261b17c5275 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -10238,6 +11854,13 @@ __metadata: languageName: node linkType: hard +"aes-js@npm:3.0.0": + version: 3.0.0 + resolution: "aes-js@npm:3.0.0" + checksum: 10/1b3772e5ba74abdccb6c6b99bf7f50b49057b38c0db1612b46c7024414f16e65ba7f1643b2d6e38490b1870bdf3ba1b87b35e2c831fd3fdaeff015f08aad19d1 + languageName: node + linkType: hard + "aes-js@npm:4.0.0-beta.5": version: 4.0.0-beta.5 resolution: "aes-js@npm:4.0.0-beta.5" @@ -10245,6 +11868,13 @@ __metadata: languageName: node linkType: hard +"aes-js@npm:^3.1.2": + version: 3.1.2 + resolution: "aes-js@npm:3.1.2" + checksum: 10/b65916767034a51375a3ac5aad62af452d89a386c1ae7b607bb9145d0bb8b8823bf2f3eba85bdfa52d61c65d5aed90ba90f677b8c826bfa1a8b7ae2fa3b54d91 + languageName: node + linkType: hard + "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -10486,6 +12116,13 @@ __metadata: languageName: node linkType: hard +"apg-js@npm:^4.3.0": + version: 4.4.0 + resolution: "apg-js@npm:4.4.0" + checksum: 10/425f19096026742f5f156f26542b68f55602aa60f0c4ae2d72a0a888cf15fe9622223191202262dd8979d76a6125de9d8fd164d56c95fb113f49099f405eb08c + languageName: node + linkType: hard + "append-field@npm:^1.0.0": version: 1.0.0 resolution: "append-field@npm:1.0.0" @@ -11206,7 +12843,7 @@ __metadata: languageName: node linkType: hard -"base-x@npm:^3.0.8": +"base-x@npm:^3.0.2, base-x@npm:^3.0.8": version: 3.0.11 resolution: "base-x@npm:3.0.11" dependencies: @@ -11243,6 +12880,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:1.1.4": + version: 1.1.4 + resolution: "bech32@npm:1.1.4" + checksum: 10/63ff37c0ce43be914c685ce89700bba1589c319af0dac1ea04f51b33d0e5ecfd40d14c24f527350b94f0a4e236385373bb9122ec276410f354ddcdbf29ca13f4 + languageName: node + linkType: hard + "bech32@npm:^2.0.0": version: 2.0.0 resolution: "bech32@npm:2.0.0" @@ -11314,6 +12958,13 @@ __metadata: languageName: node linkType: hard +"blakejs@npm:^1.1.0": + version: 1.2.1 + resolution: "blakejs@npm:1.2.1" + checksum: 10/0638b1bd058b21892633929c43005aa6a4cc4b2ac5b338a146c3c076622f1b360795bd7a4d1f077c9b01863ed2df0c1504a81c5b520d164179120434847e6cd7 + languageName: node + linkType: hard + "bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" @@ -11321,6 +12972,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:4.11.6": + version: 4.11.6 + resolution: "bn.js@npm:4.11.6" + checksum: 10/22741b015c9fff60fce32fc9988331b298eb9b6db5bfb801babb23b846eaaf894e440e0d067b2b3ae4e46aab754e90972f8f333b31bf94a686bbcb054bfa7b14 + languageName: node + linkType: hard + "bn.js@npm:5.2.1, bn.js@npm:^5.0.0, bn.js@npm:^5.2.1": version: 5.2.1 resolution: "bn.js@npm:5.2.1" @@ -11335,6 +12993,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:^5.1.2, bn.js@npm:^5.2.0": + version: 5.2.5 + resolution: "bn.js@npm:5.2.5" + checksum: 10/10b1ac91e76fd828c1187cf65a9f1a0374e7afb9acd167e19c2d7c4108f6216841007c1e3aef145fa95cc7b3d07f705236a707184af1537667362b3a842d01ac + languageName: node + linkType: hard + "body-parser@npm:1.20.1": version: 1.20.1 resolution: "body-parser@npm:1.20.1" @@ -11466,7 +13131,7 @@ __metadata: languageName: node linkType: hard -"browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4": +"browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0": version: 1.2.0 resolution: "browserify-aes@npm:1.2.0" dependencies: @@ -11562,12 +13227,32 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^4.0.0": + version: 4.0.1 + resolution: "bs58@npm:4.0.1" + dependencies: + base-x: "npm:^3.0.2" + checksum: 10/b3c5365bb9e0c561e1a82f1a2d809a1a692059fae016be233a6127ad2f50a6b986467c3a50669ce4c18929dcccb297c5909314dd347a25a68c21b68eb3e95ac2 + languageName: node + linkType: hard + "bs58@npm:^6.0.0": version: 6.0.0 resolution: "bs58@npm:6.0.0" dependencies: - base-x: "npm:^5.0.0" - checksum: 10/7c9bb2b2d93d997a8c652de3510d89772007ac64ee913dc4e16ba7ff47624caad3128dcc7f360763eb6308760c300b3e9fd91b8bcbd489acd1a13278e7949c4e + base-x: "npm:^5.0.0" + checksum: 10/7c9bb2b2d93d997a8c652de3510d89772007ac64ee913dc4e16ba7ff47624caad3128dcc7f360763eb6308760c300b3e9fd91b8bcbd489acd1a13278e7949c4e + languageName: node + linkType: hard + +"bs58check@npm:^2.1.2": + version: 2.1.2 + resolution: "bs58check@npm:2.1.2" + dependencies: + bs58: "npm:^4.0.0" + create-hash: "npm:^1.1.0" + safe-buffer: "npm:^5.1.2" + checksum: 10/43bdf08a5dd04581b78f040bc4169480e17008da482ffe2a6507327bbc4fc5c28de0501f7faf22901cfe57fbca79cbb202ca529003fedb4cb8dccd265b38e54d languageName: node linkType: hard @@ -11777,7 +13462,19 @@ __metadata: languageName: node linkType: hard -"call-bound@npm:^1.0.2": +"call-bind@npm:^1.0.9": + version: 1.0.9 + resolution: "call-bind@npm:1.0.9" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + get-intrinsic: "npm:^1.3.0" + set-function-length: "npm:^1.2.2" + checksum: 10/25b1a98d6158f0adf9fface594ca82be4e3ed481d8ff7f36ad1fccb0c8377e38c6a04ff3248693723222d378677e93077c739defc8a6741c82b7e00bcee1245d + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": version: 1.0.4 resolution: "call-bound@npm:1.0.4" dependencies: @@ -11904,6 +13601,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.4.1": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10/1b2f48f6fba1370670d5610f9cd54c391d6ede28f4b7062dd38244ea5768777af72e5be6b74fb6c6d54cb84c4a2dff3f3afa9b7cb5948f7f022cfd3d087989e0 + languageName: node + linkType: hard + "change-case-all@npm:1.0.14": version: 1.0.14 resolution: "change-case-all@npm:1.0.14" @@ -12308,6 +14012,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^14.0.0": + version: 14.0.3 + resolution: "commander@npm:14.0.3" + checksum: 10/dfa9ebe2a433d277de5cb0252d23b10a543d245d892db858d23b516336a835c50fd4f52bee4cd13c705cc8acb6f03dc632c73dd806f7d06d3353eb09953dd17a + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -13754,7 +15465,7 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:6.6.1": +"elliptic@npm:6.6.1, elliptic@npm:^6.5.7": version: 6.6.1 resolution: "elliptic@npm:6.6.1" dependencies: @@ -14829,7 +16540,42 @@ __metadata: languageName: node linkType: hard -"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.2.1": +"eth-method-registry@npm:^4.0.0": + version: 4.0.0 + resolution: "eth-method-registry@npm:4.0.0" + dependencies: + "@metamask/ethjs-contract": "npm:^0.4.1" + "@metamask/ethjs-query": "npm:^0.7.1" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 10/959e833327960f40fab8693997e071def75103a694e931ab11313e642b6958d4f2878000d91ddfd6ba3c158bdc3f381c63eae0dd26738790e2aca5004ceb0387 + languageName: node + linkType: hard + +"ethereum-cryptography@npm:^0.1.3": + version: 0.1.3 + resolution: "ethereum-cryptography@npm:0.1.3" + dependencies: + "@types/pbkdf2": "npm:^3.0.0" + "@types/secp256k1": "npm:^4.0.1" + blakejs: "npm:^1.1.0" + browserify-aes: "npm:^1.2.0" + bs58check: "npm:^2.1.2" + create-hash: "npm:^1.2.0" + create-hmac: "npm:^1.1.7" + hash.js: "npm:^1.1.7" + keccak: "npm:^3.0.0" + pbkdf2: "npm:^3.0.17" + randombytes: "npm:^2.1.0" + safe-buffer: "npm:^5.1.2" + scrypt-js: "npm:^3.0.0" + secp256k1: "npm:^4.0.1" + setimmediate: "npm:^1.0.5" + checksum: 10/975e476782746acd97d5b37366801ae622a52fb31e5d83f600804be230a61ef7b9d289dcecd9c308fb441967caf3a6e3768dd7c8add6441fcc60c398175d5a96 + languageName: node + linkType: hard + +"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2, ethereum-cryptography@npm:^2.2.1": version: 2.2.1 resolution: "ethereum-cryptography@npm:2.2.1" dependencies: @@ -14841,6 +16587,35 @@ __metadata: languageName: node linkType: hard +"ethereumjs-util@npm:^7.1.2": + version: 7.1.5 + resolution: "ethereumjs-util@npm:7.1.5" + dependencies: + "@types/bn.js": "npm:^5.1.0" + bn.js: "npm:^5.1.2" + create-hash: "npm:^1.1.2" + ethereum-cryptography: "npm:^0.1.3" + rlp: "npm:^2.2.4" + checksum: 10/f28fc1ebb8f35bf9e418f76f51be737d94d603b912c3e014c4e87cd45ccd1b10bdfef764c8f152574b57e9faa260a18773cbc110f9e0a754d6b3730699e54dc9 + languageName: node + linkType: hard + +"ethereumjs-wallet@npm:^1.0.1": + version: 1.0.2 + resolution: "ethereumjs-wallet@npm:1.0.2" + dependencies: + aes-js: "npm:^3.1.2" + bs58check: "npm:^2.1.2" + ethereum-cryptography: "npm:^0.1.3" + ethereumjs-util: "npm:^7.1.2" + randombytes: "npm:^2.1.0" + scrypt-js: "npm:^3.0.1" + utf8: "npm:^3.0.0" + uuid: "npm:^8.3.2" + checksum: 10/0a9ad0ac0930627c15e6fa6115dde8d1dd81160e57fbdf81ef0bcd1e0edd750fe9e8b00992651e694cabefee29eef2ad651dce8b24ae5253861927d0e19b6c90 + languageName: node + linkType: hard + "ethers@npm:^6.16.0": version: 6.16.0 resolution: "ethers@npm:6.16.0" @@ -14856,6 +16631,24 @@ __metadata: languageName: node linkType: hard +"ethjs-abi@npm:^0.2.0": + version: 0.2.1 + resolution: "ethjs-abi@npm:0.2.1" + dependencies: + bn.js: "npm:4.11.6" + js-sha3: "npm:0.5.5" + number-to-bn: "npm:1.7.0" + checksum: 10/733b2b95ae102f0813ad70848d42424b4b6204598892bee0e0fb435aafc7d356e479e489ee2d6b81fb2bd6e5ae6a9cdc0479bc35244b2efbe1a00a8da6735fbb + languageName: node + linkType: hard + +"ethjs-schema@npm:0.2.1": + version: 0.2.1 + resolution: "ethjs-schema@npm:0.2.1" + checksum: 10/9e2806518e208650a035aa2bc1e508a57c31ff629b46b63de4c7101e760f5e6987e6f8b70522eb44cf4a97d2c4c6348d1adaeea56915e75e4deadd1e176d5aa9 + languageName: node + linkType: hard + "event-emitter@npm:^0.3.5": version: 0.3.5 resolution: "event-emitter@npm:0.3.5" @@ -15151,6 +16944,13 @@ __metadata: languageName: node linkType: hard +"fast-json-patch@npm:^3.1.1": + version: 3.1.1 + resolution: "fast-json-patch@npm:3.1.1" + checksum: 10/3e56304e1c95ad1862a50e5b3f557a74c65c0ff2ba5b15caab983b43e70e86ddbc5bc887e9f7064f0aacfd0f0435a29ab2f000fe463379e72b906486345e6671 + languageName: node + linkType: hard + "fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -15484,6 +17284,15 @@ __metadata: languageName: node linkType: hard +"for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.3.0 resolution: "foreground-child@npm:3.3.0" @@ -16830,6 +18639,18 @@ __metadata: languageName: node linkType: hard +"hash-base@npm:^3.1.2": + version: 3.1.2 + resolution: "hash-base@npm:3.1.2" + dependencies: + inherits: "npm:^2.0.4" + readable-stream: "npm:^2.3.8" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.1" + checksum: 10/f2100420521ec77736ebd9279f2c0b3ab2820136a2fa408ea36f3201d3f6984cda166806e6a0287f92adf179430bedfbdd74348ac351e24a3eff9f01a8c406b0 + languageName: node + linkType: hard + "hash-wasm@npm:^4.9.0": version: 4.11.0 resolution: "hash-wasm@npm:4.11.0" @@ -16837,7 +18658,7 @@ __metadata: languageName: node linkType: hard -"hash.js@npm:1.1.7, hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": +"hash.js@npm:1.1.7, hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": version: 1.1.7 resolution: "hash.js@npm:1.1.7" dependencies: @@ -17506,6 +19327,13 @@ __metadata: languageName: node linkType: hard +"is-fn@npm:^1.0.0": + version: 1.0.0 + resolution: "is-fn@npm:1.0.0" + checksum: 10/eeea1e536716f93a92dc1a8550b2c0909fe74bb5144d0fb6d65e0d31eb9c06c30559f69d83a9351d2288cc7293b43bc074e0fab5fae19e312ff38aa0c7672827 + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -17821,6 +19649,15 @@ __metadata: languageName: node linkType: hard +"is-typed-array@npm:^1.1.14": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 + languageName: node + linkType: hard + "is-typedarray@npm:^1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" @@ -18791,6 +20628,13 @@ __metadata: languageName: node linkType: hard +"js-sha3@npm:0.5.5": + version: 0.5.5 + resolution: "js-sha3@npm:0.5.5" + checksum: 10/9ce8bfabdba2cfb94b911125fc278e2f46cc01b6590c98833d9361199e2c2b4bca0427d04da6aa083f05c2c3982029200964a3d6e417b0c126c80f2e32c2d5eb + languageName: node + linkType: hard + "js-sha3@npm:0.8.0": version: 0.8.0 resolution: "js-sha3@npm:0.8.0" @@ -18805,6 +20649,13 @@ __metadata: languageName: node linkType: hard +"js-sha3@npm:^0.9.2": + version: 0.9.3 + resolution: "js-sha3@npm:0.9.3" + checksum: 10/8daacb93b18609a0dc081f2f6199b80a96df36f9975b4b9c7476ae92822e07100b9e1969fc76f4b58e703cd6175f0de7656a99cbb2335cfb554c66f988fbead5 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -18906,7 +20757,7 @@ __metadata: languageName: node linkType: hard -"json-rpc-random-id@npm:^1.0.0": +"json-rpc-random-id@npm:^1.0.0, json-rpc-random-id@npm:^1.0.1": version: 1.0.1 resolution: "json-rpc-random-id@npm:1.0.1" checksum: 10/fcd2e884193a129ace4002bd65a86e9cdb206733b4693baea77bd8b372cf8de3043fbea27716a2c9a716581a908ca8d978d9dfec4847eb2cf77edb4cf4b2252c @@ -18974,6 +20825,13 @@ __metadata: languageName: node linkType: hard +"jsonschema@npm:^1.4.1": + version: 1.5.0 + resolution: "jsonschema@npm:1.5.0" + checksum: 10/46bf49b388ba922073bcb3c8d5e90af9d29fc8303dc866fd440182c88d6b4fd2807679fd39cdefb4113156d104ea47da9c0ff4bbcb0032c9fa29461cb1a92182 + languageName: node + linkType: hard + "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": version: 3.3.5 resolution: "jsx-ast-utils@npm:3.3.5" @@ -18986,6 +20844,18 @@ __metadata: languageName: node linkType: hard +"keccak@npm:^3.0.0": + version: 3.0.4 + resolution: "keccak@npm:3.0.4" + dependencies: + node-addon-api: "npm:^2.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.0" + readable-stream: "npm:^3.6.0" + checksum: 10/45478bb0a57e44d0108646499b8360914b0fbc8b0e088f1076659cb34faaa9eb829c40f6dd9dadb3460bb86cc33153c41fed37fe5ce09465a60e71e78c23fa55 + languageName: node + linkType: hard + "keyv@npm:^4.0.0, keyv@npm:^4.5.3, keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -19425,6 +21295,13 @@ __metadata: languageName: node linkType: hard +"loglevel@npm:^1.8.1": + version: 1.9.2 + resolution: "loglevel@npm:1.9.2" + checksum: 10/6153d8db308323f7ee20130bc40309e7a976c30a10379d8666b596d9c6441965c3e074c8d7ee3347fe5cfc059c0375b6f3e8a10b93d5b813cc5547f5aa412a29 + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -20268,6 +22145,13 @@ __metadata: languageName: node linkType: hard +"multiformats@npm:^9.9.0": + version: 9.9.0 + resolution: "multiformats@npm:9.9.0" + checksum: 10/ad55c7d480d22f4258a68fd88aa2aab744fe0cb1e68d732fc886f67d858b37e3aa6c2cec12b2960ead7730d43be690931485238569952d8a3d7f90fdc726c652 + languageName: node + linkType: hard + "mute-stream@npm:0.0.8, mute-stream@npm:~0.0.4": version: 0.0.8 resolution: "mute-stream@npm:0.0.8" @@ -20368,6 +22252,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^2.0.0": + version: 2.0.2 + resolution: "node-addon-api@npm:2.0.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10/e4ce4daac5b2fefa6b94491b86979a9c12d9cceba571d2c6df1eb5859f9da68e5dc198f128798e1785a88aafee6e11f4992dcccd4bf86bec90973927d158bd60 + languageName: node + linkType: hard + "node-addon-api@npm:^4.3.0": version: 4.3.0 resolution: "node-addon-api@npm:4.3.0" @@ -20377,6 +22270,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/595f59ffb4630564f587c502119cbd980d302e482781021f3b479f5fc7e41cf8f2f7280fdc2795f32d148e4f3259bd15043c52d4a3442796aa6f1ae97b959636 + languageName: node + linkType: hard + "node-addon-api@npm:^6.1.0": version: 6.1.0 resolution: "node-addon-api@npm:6.1.0" @@ -20431,6 +22333,17 @@ __metadata: languageName: node linkType: hard +"node-gyp-build@npm:^4.2.0": + version: 4.8.4 + resolution: "node-gyp-build@npm:4.8.4" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10/6a7d62289d1afc419fc8fc9bd00aa4e554369e50ca0acbc215cb91446148b75ff7e2a3b53c2c5b2c09a39d416d69f3d3237937860373104b5fe429bf30ad9ac5 + languageName: node + linkType: hard + "node-gyp@npm:^10.0.0": version: 10.2.0 resolution: "node-gyp@npm:10.2.0" @@ -20694,6 +22607,16 @@ __metadata: languageName: node linkType: hard +"number-to-bn@npm:1.7.0": + version: 1.7.0 + resolution: "number-to-bn@npm:1.7.0" + dependencies: + bn.js: "npm:4.11.6" + strip-hex-prefix: "npm:1.0.0" + checksum: 10/702e8f00b6b90abd23f711056005179c3bd5ce3b063c47d468250f63ab3b9b4b82e27bff3b4642a9e71e06c717d5ed359873501746df0a64c3db1fa6d704e704 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -21378,6 +23301,20 @@ __metadata: languageName: node linkType: hard +"pbkdf2@npm:^3.0.17": + version: 3.1.6 + resolution: "pbkdf2@npm:3.1.6" + dependencies: + create-hash: "npm:^1.2.0" + create-hmac: "npm:^1.1.7" + ripemd160: "npm:^2.0.3" + safe-buffer: "npm:^5.2.1" + sha.js: "npm:^2.4.12" + to-buffer: "npm:^1.2.2" + checksum: 10/489af0d7b0c25da78517c45e3f2f02804e2131d7cc9d0393d086ac53c07b852248eb92c1011613a87984191647a254ae2928888df042484d79f7b359c417f6ea + languageName: node + linkType: hard + "pbkdf2@npm:^3.0.3": version: 3.1.2 resolution: "pbkdf2@npm:3.1.2" @@ -21442,6 +23379,13 @@ __metadata: languageName: node linkType: hard +"pify@npm:^5.0.0": + version: 5.0.0 + resolution: "pify@npm:5.0.0" + checksum: 10/443e3e198ad6bfa8c0c533764cf75c9d5bc976387a163792fb553ffe6ce923887cf14eebf5aea9b7caa8eab930da8c33612990ae85bd8c2bc18bedb9eae94ecb + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.6 resolution: "pirates@npm:4.0.6" @@ -22057,6 +24001,16 @@ __metadata: languageName: node linkType: hard +"promise-to-callback@npm:^1.0.0": + version: 1.0.0 + resolution: "promise-to-callback@npm:1.0.0" + dependencies: + is-fn: "npm:^1.0.0" + set-immediate-shim: "npm:^1.0.1" + checksum: 10/d756ad74f7a202a6725c4281813c1052896327e4036c553adb8e3bbb91d0b1ff501965e7b00997827c016609caefbf24a5efc117dccca6000b8cbbc477b31cbf + languageName: node + linkType: hard + "promise@npm:^7.1.1": version: 7.3.1 resolution: "promise@npm:7.3.1" @@ -22527,7 +24481,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.2.2": +"readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.8": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -22801,6 +24755,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.1.1": + version: 5.2.0 + resolution: "reselect@npm:5.2.0" + checksum: 10/e53d37a35f84132682b0a819d942ff7debea90fe126f4bb5eef0f21b1f48f45dec89d27990f2ef5865f5e05d64bb88fb41e4341eb276674aee6f1f7f7362c3e8 + languageName: node + linkType: hard + "resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" @@ -23002,6 +24963,27 @@ __metadata: languageName: node linkType: hard +"ripemd160@npm:^2.0.3": + version: 2.0.3 + resolution: "ripemd160@npm:2.0.3" + dependencies: + hash-base: "npm:^3.1.2" + inherits: "npm:^2.0.4" + checksum: 10/d15d42ea0460426675e5320f86d3468ab408af95b1761cf35f8d32c0c97b4d3bb72b7226e990e643b96e1637a8ad26b343a6c7666e1a297bcab4f305a1d9d3e3 + languageName: node + linkType: hard + +"rlp@npm:^2.2.4": + version: 2.2.7 + resolution: "rlp@npm:2.2.7" + dependencies: + bn.js: "npm:^5.2.0" + bin: + rlp: bin/rlp + checksum: 10/cf1919a2dc99f336191b3363b76299db567c192b7ee3c6f5c722728c34f65577883c9c88eeb7a1bfcbc26693c8a4f1fb0662e79ee86f0c98dd258d6987303498 + languageName: node + linkType: hard + "rollup-plugin-delete@npm:^3.0.1": version: 3.0.1 resolution: "rollup-plugin-delete@npm:3.0.1" @@ -23097,6 +25079,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.4.3": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -23158,6 +25147,25 @@ __metadata: languageName: node linkType: hard +"scrypt-js@npm:3.0.1, scrypt-js@npm:^3.0.0, scrypt-js@npm:^3.0.1": + version: 3.0.1 + resolution: "scrypt-js@npm:3.0.1" + checksum: 10/2f8aa72b7f76a6f9c446bbec5670f80d47497bccce98474203d89b5667717223eeb04a50492ae685ed7adc5a060fc2d8f9fd988f8f7ebdaf3341967f3aeff116 + languageName: node + linkType: hard + +"secp256k1@npm:^4.0.1": + version: 4.0.4 + resolution: "secp256k1@npm:4.0.4" + dependencies: + elliptic: "npm:^6.5.7" + node-addon-api: "npm:^5.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.0" + checksum: 10/45000f348c853df7c1e2b67c48efb062ae78c0620ab1a5cfb02fa20d3aad39c641f4e7a18b3de3b54a7c0cc1e0addeb8ecd9d88bc332e92df17a92b60c36122a + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.7.1": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -23341,6 +25349,20 @@ __metadata: languageName: node linkType: hard +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d + languageName: node + linkType: hard + "set-function-name@npm:^2.0.0, set-function-name@npm:^2.0.1": version: 2.0.2 resolution: "set-function-name@npm:2.0.2" @@ -23353,6 +25375,13 @@ __metadata: languageName: node linkType: hard +"set-immediate-shim@npm:^1.0.1": + version: 1.0.1 + resolution: "set-immediate-shim@npm:1.0.1" + checksum: 10/98c4d6778c98363436690a340077142ef11c1a8c8c6a78118242340c8e82bb2d66a1563707ef3f5455e7ff5cbee21c87e898b333cf3e7ed241bab12d19e9eab1 + languageName: node + linkType: hard + "setimmediate@npm:^1.0.4, setimmediate@npm:^1.0.5": version: 1.0.5 resolution: "setimmediate@npm:1.0.5" @@ -23379,6 +25408,19 @@ __metadata: languageName: node linkType: hard +"sha.js@npm:^2.4.12": + version: 2.4.12 + resolution: "sha.js@npm:2.4.12" + dependencies: + inherits: "npm:^2.0.4" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.0" + bin: + sha.js: bin.js + checksum: 10/39c0993592c2ab34eb2daae2199a2a1d502713765aecb611fd97c0c4ab7cd53e902d628e1962aaf384bafd28f55951fef46dcc78799069ce41d74b03aa13b5a7 + languageName: node + linkType: hard + "sha256-uint8array@npm:^0.10.3": version: 0.10.7 resolution: "sha256-uint8array@npm:0.10.7" @@ -23545,6 +25587,13 @@ __metadata: languageName: node linkType: hard +"single-call-balance-checker-abi@npm:^1.0.0": + version: 1.0.0 + resolution: "single-call-balance-checker-abi@npm:1.0.0" + checksum: 10/d68fd83e58c42e6ccd4951d30cacffad01b4ca46595766b51ca09346f715027eac3a1d9e80e6147c7c20181ac6aca5d5330f9d1b131c91cbac3a16c1f3efbc3a + languageName: node + linkType: hard + "sirv@npm:^2.0.3": version: 2.0.4 resolution: "sirv@npm:2.0.4" @@ -23563,6 +25612,20 @@ __metadata: languageName: node linkType: hard +"siwe@npm:^2.3.2": + version: 2.3.2 + resolution: "siwe@npm:2.3.2" + dependencies: + "@spruceid/siwe-parser": "npm:^2.1.2" + "@stablelib/random": "npm:^1.0.1" + uri-js: "npm:^4.4.1" + valid-url: "npm:^1.0.9" + peerDependencies: + ethers: ^5.6.8 || ^6.0.8 + checksum: 10/6ea5ad9a9046fa916f85bf9d3092bc898f7e339d9c552714ea53ecc17daa4f78300c3cf7cc9c70fe57baf77dcee5cb38c6e1d692400b874cd84d297b1261918c + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -24737,6 +26800,17 @@ __metadata: languageName: node linkType: hard +"to-buffer@npm:^1.2.0, to-buffer@npm:^1.2.1, to-buffer@npm:^1.2.2": + version: 1.2.2 + resolution: "to-buffer@npm:1.2.2" + dependencies: + isarray: "npm:^2.0.5" + safe-buffer: "npm:^5.2.1" + typed-array-buffer: "npm:^1.0.3" + checksum: 10/69d806c20524ff1e4c44d49276bc96ff282dcae484780a3974e275dabeb75651ea430b074a2a4023701e63b3e1d87811cd82c0972f35280fe5461710e4872aba + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -24972,6 +27046,13 @@ __metadata: languageName: node linkType: hard +"tweetnacl@npm:^1.0.3": + version: 1.0.3 + resolution: "tweetnacl@npm:1.0.3" + checksum: 10/ca122c2f86631f3c0f6d28efb44af2a301d4a557a62a3e2460286b08e97567b258c2212e4ad1cfa22bd6a57edcdc54ba76ebe946847450ab0999e6d48ccae332 + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -25083,6 +27164,17 @@ __metadata: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 + languageName: node + linkType: hard + "typed-array-byte-length@npm:^1.0.1": version: 1.0.1 resolution: "typed-array-byte-length@npm:1.0.1" @@ -25236,6 +27328,15 @@ __metadata: languageName: node linkType: hard +"ulid@npm:^2.3.0": + version: 2.4.0 + resolution: "ulid@npm:2.4.0" + bin: + ulid: bin/cli.js + checksum: 10/409ad0a703ea9f25d2a35cf790b767030a2c7b5ca0df6acb73669628156ab63bb97925a3fd7aaaf0b2384d39bd3ed2f7cd2c2de9f1171df6bc79b8c097865d92 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -25526,6 +27627,13 @@ __metadata: languageName: node linkType: hard +"utf8@npm:^3.0.0": + version: 3.0.0 + resolution: "utf8@npm:3.0.0" + checksum: 10/31d19c4faacbb65b09ebc1c21c32b20bdb0919c6f6773cee5001b99bb83f8e503e7233c08fc71ebb34f7cfebd95cec3243b81d90176097aa2f286cccb4ce866e + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -25897,6 +28005,21 @@ __metadata: languageName: node linkType: hard +"which-typed-array@npm:^1.1.16": + version: 1.1.22 + resolution: "which-typed-array@npm:1.1.22" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10/59b0383347e2f3b0bc5be570c2dfae551b172a9c83e0a6b03c6e17401d6161dfa1d912c7657062fe9add254a0d3c25ef70593dbaec8fefa8714715ff69e0a3fc + languageName: node + linkType: hard + "which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" @@ -26060,6 +28183,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/70dfe53f23ff4368d46e4c0b1d4ca734db2c4149c6f68bc62cb16fc21f753c47b35fcc6e582f3bdfba0eaeb1c488cddab3c2255755a5c3eecb251431e42b3ff6 + languageName: node + linkType: hard + "ws@npm:^7.3.1": version: 7.5.10 resolution: "ws@npm:7.5.10"