diff --git a/packages/tron-wallet-snap/CHANGELOG.md b/packages/tron-wallet-snap/CHANGELOG.md index a7812003..d54ba186 100644 --- a/packages/tron-wallet-snap/CHANGELOG.md +++ b/packages/tron-wallet-snap/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add Core messenger plumbing (`getMessenger`, `RemoteFeatureFlagsProvider`, `AssetsProvider`) for upcoming AssetsController migration ([#95](https://github.com/MetaMask/internal-snaps/pull/95)) - Route fungible asset reads through Core AssetsController based on migration stage ([#127](https://github.com/MetaMask/internal-snaps/pull/127)) +### Removed + +- Assets migration feature-flag routing. Fungible reads (`getAccountAssetByID`, `getAccountAssetsByIDs`, `getAccountAssetsByScope`) now always use Core `AssetsController` via `AssetsProvider`; snap-owned protocol assets remain on the Snap adapter for sync, reads, and keyring events. Removed `RemoteFeatureFlagController:getState` messenger endowment ([#97](https://github.com/MetaMask/internal-snaps/pull/97)) + ## [3.0.0] ### Added diff --git a/packages/tron-wallet-snap/package.json b/packages/tron-wallet-snap/package.json index cc67ce03..419fd07d 100644 --- a/packages/tron-wallet-snap/package.json +++ b/packages/tron-wallet-snap/package.json @@ -57,7 +57,6 @@ "@metamask/keyring-api": "^23.7.0", "@metamask/keyring-snap-sdk": "^9.2.1", "@metamask/messenger": "^2.0.0", - "@metamask/remote-feature-flag-controller": "^5.0.0", "@metamask/snap-networks-utils": "^1.0.0", "@metamask/snaps-cli": "^8.4.1", "@metamask/snaps-jest": "^10.2.0", diff --git a/packages/tron-wallet-snap/snap.manifest.json b/packages/tron-wallet-snap/snap.manifest.json index 5edfb815..1580ffb3 100644 --- a/packages/tron-wallet-snap/snap.manifest.json +++ b/packages/tron-wallet-snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/internal-snaps.git" }, "source": { - "shasum": "7nTrlZaA1AUK1SwS66buFFwKxs4nZJG4xWzj49NPkIk=", + "shasum": "6dgldMOKKiG/d+FsXzdI0Smd3MfJn7MCqhykDVXeRf8=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -68,8 +68,7 @@ "actions": [ "AssetsController:getAccountAssetByID", "AssetsController:getAccountAssetsByIDs", - "AssetsController:getAccountAssetsByScope", - "RemoteFeatureFlagController:getState" + "AssetsController:getAccountAssetsByScope" ] } }, diff --git a/packages/tron-wallet-snap/src/context.ts b/packages/tron-wallet-snap/src/context.ts index 0a983ed3..b6d738a1 100644 --- a/packages/tron-wallet-snap/src/context.ts +++ b/packages/tron-wallet-snap/src/context.ts @@ -1,11 +1,5 @@ -import { - AssetsProvider, - RemoteFeatureFlagsProvider, -} from '@metamask/snap-networks-utils'; -import type { - AssetsProviderMessenger, - RemoteFeatureFlagsProviderMessenger, -} from '@metamask/snap-networks-utils'; +import { AssetsProvider } from '@metamask/snap-networks-utils'; +import type { AssetsProviderMessenger } from '@metamask/snap-networks-utils'; import { getMessenger } from '@metamask/snaps-sdk'; import { InMemoryCache } from './caching/InMemoryCache'; @@ -100,9 +94,6 @@ const tokenApiClient = new TokenApiClient(configProvider); * Core controllers plumbing */ const coreMessenger = getMessenger(); -const remoteFeatureFlagsProvider = new RemoteFeatureFlagsProvider({ - messenger: coreMessenger as RemoteFeatureFlagsProviderMessenger, -}); const assetsProvider = new AssetsProvider({ messenger: coreMessenger as AssetsProviderMessenger, }); @@ -123,7 +114,6 @@ const assetsService = new AssetsService({ priceApiClient, tokenApiClient, snapClient, - coreMessenger, assetsProvider, }); @@ -263,10 +253,9 @@ export type SnapExecutionContext = { transactionScanService: TransactionScanService; transactionExpirationRefresherService: TransactionExpirationRefresherService; /** - * Core messenger plumbing (routing wired in a follow-up PR). + * Core messenger plumbing for AssetsController reads. */ coreMessenger: CoreMessenger; - remoteFeatureFlagsProvider: RemoteFeatureFlagsProvider; assetsProvider: AssetsProvider; /** * Handlers @@ -301,7 +290,6 @@ const snapContext: SnapExecutionContext = { transactionScanService, transactionExpirationRefresherService, coreMessenger, - remoteFeatureFlagsProvider, assetsProvider, /** * Handlers diff --git a/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts b/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts index 30e208b2..91cc2666 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts @@ -1,8 +1,4 @@ import type { Asset, Caip19AssetId } from '@metamask/assets-controller'; -import { - SNAPS_ASSETS_MIGRATION_FLAG_KEYS, - SnapsAssetsMigrationStage, -} from '@metamask/assets-controller'; import type { KeyringAccount } from '@metamask/keyring-api'; import { KeyringEvent } from '@metamask/keyring-api'; import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; @@ -59,18 +55,13 @@ jest.mock('@metamask/keyring-snap-sdk', () => ({ // eslint-disable-next-line @typescript-eslint/no-require-imports const { AssetsService } = require('./AssetsService'); -const TRON_FLAG_KEY = SNAPS_ASSETS_MIGRATION_FLAG_KEYS.tron; - function createMessengerCallMock( - getState: () => unknown, getAccountAssetByID: jest.Mock, getAccountAssetsByIDs: jest.Mock = jest.fn().mockResolvedValue({}), getAccountAssetsByScope: jest.Mock = jest.fn().mockResolvedValue({}), ): CoreMessengerCaller['call'] { return async (actionType, ...args) => { switch (actionType) { - case 'RemoteFeatureFlagController:getState': - return getState() as Awaited>; case 'AssetsController:getAccountAssetByID': return getAccountAssetByID(...args); case 'AssetsController:getAccountAssetsByIDs': @@ -83,23 +74,6 @@ function createMessengerCallMock( }; } -function restoreMigrationStageEnv( - originalEnvironment: string | undefined, - originalStage: string | undefined, -): void { - /* eslint-disable n/no-process-env */ - if (originalEnvironment === undefined) { - delete process.env.ENVIRONMENT; - } else { - process.env.ENVIRONMENT = originalEnvironment; - } - delete process.env.TRON_ASSETS_MIGRATION_STAGE; - if (originalStage !== undefined) { - process.env.TRON_ASSETS_MIGRATION_STAGE = originalStage; - } - /* eslint-enable n/no-process-env */ -} - function buildControllerAsset( assetId: string, amount: string, @@ -263,7 +237,6 @@ type WithAssetsServiceCallback = (payload: { mockTokenApiClient: jest.Mocked>; mockSnapClient: jest.Mocked>; mockCoreMessenger: jest.Mocked; - setMigrationStage: (stage: SnapsAssetsMigrationStage) => void; }) => Promise | ReturnValue; /** @@ -339,24 +312,16 @@ async function withAssetsService( const mockGetAccountAssetByID = jest.fn(); const mockGetAccountAssetsByIDs = jest.fn().mockResolvedValue({}); const mockGetAccountAssetsByScope = jest.fn().mockResolvedValue({}); - let migrationStage = SnapsAssetsMigrationStage.Off; const mockCoreMessenger: jest.Mocked = { - call: jest.fn().mockImplementation( - createMessengerCallMock( - () => ({ - remoteFeatureFlags: { - [TRON_FLAG_KEY]: { stage: migrationStage }, - }, - }), - mockGetAccountAssetByID, - mockGetAccountAssetsByIDs, - mockGetAccountAssetsByScope, + call: jest + .fn() + .mockImplementation( + createMessengerCallMock( + mockGetAccountAssetByID, + mockGetAccountAssetsByIDs, + mockGetAccountAssetsByScope, + ), ), - ), - }; - - const setMigrationStage = (stage: SnapsAssetsMigrationStage): void => { - migrationStage = stage; }; const assetsProvider = new AssetsProvider({ @@ -372,7 +337,6 @@ async function withAssetsService( priceApiClient: mockPriceApiClient, tokenApiClient: mockTokenApiClient, snapClient: mockSnapClient, - coreMessenger: mockCoreMessenger, assetsProvider, }); @@ -386,80 +350,19 @@ async function withAssetsService( mockTokenApiClient, mockSnapClient, mockCoreMessenger, - setMigrationStage, }); } describe('AssetsService', () => { describe('fetchAssetsAndBalancesForAccount', () => { describe('inactive account fallback', () => { - it('falls back to TRC20 balance endpoint when account info fails (inactive account)', async () => { - await withAssetsService( - async ({ - assetsService, - mockTrongridApiClient, - mockTronHttpClient, - mockPriceApiClient, - setMigrationStage, - }) => { - setMigrationStage(SnapsAssetsMigrationStage.Off); - mockTrongridApiClient.getAccountInfoByAddress.mockRejectedValue( - new TrongridAccountNotFoundError(), - ); - mockTronHttpClient.getAccountResources.mockResolvedValue( - emptyAccountResources, - ); - - const trc20Balances = [ - { TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t: '24249143' }, - ]; - mockTrongridApiClient.getTrc20BalancesByAddress.mockResolvedValue( - trc20Balances, - ); - - const trc20AssetId = `${String(Network.Mainnet)}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue( - createSpotPrices({ - [trc20AssetId]: { id: trc20AssetId, price: 1.0 }, - }), - ); - - const assets = await assetsService.fetchAssetsAndBalancesForAccount( - Network.Mainnet, - mockAccount, - ); - - expect( - mockTrongridApiClient.getTrc20BalancesByAddress, - ).toHaveBeenCalledWith(Network.Mainnet, mockAccount.address); - - const trxAsset = assets.find( - (asset: AssetEntity) => - asset.assetType === KnownCaip19Id.TrxMainnet, - ); - expect(trxAsset).toBeDefined(); - expect(trxAsset?.rawAmount).toBe('0'); - - const trc20Asset = assets.find( - (asset: AssetEntity) => asset.assetType === trc20AssetId, - ); - expect(trc20Asset).toBeDefined(); - expect(trc20Asset?.rawAmount).toBe('24249143'); - }, - ); - }); - - it('skips TRC20 fallback and returns protocol assets only when mode is controller', async () => { + it('skips TRC20 fallback and returns protocol assets only for inactive accounts', async () => { await withAssetsService( async ({ assetsService, mockTrongridApiClient, mockTronHttpClient, - setMigrationStage, }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); mockTrongridApiClient.getAccountInfoByAddress.mockRejectedValue( new TrongridAccountNotFoundError(), ); @@ -496,11 +399,7 @@ describe('AssetsService', () => { assetsService, mockTrongridApiClient, mockTronHttpClient, - setMigrationStage, }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); mockTrongridApiClient.getAccountInfoByAddress.mockRejectedValue( new TrongridAccountNotFoundError(), ); @@ -537,11 +436,7 @@ describe('AssetsService', () => { assetsService, mockTrongridApiClient, mockTronHttpClient, - setMigrationStage, }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); mockTrongridApiClient.getAccountInfoByAddress.mockRejectedValue( new TrongridAccountNotFoundError(), ); @@ -572,11 +467,7 @@ describe('AssetsService', () => { assetsService, mockTrongridApiClient, mockTronHttpClient, - setMigrationStage, }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); mockTrongridApiClient.getAccountInfoByAddress.mockRejectedValue( new TrongridAccountNotFoundError(), ); @@ -617,11 +508,7 @@ describe('AssetsService', () => { assetsService, mockTrongridApiClient, mockTronHttpClient, - setMigrationStage, }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); mockTrongridApiClient.getAccountInfoByAddress.mockResolvedValue( createMockTronAccount({ address: mockAccount.address, @@ -1534,7 +1421,12 @@ describe('AssetsService', () => { await assetsService.saveMany(assets); - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith(assets); + const snapOwnedAssets = assets.filter((asset) => + SNAP_OWNED_ASSETS.includes(asset.assetType), + ); + expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( + snapOwnedAssets, + ); expect(emitSnapKeyringEvent).toHaveBeenCalledWith( expect.anything(), KeyringEvent.AccountAssetListUpdated, @@ -1554,61 +1446,13 @@ describe('AssetsService', () => { ); }); - it('correctly updates non-essential assets with zero amounts', async () => { - await withAssetsService(async ({ assetsService, mockState }) => { - const trc20AssetId = `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; - const assets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: trc20AssetId, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '0', - uiAmount: '0', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: assets, - }); - - await assetsService.saveMany(assets); - - expect(await assetsService.getAll()).toStrictEqual(assets); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: [KnownCaip19Id.TrxMainnet], - removed: [trc20AssetId], - }, - }, - }, - ); - }); - }); - - it('updates stale non-essential assets balance to 0 if missed from the latest snapshot', async () => { + it('does not persist or emit fungible assets', async () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { const trc20AssetId = `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; - const savedAssets: AssetEntity[] = [ + const assets: AssetEntity[] = [ { - assetType: KnownCaip19Id.TrxMainnet as NativeCaipAssetType, + assetType: KnownCaip19Id.TrxMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, symbol: 'TRX', @@ -1618,17 +1462,38 @@ describe('AssetsService', () => { iconUrl: '', }, { - assetType: trc20AssetId as TokenCaipAssetType, + assetType: trc20AssetId, keyringAccountId: mockAccount.id, network: Network.Mainnet, symbol: 'USDT', decimals: 6, - rawAmount: '1658250000', - uiAmount: '1658.25', + rawAmount: '0', + uiAmount: '0', iconUrl: '', }, ]; - const finalSavedAssets: AssetEntity[] = [ + + mockState.getKey.mockResolvedValue({ + [mockAccount.id]: assets, + }); + + await assetsService.saveMany(assets); + + expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith([]); + expect(emitSnapKeyringEvent).not.toHaveBeenCalledWith( + expect.anything(), + KeyringEvent.AccountAssetListUpdated, + expect.anything(), + ); + }, + ); + }); + + it('does not zero or remove TRC20 when missing from snap-owned sync snapshot', async () => { + await withAssetsService( + async ({ assetsService, mockState, mockAssetsRepository }) => { + const trc20AssetId = `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; + const savedAssets: AssetEntity[] = [ { assetType: KnownCaip19Id.TrxMainnet as NativeCaipAssetType, keyringAccountId: mockAccount.id, @@ -1645,54 +1510,43 @@ describe('AssetsService', () => { network: Network.Mainnet, symbol: 'USDT', decimals: 6, - rawAmount: '0', - uiAmount: '0', + rawAmount: '1658250000', + uiAmount: '1658.25', + iconUrl: '', + }, + ]; + const incomingSnapOwned: AssetEntity[] = [ + { + assetType: KnownCaip19Id.EnergyMainnet, + keyringAccountId: mockAccount.id, + network: Network.Mainnet, + symbol: 'ENERGY', + decimals: 0, + rawAmount: '100', + uiAmount: '100', iconUrl: '', }, ]; - - const updatedAssets: AssetEntity[] = [savedAssets[0] as AssetEntity]; mockState.getKey.mockResolvedValue({ [mockAccount.id]: savedAssets, }); - await assetsService.saveMany(updatedAssets); + await assetsService.saveMany(incomingSnapOwned); expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - finalSavedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: [KnownCaip19Id.TrxMainnet], - removed: [trc20AssetId], - }, - }, - }, - ); - - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountBalancesUpdated, - { - balances: { - [mockAccount.id]: { - [KnownCaip19Id.TrxMainnet]: { - unit: 'TRX', - amount: '1', - }, - [trc20AssetId]: { - unit: 'USDT', - amount: '0', - }, - }, - }, - }, + incomingSnapOwned, ); + const persistedAssets = + mockAssetsRepository.saveMany.mock.calls[0]?.[0] ?? []; + expect( + persistedAssets.find((asset) => asset.assetType === trc20AssetId), + ).toBeUndefined(); + expect( + persistedAssets.find( + (asset) => asset.assetType === KnownCaip19Id.TrxMainnet, + ), + ).toBeUndefined(); }, ); }); @@ -1715,7 +1569,7 @@ describe('AssetsService', () => { assetType: KnownCaip19Id.MaximumEnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'MAX-ENERGY', + symbol: 'MAXIMUM-ENERGY', decimals: 0, rawAmount: '0', uiAmount: '0', @@ -1725,7 +1579,7 @@ describe('AssetsService', () => { assetType: KnownCaip19Id.MaximumBandwidthMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'MAX-BANDWIDTH', + symbol: 'MAXIMUM-BANDWIDTH', decimals: 0, rawAmount: '0', uiAmount: '0', @@ -1737,7 +1591,11 @@ describe('AssetsService', () => { await assetsService.saveMany(assets); - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith(assets); + expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( + assets.filter((asset) => + SNAP_OWNED_ASSETS.includes(asset.assetType), + ), + ); expect(emitSnapKeyringEvent).toHaveBeenCalledWith( expect.anything(), KeyringEvent.AccountAssetListUpdated, @@ -1775,7 +1633,7 @@ describe('AssetsService', () => { assetType: KnownCaip19Id.TrxStakedForBandwidthMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'sTRX-BANDWIDTH', + symbol: 'STAKED-BANDWIDTH', decimals: 6, rawAmount: '0', uiAmount: '0', @@ -1785,7 +1643,7 @@ describe('AssetsService', () => { assetType: KnownCaip19Id.TrxStakedForEnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'sTRX-ENERGY', + symbol: 'STAKED-ENERGY', decimals: 6, rawAmount: '0', uiAmount: '0', @@ -1797,7 +1655,11 @@ describe('AssetsService', () => { await assetsService.saveMany(assets); - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith(assets); + expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( + assets.filter((asset) => + SNAP_OWNED_ASSETS.includes(asset.assetType), + ), + ); expect(emitSnapKeyringEvent).toHaveBeenCalledWith( expect.anything(), KeyringEvent.AccountAssetListUpdated, @@ -1821,21 +1683,11 @@ describe('AssetsService', () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { const assets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, { assetType: KnownCaip19Id.TrxReadyForWithdrawalMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'trx-ready-for-withdrawal', + symbol: 'READY-FOR-WITHDRAWAL', decimals: 6, rawAmount: '0', uiAmount: '0', @@ -1854,9 +1706,7 @@ describe('AssetsService', () => { { assets: { [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxReadyForWithdrawalMainnet, - ]), + added: [KnownCaip19Id.TrxReadyForWithdrawalMainnet], removed: [], }, }, @@ -1866,21 +1716,11 @@ describe('AssetsService', () => { ); }); - describe('updating assets from 0 to >0', () => { + describe('updating snap-owned assets', () => { it('adds energy to the asset list when it updates from 0 to >0', async () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, + const previousAssets: AssetEntity[] = [ { assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, @@ -1892,32 +1732,21 @@ describe('AssetsService', () => { iconUrl: '', }, ]; - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, { assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, symbol: 'ENERGY', decimals: 0, - rawAmount: '50000', - uiAmount: '50000', + rawAmount: '100', + uiAmount: '100', iconUrl: '', }, ]; mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, + [mockAccount.id]: previousAssets, }); await assetsService.saveMany(updatedAssets); @@ -1931,14 +1760,26 @@ describe('AssetsService', () => { { assets: { [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.EnergyMainnet, - ]), + added: [KnownCaip19Id.EnergyMainnet], removed: [], }, }, }, ); + expect(emitSnapKeyringEvent).toHaveBeenCalledWith( + expect.anything(), + KeyringEvent.AccountBalancesUpdated, + { + balances: { + [mockAccount.id]: { + [KnownCaip19Id.EnergyMainnet]: { + unit: 'ENERGY', + amount: '100', + }, + }, + }, + }, + ); }, ); }); @@ -1946,17 +1787,7 @@ describe('AssetsService', () => { it('adds bandwidth to the asset list when it updates from 0 to >0', async () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, + const previousAssets: AssetEntity[] = [ { assetType: KnownCaip19Id.BandwidthMainnet, keyringAccountId: mockAccount.id, @@ -1968,32 +1799,21 @@ describe('AssetsService', () => { iconUrl: '', }, ]; - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, { assetType: KnownCaip19Id.BandwidthMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, symbol: 'BANDWIDTH', decimals: 0, - rawAmount: '1500', - uiAmount: '1500', + rawAmount: '600', + uiAmount: '600', iconUrl: '', }, ]; mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, + [mockAccount.id]: previousAssets, }); await assetsService.saveMany(updatedAssets); @@ -2007,9 +1827,7 @@ describe('AssetsService', () => { { assets: { [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.BandwidthMainnet, - ]), + added: [KnownCaip19Id.BandwidthMainnet], removed: [], }, }, @@ -2019,59 +1837,36 @@ describe('AssetsService', () => { ); }); - it('adds TRC20 token to the asset list when it updates from 0 to >0', async () => { + it('updates energy balance when it decreases but remains >0', async () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { - const trc20AssetId = `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; - - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, + const previousAssets: AssetEntity[] = [ { - assetType: trc20AssetId, + assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '0', - uiAmount: '0', + symbol: 'ENERGY', + decimals: 0, + rawAmount: '1000', + uiAmount: '1000', iconUrl: '', }, ]; - const updatedAssets: AssetEntity[] = [ { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: trc20AssetId, + assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '100000000', + symbol: 'ENERGY', + decimals: 0, + rawAmount: '100', uiAmount: '100', iconUrl: '', }, ]; mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, + [mockAccount.id]: previousAssets, }); await assetsService.saveMany(updatedAssets); @@ -2081,15 +1876,14 @@ describe('AssetsService', () => { ); expect(emitSnapKeyringEvent).toHaveBeenCalledWith( expect.anything(), - KeyringEvent.AccountAssetListUpdated, + KeyringEvent.AccountBalancesUpdated, { - assets: { + balances: { [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - trc20AssetId, - ]), - removed: [], + [KnownCaip19Id.EnergyMainnet]: { + unit: 'ENERGY', + amount: '100', + }, }, }, }, @@ -2098,593 +1892,27 @@ describe('AssetsService', () => { ); }); - it('handles multiple assets updating from 0 to >0 simultaneously', async () => { + it('keeps energy in the list when it drops to 0', async () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { - const trc20AssetId = `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; - - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.EnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'ENERGY', - decimals: 0, - rawAmount: '0', - uiAmount: '0', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.BandwidthMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'BANDWIDTH', - decimals: 0, - rawAmount: '0', - uiAmount: '0', - iconUrl: '', - }, - { - assetType: trc20AssetId, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '0', - uiAmount: '0', - iconUrl: '', - }, - ]; - - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.EnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'ENERGY', - decimals: 0, - rawAmount: '50000', - uiAmount: '50000', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.BandwidthMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'BANDWIDTH', - decimals: 0, - rawAmount: '1500', - uiAmount: '1500', - iconUrl: '', - }, - { - assetType: trc20AssetId, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '100000000', - uiAmount: '100', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, - }); - - await assetsService.saveMany(updatedAssets); - - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - updatedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.EnergyMainnet, - KnownCaip19Id.BandwidthMainnet, - ]), - removed: [], - }, - }, - }, - ); - }, - ); - }); - - it('handles staked assets updating from 0 to >0', async () => { - await withAssetsService( - async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '5000000', - uiAmount: '5', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.TrxStakedForEnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'sTRX-ENERGY', - decimals: 6, - rawAmount: '0', - uiAmount: '0', - iconUrl: '', - }, - ]; - - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '2000000', - uiAmount: '2', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.TrxStakedForEnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'sTRX-ENERGY', - decimals: 6, - rawAmount: '3000000', - uiAmount: '3', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, - }); - - await assetsService.saveMany(updatedAssets); - - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - updatedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxStakedForEnergyMainnet, - ]), - removed: [], - }, - }, - }, - ); - }, - ); - }); - }); - - describe('updating assets going down', () => { - it('updates energy balance when it decreases but remains >0', async () => { - await withAssetsService( - async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.EnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'ENERGY', - decimals: 0, - rawAmount: '100000', - uiAmount: '100000', - iconUrl: '', - }, - ]; - - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.EnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'ENERGY', - decimals: 0, - rawAmount: '35000', - uiAmount: '35000', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, - }); - - await assetsService.saveMany(updatedAssets); - - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - updatedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - KnownCaip19Id.EnergyMainnet, - ]), - removed: [], - }, - }, - }, - ); - - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountBalancesUpdated, - { - balances: { - [mockAccount.id]: { - [KnownCaip19Id.TrxMainnet]: { - unit: 'TRX', - amount: '1', - }, - [KnownCaip19Id.EnergyMainnet]: { - unit: 'ENERGY', - amount: '35000', - }, - }, - }, - }, - ); - }, - ); - }); - - it('updates bandwidth balance when it decreases but remains >0', async () => { - await withAssetsService( - async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.BandwidthMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'BANDWIDTH', - decimals: 0, - rawAmount: '5000', - uiAmount: '5000', - iconUrl: '', - }, - ]; - - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.BandwidthMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'BANDWIDTH', - decimals: 0, - rawAmount: '4700', - uiAmount: '4700', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, - }); - - await assetsService.saveMany(updatedAssets); - - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - updatedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - KnownCaip19Id.BandwidthMainnet, - ]), - removed: [], - }, - }, - }, - ); - - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountBalancesUpdated, - { - balances: { - [mockAccount.id]: { - [KnownCaip19Id.TrxMainnet]: { - unit: 'TRX', - amount: '1', - }, - [KnownCaip19Id.BandwidthMainnet]: { - unit: 'BANDWIDTH', - amount: '4700', - }, - }, - }, - }, - ); - }, - ); - }); - - it('updates TRC20 token balance when it decreases but remains >0', async () => { - await withAssetsService( - async ({ assetsService, mockState, mockAssetsRepository }) => { - const trc20AssetId = `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`; - - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: trc20AssetId, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '100000000', - uiAmount: '100', - iconUrl: '', - }, - ]; - - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: trc20AssetId, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'USDT', - decimals: 6, - rawAmount: '50000000', - uiAmount: '50', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, - }); - - await assetsService.saveMany(updatedAssets); - - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - updatedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - trc20AssetId, - ]), - removed: [], - }, - }, - }, - ); - }, - ); - }); - - it('keeps energy in the list when it drops to 0', async () => { - await withAssetsService( - async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.EnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'ENERGY', - decimals: 0, - rawAmount: '50000', - uiAmount: '50000', - iconUrl: '', - }, - ]; - - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.EnergyMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'ENERGY', - decimals: 0, - rawAmount: '0', - uiAmount: '0', - iconUrl: '', - }, - ]; - - mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, - }); - - await assetsService.saveMany(updatedAssets); - - expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( - updatedAssets, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - KnownCaip19Id.EnergyMainnet, - ]), - removed: [], - }, - }, - }, - ); - }, - ); - }); - - it('keeps bandwidth in the list when it drops to 0', async () => { - await withAssetsService( - async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, + const previousAssets: AssetEntity[] = [ { - assetType: KnownCaip19Id.BandwidthMainnet, + assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'BANDWIDTH', + symbol: 'ENERGY', decimals: 0, - rawAmount: '300', - uiAmount: '300', + rawAmount: '100', + uiAmount: '100', iconUrl: '', }, ]; - const updatedAssets: AssetEntity[] = [ { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '1000000', - uiAmount: '1', - iconUrl: '', - }, - { - assetType: KnownCaip19Id.BandwidthMainnet, + assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, - symbol: 'BANDWIDTH', + symbol: 'ENERGY', decimals: 0, rawAmount: '0', uiAmount: '0', @@ -2693,7 +1921,7 @@ describe('AssetsService', () => { ]; mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, + [mockAccount.id]: previousAssets, }); await assetsService.saveMany(updatedAssets); @@ -2707,10 +1935,7 @@ describe('AssetsService', () => { { assets: { [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - KnownCaip19Id.BandwidthMainnet, - ]), + added: [KnownCaip19Id.EnergyMainnet], removed: [], }, }, @@ -2723,25 +1948,15 @@ describe('AssetsService', () => { it('handles both energy and bandwidth fluctuating in a transaction', async () => { await withAssetsService( async ({ assetsService, mockState, mockAssetsRepository }) => { - const savedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '2000000', - uiAmount: '2', - iconUrl: '', - }, + const previousAssets: AssetEntity[] = [ { assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, symbol: 'ENERGY', decimals: 0, - rawAmount: '80000', - uiAmount: '80000', + rawAmount: '1000', + uiAmount: '1000', iconUrl: '', }, { @@ -2750,31 +1965,20 @@ describe('AssetsService', () => { network: Network.Mainnet, symbol: 'BANDWIDTH', decimals: 0, - rawAmount: '1500', - uiAmount: '1500', + rawAmount: '600', + uiAmount: '600', iconUrl: '', }, ]; - const updatedAssets: AssetEntity[] = [ - { - assetType: KnownCaip19Id.TrxMainnet, - keyringAccountId: mockAccount.id, - network: Network.Mainnet, - symbol: 'TRX', - decimals: 6, - rawAmount: '2000000', - uiAmount: '2', - iconUrl: '', - }, { assetType: KnownCaip19Id.EnergyMainnet, keyringAccountId: mockAccount.id, network: Network.Mainnet, symbol: 'ENERGY', decimals: 0, - rawAmount: '45000', - uiAmount: '45000', + rawAmount: '900', + uiAmount: '900', iconUrl: '', }, { @@ -2783,14 +1987,14 @@ describe('AssetsService', () => { network: Network.Mainnet, symbol: 'BANDWIDTH', decimals: 0, - rawAmount: '1235', - uiAmount: '1235', + rawAmount: '500', + uiAmount: '500', iconUrl: '', }, ]; mockState.getKey.mockResolvedValue({ - [mockAccount.id]: savedAssets, + [mockAccount.id]: previousAssets, }); await assetsService.saveMany(updatedAssets); @@ -2798,40 +2002,19 @@ describe('AssetsService', () => { expect(mockAssetsRepository.saveMany).toHaveBeenCalledWith( updatedAssets, ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [mockAccount.id]: { - added: expect.arrayContaining([ - KnownCaip19Id.TrxMainnet, - KnownCaip19Id.EnergyMainnet, - KnownCaip19Id.BandwidthMainnet, - ]), - removed: [], - }, - }, - }, - ); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( expect.anything(), KeyringEvent.AccountBalancesUpdated, { balances: { [mockAccount.id]: { - [KnownCaip19Id.TrxMainnet]: { - unit: 'TRX', - amount: '2', - }, [KnownCaip19Id.EnergyMainnet]: { unit: 'ENERGY', - amount: '45000', + amount: '900', }, [KnownCaip19Id.BandwidthMainnet]: { unit: 'BANDWIDTH', - amount: '1235', + amount: '500', }, }, }, @@ -2887,55 +2070,18 @@ describe('AssetsService', () => { }); }); - describe('assets migration mode', () => { + describe('AssetsController routing', () => { const accountId = mockAccount.id; const fungibleAssetId = KnownCaip19Id.TrxMainnet; const snapAssetId = KnownCaip19Id.EnergyMainnet; - it('fetchAssetsAndBalancesForAccount returns fungibles when mode is snap', async () => { - await withAssetsService( - async ({ - assetsService, - mockTrongridApiClient, - mockTronHttpClient, - setMigrationStage, - }) => { - setMigrationStage(SnapsAssetsMigrationStage.Off); - mockTrongridApiClient.getAccountInfoByAddress.mockResolvedValue( - createMockTronAccount({ - address: mockAccount.address, - balance: 1_000_000, - }), - ); - mockTronHttpClient.getAccountResources.mockResolvedValue( - emptyAccountResources, - ); - - const assets = await assetsService.fetchAssetsAndBalancesForAccount( - Network.Mainnet, - mockAccount, - ); - - expect( - assets.some( - (asset: AssetEntity) => asset.assetType === fungibleAssetId, - ), - ).toBe(true); - }, - ); - }); - - it('fetchAssetsAndBalancesForAccount returns protocol assets only when mode is controller', async () => { + it('fetchAssetsAndBalancesForAccount returns protocol assets only', async () => { await withAssetsService( async ({ assetsService, mockTrongridApiClient, mockTronHttpClient, - setMigrationStage, }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); mockTrongridApiClient.getAccountInfoByAddress.mockResolvedValue( createMockTronAccount({ address: mockAccount.address, @@ -2998,18 +2144,10 @@ describe('AssetsService', () => { ); }); - it('routes fungible reads through AssetsController when mode is controller', async () => { + it('routes fungible reads through AssetsController', async () => { await withAssetsService(async ({ assetsService, mockCoreMessenger }) => { mockCoreMessenger.call.mockImplementation( createMessengerCallMock( - () => ({ - remoteFeatureFlags: { - [TRON_FLAG_KEY]: { - stage: - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - }, - }, - }), jest.fn().mockResolvedValue( buildControllerAsset(fungibleAssetId, '2000000', { symbol: 'TRX', @@ -3040,14 +2178,6 @@ describe('AssetsService', () => { mockCoreMessenger.call.mockImplementation( createMessengerCallMock( - () => ({ - remoteFeatureFlags: { - [TRON_FLAG_KEY]: { - stage: - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - }, - }, - }), jest.fn(), jest.fn().mockImplementation(async () => { return { @@ -3094,9 +2224,9 @@ describe('AssetsService', () => { uiAmount: '100', iconUrl: '', }; - mockAssetsRepository.getByAccountIdAndAssetTypes.mockResolvedValue([ + mockAssetsRepository.getByAccountIdAndAssetType.mockResolvedValue( snapAsset, - ]); + ); const results = await assetsService.getAccountAssetsByIDs(accountId, [ snapAssetId, @@ -3112,122 +2242,62 @@ describe('AssetsService', () => { ); }); - it('getAccountAssetsByIDs routes all asset IDs through AssetsController when mode is controller', async () => { - await withAssetsService(async ({ assetsService, mockCoreMessenger }) => { - mockCoreMessenger.call.mockImplementation( - createMessengerCallMock( - () => ({ - remoteFeatureFlags: { - [TRON_FLAG_KEY]: { - stage: - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - }, - }, - }), - jest.fn(), - jest.fn().mockImplementation(async () => { - return { - [fungibleAssetId as Caip19AssetId]: buildControllerAsset( - fungibleAssetId, - '3000000', - { - symbol: 'TRX', - name: 'TRON', - decimals: 6, - }, - ), - [snapAssetId as Caip19AssetId]: buildControllerAsset( - snapAssetId, - '250', - { - symbol: 'ENERGY', - name: 'Energy', - decimals: 0, - }, - ), - }; - }), - ), - ); - - const results = await assetsService.getAccountAssetsByIDs(accountId, [ - fungibleAssetId, - snapAssetId, - ]); - - expect(results[0]?.rawAmount).toBe('3000000'); - expect(results[1]?.rawAmount).toBe('250'); - expect(mockCoreMessenger.call).toHaveBeenCalledWith( - 'AssetsController:getAccountAssetsByIDs', - accountId, - [fungibleAssetId, snapAssetId], - ); - }); - }); - - it('getByKeyringAccountId reads from AssetsController when mode is controller', async () => { + it('getAccountAssetsByIDs merges snap-owned and fungible reads in request order', async () => { await withAssetsService( - async ({ assetsService, mockCoreMessenger, setMigrationStage }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, + async ({ assetsService, mockAssetsRepository, mockCoreMessenger }) => { + const snapAsset: AssetEntity = { + assetType: snapAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + symbol: 'ENERGY', + decimals: 0, + rawAmount: '250', + uiAmount: '250', + iconUrl: '', + }; + mockAssetsRepository.getByAccountIdAndAssetType.mockResolvedValue( + snapAsset, ); mockCoreMessenger.call.mockImplementation( createMessengerCallMock( - () => ({ - remoteFeatureFlags: { - [TRON_FLAG_KEY]: { - stage: - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - }, - }, - }), - jest.fn(), jest.fn(), - jest.fn().mockResolvedValue({ - [snapAssetId as Caip19AssetId]: buildControllerAsset( - snapAssetId, - '100', - { - symbol: 'ENERGY', - name: 'Energy', - decimals: 0, - }, - ), + jest.fn().mockImplementation(async () => { + return { + [fungibleAssetId as Caip19AssetId]: buildControllerAsset( + fungibleAssetId, + '3000000', + { + symbol: 'TRX', + name: 'TRON', + decimals: 6, + }, + ), + }; }), ), ); - const assets = await assetsService.getByKeyringAccountId(accountId); + const results = await assetsService.getAccountAssetsByIDs(accountId, [ + fungibleAssetId, + snapAssetId, + ]); + expect(results[0]?.rawAmount).toBe('3000000'); + expect(results[1]).toStrictEqual(snapAsset); expect(mockCoreMessenger.call).toHaveBeenCalledWith( - 'AssetsController:getAccountAssetsByScope', + 'AssetsController:getAccountAssetsByIDs', accountId, - Network.Mainnet, + [fungibleAssetId], ); - expect( - assets.some( - (asset: AssetEntity) => asset.assetType === snapAssetId, - ), - ).toBe(true); - expect( - assets.some( - (asset: AssetEntity) => asset.assetType === fungibleAssetId, - ), - ).toBe(false); }, ); }); - it('saveMany emits only snap-owned assets when mode is controller', async () => { + it('getByKeyringAccountId returns snap-owned assets only', async () => { await withAssetsService( - async ({ assetsService, mockState, setMigrationStage }) => { - setMigrationStage( - SnapsAssetsMigrationStage.ReadAssetsControllerWithoutFallback, - ); - mockState.getKey.mockResolvedValue({}); - - const assets: AssetEntity[] = [ + async ({ assetsService, mockAssetsRepository, mockCoreMessenger }) => { + mockAssetsRepository.getByAccountId.mockResolvedValue([ { assetType: fungibleAssetId, keyringAccountId: accountId, @@ -3248,43 +2318,31 @@ describe('AssetsService', () => { uiAmount: '100', iconUrl: '', }, - ]; + ]); - await assetsService.saveMany(assets); + const assets = await assetsService.getByKeyringAccountId(accountId); - expect(emitSnapKeyringEvent).toHaveBeenCalledWith( - expect.anything(), - KeyringEvent.AccountAssetListUpdated, - { - assets: { - [accountId]: { - added: [snapAssetId], - removed: [], - }, - }, - }, - ); + expect(mockCoreMessenger.call).not.toHaveBeenCalled(); + expect( + assets.some( + (asset: AssetEntity) => asset.assetType === fungibleAssetId, + ), + ).toBe(false); + expect( + assets.some( + (asset: AssetEntity) => asset.assetType === snapAssetId, + ), + ).toBe(true); }, ); }); - it('ignores TRON_ASSETS_MIGRATION_STAGE in production', async () => { - /* eslint-disable n/no-process-env */ - const originalEnvironment = process.env.ENVIRONMENT; - const originalStage = process.env.TRON_ASSETS_MIGRATION_STAGE; - process.env.ENVIRONMENT = 'production'; - process.env.TRON_ASSETS_MIGRATION_STAGE = '2'; - /* eslint-enable n/no-process-env */ + it('saveMany emits only snap-owned assets', async () => { + await withAssetsService(async ({ assetsService, mockState }) => { + mockState.getKey.mockResolvedValue({}); - await withAssetsService( - async ({ assetsService, mockAssetsRepository, mockCoreMessenger }) => { - mockCoreMessenger.call.mockImplementation( - createMessengerCallMock( - () => ({ remoteFeatureFlags: {} }), - jest.fn(), - ), - ); - const snapAsset: AssetEntity = { + const assets: AssetEntity[] = [ + { assetType: fungibleAssetId, keyringAccountId: accountId, network: Network.Mainnet, @@ -3293,21 +2351,34 @@ describe('AssetsService', () => { rawAmount: '1000000', uiAmount: '1', iconUrl: '', - }; - mockAssetsRepository.getByAccountIdAndAssetType.mockResolvedValue( - snapAsset, - ); - - const asset = await assetsService.getAccountAssetByID( - accountId, - fungibleAssetId, - ); + }, + { + assetType: snapAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + symbol: 'ENERGY', + decimals: 0, + rawAmount: '100', + uiAmount: '100', + iconUrl: '', + }, + ]; - expect(asset).toStrictEqual(snapAsset); - }, - ); + await assetsService.saveMany(assets); - restoreMigrationStageEnv(originalEnvironment, originalStage); + expect(emitSnapKeyringEvent).toHaveBeenCalledWith( + expect.anything(), + KeyringEvent.AccountAssetListUpdated, + { + assets: { + [accountId]: { + added: [snapAssetId], + removed: [], + }, + }, + }, + ); + }); }); }); diff --git a/packages/tron-wallet-snap/src/services/assets/AssetsService.ts b/packages/tron-wallet-snap/src/services/assets/AssetsService.ts index 3039fa84..34f9f091 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.ts @@ -1,9 +1,3 @@ -import { - SNAPS_ASSETS_MIGRATION_FLAG_KEYS, - SnapsAssetsMigrationStage, - getSnapsAssetsMigrationNamespace, - parseSnapsAssetsMigrationStage, -} from '@metamask/assets-controller'; import type { Caip19AssetId } from '@metamask/assets-controller'; import type { KeyringAccount } from '@metamask/keyring-api'; import type { AssetsProvider } from '@metamask/snap-networks-utils'; @@ -13,8 +7,7 @@ import type { FungibleAssetMarketData, HistoricalPriceIntervals, } from '@metamask/snaps-sdk'; -import type { CaipAssetType, CaipChainId, Json } from '@metamask/utils'; -import { parseCaipAssetType } from '@metamask/utils'; +import type { CaipAssetType } from '@metamask/utils'; import type { PriceApiClient } from '../../clients/price-api/PriceApiClient'; import type { SnapClient } from '../../clients/snap/SnapClient'; @@ -23,26 +16,18 @@ import type { TronHttpClient } from '../../clients/tron-http/TronHttpClient'; import type { TrongridApiClient } from '../../clients/trongrid/TrongridApiClient'; import { Network } from '../../constants'; import type { AssetEntity } from '../../entities/assets'; -import type { CoreMessengerCaller } from '../../types/core-messenger'; import type { ILogger } from '../../utils/logger'; import type { State, UnencryptedStateValue } from '../state/State'; import { SnapAssetsAdapter } from './adapters/SnapAssetsAdapter'; import type { AssetsRepository } from './AssetsRepository'; import { mapControllerAsset } from './mapControllerAsset'; - -/** - * Assets migration stage used when no remote feature flag is set for the chain. - * Change this value to test Stage 0 / 1 / 2 locally. - */ -const ASSETS_MIGRATION_STAGE = SnapsAssetsMigrationStage.Off; +import { isSnapOwnedAsset } from './snapOwnedAssets'; export class AssetsService { readonly #snapAdapter: SnapAssetsAdapter; readonly #assetsProvider: AssetsProvider; - readonly #coreMessenger: CoreMessengerCaller; - readonly cacheTtlsMilliseconds: SnapAssetsAdapter['cacheTtlsMilliseconds']; constructor({ @@ -54,7 +39,6 @@ export class AssetsService { priceApiClient, tokenApiClient, snapClient, - coreMessenger, assetsProvider, }: { logger: ILogger; @@ -65,10 +49,8 @@ export class AssetsService { priceApiClient: PriceApiClient; tokenApiClient: TokenApiClient; snapClient: SnapClient; - coreMessenger: CoreMessengerCaller; assetsProvider: AssetsProvider; }) { - this.#coreMessenger = coreMessenger; this.#assetsProvider = assetsProvider; this.#snapAdapter = new SnapAssetsAdapter({ @@ -80,40 +62,10 @@ export class AssetsService { priceApiClient, tokenApiClient, snapClient, - resolveMigrationStage: ( - chainId: string, - ): Promise => - this.#resolveMigrationStage(chainId), }); this.cacheTtlsMilliseconds = this.#snapAdapter.cacheTtlsMilliseconds; } - 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 (Object.hasOwn(remoteFeatureFlags, flagKey)) { - const remoteStage = parseSnapsAssetsMigrationStage( - remoteFeatureFlags[flagKey] as Json | undefined, - ); - - if (remoteStage !== undefined) { - return remoteStage; - } - } - } - - return ASSETS_MIGRATION_STAGE; - } - static isFiat(caipAssetId: CaipAssetType): boolean { return SnapAssetsAdapter.isFiat(caipAssetId); } @@ -122,17 +74,10 @@ export class AssetsService { return SnapAssetsAdapter.hasChanged(asset, assetsLookup); } - async getAccountAssetByID( + async #getProviderAccountAssetByID( accountId: string, assetId: string, ): Promise { - const { chainId } = parseCaipAssetType(assetId as CaipAssetType); - const stage = await this.#resolveMigrationStage(chainId); - - if (stage === SnapsAssetsMigrationStage.Off) { - return this.#snapAdapter.getAccountAssetByID(accountId, assetId); - } - const asset = await this.#assetsProvider.getAccountAssetByID( accountId, assetId as Caip19AssetId, @@ -145,6 +90,53 @@ export class AssetsService { return mapControllerAsset(accountId, asset); } + async #getProviderAccountAssetsByIDs( + accountId: string, + assetIds: string[], + ): Promise> { + const controllerAssets = await this.#assetsProvider.getAccountAssetsByIDs( + accountId, + assetIds as Caip19AssetId[], + ); + + return Object.fromEntries( + assetIds.map((assetId) => { + const controllerAsset = controllerAssets[assetId as Caip19AssetId]; + return [ + assetId, + controllerAsset + ? mapControllerAsset(accountId, controllerAsset) + : null, + ]; + }), + ); + } + + async #getProviderAccountAssetsByScope( + scope: Network, + accountId: string, + ): Promise { + const controllerAssets = await this.#assetsProvider.getAccountAssetsByScope( + scope, + accountId, + ); + + return Object.values(controllerAssets).map((asset) => + mapControllerAsset(accountId, asset), + ); + } + + async getAccountAssetByID( + accountId: string, + assetId: string, + ): Promise { + if (isSnapOwnedAsset(assetId)) { + return this.#snapAdapter.getAccountAssetByID(accountId, assetId); + } + + return this.#getProviderAccountAssetByID(accountId, assetId); + } + async getAccountAssetsByIDs( accountId: string, assetIds: string[], @@ -153,53 +145,72 @@ export class AssetsService { return []; } - const { chainId } = parseCaipAssetType(assetIds[0] as CaipAssetType); - const stage = await this.#resolveMigrationStage(chainId); - - if (stage === SnapsAssetsMigrationStage.Off) { - const results = await this.#snapAdapter.getAccountAssetsByIDs( - accountId, - assetIds, - ); + const result: (AssetEntity | null)[] = new Array(assetIds.length).fill( + null, + ); + const fungibleIds: string[] = []; + const fungibleIndices: number[] = []; + + for (const [index, assetId] of assetIds.entries()) { + if (isSnapOwnedAsset(assetId)) { + result[index] = await this.#snapAdapter.getAccountAssetByID( + accountId, + assetId, + ); + } else { + fungibleIds.push(assetId); + fungibleIndices.push(index); + } + } - return assetIds.map((assetId) => results[assetId] ?? null); + if (fungibleIds.length === 0) { + return result; } - const controllerAssets = await this.#assetsProvider.getAccountAssetsByIDs( + const fungibleResults = await this.#getProviderAccountAssetsByIDs( accountId, - assetIds as Caip19AssetId[], + fungibleIds, ); - return assetIds.map((assetId) => { - const controllerAsset = controllerAssets[assetId as Caip19AssetId]; - return controllerAsset - ? mapControllerAsset(accountId, controllerAsset) - : null; + fungibleIds.forEach((assetId, fungibleIndex) => { + const resultIndex = fungibleIndices[fungibleIndex]; + if (resultIndex !== undefined) { + result[resultIndex] = fungibleResults[assetId] ?? null; + } }); + + return result; } async getAccountAssetsByScope( scope: Network, accountId: string, ): Promise { - const stage = await this.#resolveMigrationStage(scope); - - if (stage === SnapsAssetsMigrationStage.Off) { - return this.#snapAdapter.getAccountAssetsByScope(scope, accountId); - } - - const controllerAssets = await this.#assetsProvider.getAccountAssetsByScope( + const snapAssets = await this.#snapAdapter.getAccountAssetsByScope( scope, accountId, ); - - return Object.values(controllerAssets).map((asset) => - mapControllerAsset(accountId, asset), + const snapOwnedAssets = snapAssets.filter((asset) => + isSnapOwnedAsset(asset.assetType), ); + const coreAssets = await this.#getProviderAccountAssetsByScope( + scope, + accountId, + ); + + return [ + ...coreAssets.filter((asset) => !isSnapOwnedAsset(asset.assetType)), + ...snapOwnedAssets, + ]; } async getByKeyringAccountId(accountId: string): Promise { - return this.getAccountAssetsByScope(Network.Mainnet, accountId); + const assets = await this.#snapAdapter.getAccountAssetsByScope( + Network.Mainnet, + accountId, + ); + + return assets.filter((asset) => isSnapOwnedAsset(asset.assetType)); } async fetchAssetsAndBalancesForAccount( @@ -253,5 +264,3 @@ export class AssetsService { return this.#snapAdapter.getMultipleTokensMarketData(assets); } } - -export { SnapsAssetsMigrationStage }; diff --git a/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts b/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts index 011fd657..e8d8a889 100644 --- a/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts +++ b/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts @@ -1,4 +1,3 @@ -import { SnapsAssetsMigrationStage } from '@metamask/assets-controller'; import { KeyringEvent } from '@metamask/keyring-api'; import type { AccountAssetListUpdatedEvent, @@ -115,10 +114,6 @@ export class SnapAssetsAdapter { readonly #snapClient: SnapClient; - readonly #resolveMigrationStage: ( - chainId: string, - ) => Promise; - readonly cacheTtlsMilliseconds: { fiatExchangeRates: number; spotPrices: number; @@ -134,7 +129,6 @@ export class SnapAssetsAdapter { priceApiClient, tokenApiClient, snapClient, - resolveMigrationStage, }: { logger: ILogger; assetsRepository: AssetsRepository; @@ -144,9 +138,6 @@ export class SnapAssetsAdapter { priceApiClient: PriceApiClient; tokenApiClient: TokenApiClient; snapClient: SnapClient; - resolveMigrationStage: ( - chainId: string, - ) => Promise; }) { this.#logger = createPrefixedLogger(logger, '[🪙 SnapAssetsAdapter]'); this.#assetsRepository = assetsRepository; @@ -156,7 +147,6 @@ export class SnapAssetsAdapter { this.#priceApiClient = priceApiClient; this.#tokenApiClient = tokenApiClient; this.#snapClient = snapClient; - this.#resolveMigrationStage = resolveMigrationStage; const { cacheTtlsMilliseconds } = configProvider.get().priceApi; this.cacheTtlsMilliseconds = cacheTtlsMilliseconds; @@ -166,10 +156,6 @@ export class SnapAssetsAdapter { return caipAssetId.includes('swift:0/iso4217:'); } - async #resolveStage(chainId: string): Promise { - return this.#resolveMigrationStage(chainId); - } - async getAccountAssetByID( accountId: string, assetId: string, @@ -195,36 +181,30 @@ export class SnapAssetsAdapter { } /** - * Fetches all assets and balances for an account. + * Fetches snap-owned protocol assets and balances for an account. + * + * Fungible balances (TRX, TRC10, TRC20) are owned by Core AssetsController. + * This method only syncs Snap-managed protocol assets: energy, bandwidth, + * staking positions, lock/withdrawal, and rewards. * * Data Sources: - * - `getAccountInfoByAddress`: TRX balance, TRC10 tokens, TRC20 tokens (active accounts only) + * - `getAccountInfoByAddress`: Staking data (active accounts only) * - `getAccountResources`: Energy and Bandwidth (returns {} for inactive accounts) - * - `getTrc20BalancesByAddress`: TRC20 balances fallback (works for inactive accounts) - * - * Logic Flow: - * 1. Fetch account info, resources, and TRC20 fallback (for inactive accounts) - * 2. Normalize data into consistent shape via `#buildAccountData` - * 3. Extract all assets via `#extractAssets` - * 4. Fetch metadata and prices in parallel - * 5. Enrich assets with metadata via `#enrichAssetsWithMetadata` - * 6. Filter spam tokens via `#filterTokensWithoutPriceData` + * - `getReward`: Unclaimed staking rewards * * @param scope - The network to query. * @param account - The keyring account. - * @returns Promise - Array of assets with balances. + * @returns Promise - Array of snap-owned assets with balances. */ async fetchAssetsAndBalancesForAccount( scope: Network, account: KeyringAccount, ): Promise { - this.#logger.info('Fetching assets and balances by account', { + this.#logger.info('Fetching snap-owned assets and balances by account', { account, scope, }); - const stage = await this.#resolveStage(scope); - const [ tronAccountInfoRequest, tronAccountResourcesRequest, @@ -235,40 +215,21 @@ export class SnapAssetsAdapter { this.#tronHttpClient.getReward(scope, account.address), ]); - const isInactiveAccount = tronAccountInfoRequest.status === 'rejected'; - if (isInactiveAccount) { + if (tronAccountInfoRequest.status === 'rejected') { this.#logger.info( 'Account info request failed, treating as inactive account', { account, scope }, ); } - const trc20BalancesFallback = - stage === SnapsAssetsMigrationStage.Off && isInactiveAccount - ? await this.#trongridApiClient - .getTrc20BalancesByAddress(scope, account.address) - .catch(async (error) => { - await this.#snapClient.trackError(error as Error); - this.#logger.warn( - 'Failed to fetch TRC20 balances for inactive account', - { error, account, scope }, - ); - return []; - }) - : []; - const accountData = this.#buildAccountData({ tronAccountInfoRequest, tronAccountResourcesRequest, - trc20BalancesFallback, + trc20BalancesFallback: [], stakingRewardsRequest, }); - const rawAssets = - stage === SnapsAssetsMigrationStage.Off - ? this.#extractAssets(account, scope, accountData) - : this.#extractSnapOwnedAssets(account, scope, accountData); - + const rawAssets = this.#extractSnapOwnedAssets(account, scope, accountData); const assetTypes = rawAssets.map((asset) => asset.assetType); const priceableAssetTypes = this.#getPriceableAssetTypes(rawAssets); @@ -376,28 +337,6 @@ export class SnapAssetsAdapter { }; } - /** - * Extracts all assets from normalized account data. - * Coordinates calls to individual extraction functions. - * - * @param account - The keyring account. - * @param scope - The network. - * @param data - Normalized account data. - * @returns AssetEntity[] - Array of all extracted assets. - */ - #extractAssets( - account: KeyringAccount, - scope: Network, - data: NormalizedAccountData, - ): AssetEntity[] { - return [ - this.#extractNativeAsset(account, scope, data.nativeBalance), - ...this.#extractSnapOwnedAssets(account, scope, data), - ...this.#extractTrc10Assets(account, scope, data.trc10Balances), - ...this.#extractTrc20Assets(account, scope, data.trc20Balances), - ]; - } - #extractSnapOwnedAssets( account: KeyringAccount, scope: Network, @@ -487,34 +426,6 @@ export class SnapAssetsAdapter { }); } - /** - * Extracts the native TRX asset from the balance. - * - * @param account - The keyring account. - * @param scope - The network. - * @param balance - The native balance in sun. - * @returns AssetEntity - The native TRX asset. - */ - #extractNativeAsset( - account: KeyringAccount, - scope: Network, - balance: number, - ): AssetEntity { - return { - assetType: Networks[scope].nativeToken.id, - keyringAccountId: account.id, - network: scope, - symbol: Networks[scope].nativeToken.symbol, - decimals: Networks[scope].nativeToken.decimals, - rawAmount: balance.toString(), - uiAmount: toUiAmount( - balance, - Networks[scope].nativeToken.decimals, - ).toString(), - iconUrl: Networks[scope].nativeToken.iconUrl, - }; - } - /** * Extracts staked TRX assets (for bandwidth and energy). * @@ -794,66 +705,6 @@ export class SnapAssetsAdapter { ]; } - /** - * Extracts TRC10 assets from the balances array. - * - * @param account - The keyring account. - * @param scope - The network. - * @param trc10Balances - TRC10 token balances as `{ key: tokenId, value: balance }[]`. - * @returns AssetEntity[] - Array of TRC10 asset entities. - */ - #extractTrc10Assets( - account: KeyringAccount, - scope: Network, - trc10Balances: TronAccount['assetV2'], - ): AssetEntity[] { - return ( - trc10Balances?.flatMap((tokenObject) => { - // assetV2 has structure: { "key": "token_id", "value": "balance" } - return { - assetType: `${scope}/trc10:${tokenObject.key}` as TokenCaipAssetType, - keyringAccountId: account.id, - network: scope, - symbol: '', - decimals: 0, - rawAmount: tokenObject.value?.toString() ?? '0', - uiAmount: '0', - iconUrl: '', // Will be enriched with metadata later - }; - }) ?? [] - ); - } - - /** - * Extracts TRC20 assets from a balances array. - * Works with both active accounts (tronAccountInfo.trc20) and inactive accounts (getTrc20BalancesByAddress). - * - * @param account - The keyring account. - * @param scope - The network. - * @param trc20Balances - Array of `Record` objects (e.g., `[{ "TContractAddr": "1000" }]`). - * @returns AssetEntity[] - Array of TRC20 asset entities. - */ - #extractTrc20Assets( - account: KeyringAccount, - scope: Network, - trc20Balances: Trc20Balance[], - ): AssetEntity[] { - return trc20Balances.flatMap((tokenObject) => { - return Object.entries(tokenObject).map(([address, balance]) => { - return { - assetType: `${scope}/trc20:${address}` as TokenCaipAssetType, - keyringAccountId: account.id, - network: scope, - symbol: '', - decimals: 0, - rawAmount: balance, - uiAmount: '0', - iconUrl: '', // Will be enriched with metadata later - }; - }); - }); - } - async getAssetsMetadata( assetTypes: CaipAssetType[], ): Promise> { @@ -1292,33 +1143,14 @@ export class SnapAssetsAdapter { async saveMany(assets: AssetEntity[]): Promise { this.#logger.info('Saving assets', assets); - const stagesByNetwork = new Map(); - await Promise.all( - [...new Set(assets.map((asset) => asset.network))].map( - async (network) => { - stagesByNetwork.set(network, await this.#resolveStage(network)); - }, - ), - ); - + // Core owns fungibles; only persist/emit snap-owned protocol assets. const shouldEmitAsset = (asset: AssetEntity): boolean => - (stagesByNetwork.get(asset.network) ?? SnapsAssetsMigrationStage.Off) === - SnapsAssetsMigrationStage.Off || isSnapOwnedAsset(asset.assetType); + isSnapOwnedAsset(asset.assetType); const hasZeroAmount = (asset: AssetEntity): boolean => asset.rawAmount === '0' || asset.uiAmount === '0'; const savedAssets = await this.getAll(); - const isEssentialAsset = (asset: AssetEntity): boolean => - ESSENTIAL_ASSETS.includes(asset.assetType); - - const isProtectedAsset = (asset: AssetEntity): boolean => { - const stage = - stagesByNetwork.get(asset.network) ?? SnapsAssetsMigrationStage.Off; - return stage === SnapsAssetsMigrationStage.Off - ? isEssentialAsset(asset) - : isSnapOwnedAsset(asset.assetType); - }; // Track only the account/network pairs refreshed in this run. // That prevents us from treating assets from untouched networks as disappeared. @@ -1335,27 +1167,18 @@ export class SnapAssetsAdapter { assets.map((asset) => `${asset.keyringAccountId}:${asset.assetType}`), ); - // A saved asset is considered disappeared only if its network was part of - // this sync, it is not essential, and it is missing from the latest - // snapshot for that account. + // A saved snap-owned asset is considered disappeared only if its network was + // part of this sync and it is missing from the latest snapshot. Fungibles are + // ignored because Core owns them. const disappearedAssets = savedAssets.filter((savedAsset) => { const syncedNetworks = syncedNetworksByAccount[savedAsset.keyringAccountId]; - if ( - !syncedNetworks?.has(savedAsset.network) || - isProtectedAsset(savedAsset) - ) { + if (!syncedNetworks?.has(savedAsset.network)) { return false; } - const stage = - stagesByNetwork.get(savedAsset.network) ?? - SnapsAssetsMigrationStage.Off; - if ( - stage !== SnapsAssetsMigrationStage.Off && - !isSnapOwnedAsset(savedAsset.assetType) - ) { + if (!isSnapOwnedAsset(savedAsset.assetType)) { return false; } @@ -1365,10 +1188,10 @@ export class SnapAssetsAdapter { }); // A token should be removed from the visible asset list only when the latest - // snapshot says its balance is zero. Essential assets stay visible even at - // zero because they are part of the permanent Tron account model. + // snapshot says its balance is zero. Snap-owned protocol assets stay visible + // even at zero because they are part of the permanent Tron account model. const shouldBeInRemovedList = (asset: AssetEntity): boolean => - hasZeroAmount(asset) && !isEssentialAsset(asset); // Never remove essential assets (including energy & bandwidth) from the account asset list + hasZeroAmount(asset) && !isSnapOwnedAsset(asset.assetType); // Assets are added to the visible list when they are non-zero and either: // - we are doing a full non-incremental broadcast, or @@ -1437,7 +1260,10 @@ export class SnapAssetsAdapter { uiAmount: '0', })); - const assetsToSave = [...assets, ...removedAssetsWithZeroBalance]; + const assetsToSave = [ + ...assets.filter(shouldEmitAsset), + ...removedAssetsWithZeroBalance, + ]; // Save assets using repository await this.#assetsRepository.saveMany(assetsToSave); diff --git a/packages/tron-wallet-snap/src/types/core-messenger.ts b/packages/tron-wallet-snap/src/types/core-messenger.ts index 62ac03e2..258d0752 100644 --- a/packages/tron-wallet-snap/src/types/core-messenger.ts +++ b/packages/tron-wallet-snap/src/types/core-messenger.ts @@ -4,7 +4,6 @@ import type { AssetsControllerGetAccountAssetsByScopeAction, } from '@metamask/assets-controller'; import type { Messenger } from '@metamask/messenger'; -import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; import type { AsyncMessenger } from '@metamask/snaps-sdk'; /** @@ -13,7 +12,6 @@ import type { AsyncMessenger } from '@metamask/snaps-sdk'; export const TRON_WALLET_SNAP_MESSENGER_NAMESPACE = 'TronWalletSnap' as const; export type CoreMessengerActions = - | RemoteFeatureFlagControllerGetStateAction | AssetsControllerGetAccountAssetByIDAction | AssetsControllerGetAccountAssetsByIDsAction | AssetsControllerGetAccountAssetsByScopeAction; diff --git a/yarn.lock b/yarn.lock index fd2359b2..8a6f1fd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3786,7 +3786,6 @@ __metadata: "@metamask/keyring-api": "npm:^23.7.0" "@metamask/keyring-snap-sdk": "npm:^9.2.1" "@metamask/messenger": "npm:^2.0.0" - "@metamask/remote-feature-flag-controller": "npm:^5.0.0" "@metamask/snap-networks-utils": "npm:^1.0.0" "@metamask/snaps-cli": "npm:^8.4.1" "@metamask/snaps-jest": "npm:^10.2.0"