From 11e16e2763d1188d31a3ec7689a0e35a3803187a Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 15:11:49 +0100 Subject: [PATCH 1/7] feat(tron-wallet-snap): add CoreAssetsAdapter and mapControllerAsset Introduce CoreAssetsAdapter with AssetsController reads and snap-owned fetch/publish helpers. Wire it into AssetsService unused pending routing. --- eslint-suppressions.json | 102 +------ packages/tron-wallet-snap/CHANGELOG.md | 4 + packages/tron-wallet-snap/package.json | 1 + packages/tron-wallet-snap/src/context.ts | 8 + .../src/services/assets/AssetsService.test.ts | 14 +- .../src/services/assets/AssetsService.ts | 18 +- .../assets/adapters/CoreAssetsAdapter.ts | 248 ++++++++++++++++++ .../assets/utils/mapControllerAsset.test.ts | 61 +++++ .../assets/utils/mapControllerAsset.ts | 34 +++ yarn.lock | 1 + 10 files changed, 386 insertions(+), 105 deletions(-) create mode 100644 packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts create mode 100644 packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.test.ts create mode 100644 packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 65126719..279fd759 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1338,15 +1338,7 @@ "count": 20 } }, - "packages/tron-wallet-snap/src/caching/InMemoryCache.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/caching/StateCache.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - }, "no-restricted-syntax": { "count": 1 } @@ -1361,20 +1353,7 @@ "count": 2 } }, - "packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, - "packages/tron-wallet-snap/src/clients/price-api/types.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/clients/security-alerts-api/utils.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - }, "no-restricted-syntax": { "count": 5 } @@ -1387,11 +1366,6 @@ "count": 1 } }, - "packages/tron-wallet-snap/src/clients/token-api/TokenApiClient.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/clients/trongrid/TrongridApiClient.test.ts": { "require-atomic-updates": { "count": 1 @@ -1410,16 +1384,6 @@ "count": 42 } }, - "packages/tron-wallet-snap/src/handlers/clientRequest/clientRequest.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, - "packages/tron-wallet-snap/src/handlers/clientRequest/validation.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/handlers/cronjob/cronjob.test.tsx": { "@typescript-eslint/explicit-function-return-type": { "count": 2 @@ -1438,11 +1402,6 @@ "count": 1 } }, - "packages/tron-wallet-snap/src/services/accounts/AccountsService.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/services/assets/AssetsRepository.test.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 2 @@ -1450,22 +1409,12 @@ }, "packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts": { "@typescript-eslint/explicit-function-return-type": { - "count": 3 + "count": 2 }, "@typescript-eslint/no-explicit-any": { "count": 1 } }, - "packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, - "packages/tron-wallet-snap/src/services/config/ConfigProvider.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/services/send/FeeCalculatorService.ts": { "no-restricted-syntax": { "count": 1 @@ -1479,11 +1428,6 @@ "count": 15 } }, - "packages/tron-wallet-snap/src/services/send/SendService.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/services/send/TransactionExpirationRefresherService.test.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 5 @@ -1494,11 +1438,6 @@ "count": 7 } }, - "packages/tron-wallet-snap/src/services/staking/StakingService.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/services/state/State.test.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 1 @@ -1507,11 +1446,6 @@ "count": 1 } }, - "packages/tron-wallet-snap/src/services/transaction-expiration-refresher/TransactionExpirationRefresherService.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/services/transaction-scan/TransactionScanService.ts": { "no-restricted-syntax": { "count": 5 @@ -1528,14 +1462,6 @@ }, "@typescript-eslint/no-explicit-any": { "count": 7 - }, - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, - "packages/tron-wallet-snap/src/services/wallet/WalletService.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 } }, "packages/tron-wallet-snap/src/ui/confirmation/components/TransactionAlert/TransactionAlert.test.tsx": { @@ -1554,9 +1480,6 @@ "packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignMessage/render.test.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 - }, - "import-x/no-extraneous-dependencies": { - "count": 1 } }, "packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignTransaction/ConfirmSignTransaction.test.tsx": { @@ -1567,14 +1490,6 @@ "packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignTransaction/render.test.tsx": { "@typescript-eslint/no-explicit-any": { "count": 14 - }, - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, - "packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignTransaction/render.tsx": { - "import-x/no-extraneous-dependencies": { - "count": 1 } }, "packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmTransactionRequest/ConfirmTransactionRequest.test.tsx": { @@ -1590,26 +1505,11 @@ "count": 2 } }, - "packages/tron-wallet-snap/src/utils/deriveTronFromCoinTypeNode.test.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, - "packages/tron-wallet-snap/src/utils/deriveTronFromCoinTypeNode.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/utils/getExplorerUrl.test.ts": { "n/no-process-env": { "count": 3 } }, - "packages/tron-wallet-snap/src/validation/keyring-structs.test.ts": { - "import-x/no-extraneous-dependencies": { - "count": 1 - } - }, "packages/tron-wallet-snap/src/validation/transaction.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/packages/tron-wallet-snap/CHANGELOG.md b/packages/tron-wallet-snap/CHANGELOG.md index a091cac2..a076b594 100644 --- a/packages/tron-wallet-snap/CHANGELOG.md +++ b/packages/tron-wallet-snap/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `CoreAssetsAdapter` and `mapControllerAsset` for AssetsController integration (wired unused until routing lands) + ### Changed - Extract shared asset util functions and inject `SnapAssetsAdapter` from `context` into `AssetsService` ([#143](https://github.com/MetaMask/internal-snaps/pull/143)) diff --git a/packages/tron-wallet-snap/package.json b/packages/tron-wallet-snap/package.json index c0948348..7debfa82 100644 --- a/packages/tron-wallet-snap/package.json +++ b/packages/tron-wallet-snap/package.json @@ -60,6 +60,7 @@ "@metamask/snaps-jest": "^10.2.0", "@metamask/snaps-sdk": "^11.2.0", "@metamask/superstruct": "^3.4.1", + "@metamask/utils": "^11.9.0", "@types/lodash": "^4.17.15", "async-mutex": "^0.5.0", "bignumber.js": "^9.3.1", diff --git a/packages/tron-wallet-snap/src/context.ts b/packages/tron-wallet-snap/src/context.ts index 1a4ee12d..12f19d81 100644 --- a/packages/tron-wallet-snap/src/context.ts +++ b/packages/tron-wallet-snap/src/context.ts @@ -25,6 +25,7 @@ import { RpcHandler } from './handlers/rpc/rpc'; import { UserInputHandler } from './handlers/user-input/userInput'; import { AccountsRepository } from './services/accounts/AccountsRepository'; import { AccountsService } from './services/accounts/AccountsService'; +import { CoreAssetsAdapter } from './services/assets/adapters/CoreAssetsAdapter'; import { SnapAssetsAdapter } from './services/assets/adapters/SnapAssetsAdapter'; import { AssetsRepository } from './services/assets/AssetsRepository'; import { AssetsService } from './services/assets/AssetsService'; @@ -125,10 +126,17 @@ const snapAssetsAdapter = new SnapAssetsAdapter({ snapClient, configProvider, }); +const coreAssetsAdapter = new CoreAssetsAdapter({ + logger, + assetsProvider, + trongridApiClient, + tronHttpClient, +}); // Business Services const assetsService = new AssetsService({ snapAdapter: snapAssetsAdapter, + coreAdapter: coreAssetsAdapter, }); const transactionsService = new TransactionsService({ 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 19f166ed..7f261b99 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts @@ -54,6 +54,8 @@ const { configProvider } = require('../../context'); // eslint-disable-next-line @typescript-eslint/no-require-imports const { AssetsService } = require('./AssetsService'); // eslint-disable-next-line @typescript-eslint/no-require-imports +const { CoreAssetsAdapter } = require('./adapters/CoreAssetsAdapter'); +// eslint-disable-next-line @typescript-eslint/no-require-imports const { SnapAssetsAdapter } = require('./adapters/SnapAssetsAdapter'); const mockAccount: KeyringAccount = { @@ -275,7 +277,17 @@ async function withAssetsService( snapClient: mockSnapClient, configProvider, }); - const assetsService = new AssetsService({ snapAdapter }); + const coreAdapter = new CoreAssetsAdapter({ + logger: mockLogger, + assetsProvider: { + getAccountAssetByID: jest.fn().mockResolvedValue(null), + getAccountAssetsByIDs: jest.fn().mockResolvedValue({}), + getAccountAssetsByScope: jest.fn().mockResolvedValue({}), + }, + trongridApiClient: mockTrongridApiClient, + tronHttpClient: mockTronHttpClient, + }); + const assetsService = new AssetsService({ snapAdapter, coreAdapter }); return await testFunction({ assetsService, diff --git a/packages/tron-wallet-snap/src/services/assets/AssetsService.ts b/packages/tron-wallet-snap/src/services/assets/AssetsService.ts index c1ef4ba3..9af372ec 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.ts @@ -9,20 +9,32 @@ import type { CaipAssetType } from '@metamask/utils'; import type { Network } from '../../constants'; import type { AssetEntity } from '../../entities/assets'; +import type { CoreAssetsAdapter } from './adapters/CoreAssetsAdapter'; import { SnapAssetsAdapter } from './adapters/SnapAssetsAdapter'; /** * Assets domain facade. Currently delegates all behavior to SnapAssetsAdapter - * (legacy snap-owned reads/writes). Core adapter routing can be introduced later - * without changing callers. + * (legacy snap-owned reads/writes). Core adapter is initialized for upcoming + * routing without changing callers. */ export class AssetsService { readonly #snapAdapter: SnapAssetsAdapter; + // Initialized for upcoming Core routing; not read until the migration PR lands. + // eslint-disable-next-line no-unused-private-class-members -- reserved adapter slot + readonly #coreAdapter: CoreAssetsAdapter; + readonly cacheTtlsMilliseconds: SnapAssetsAdapter['cacheTtlsMilliseconds']; - constructor({ snapAdapter }: { snapAdapter: SnapAssetsAdapter }) { + constructor({ + snapAdapter, + coreAdapter, + }: { + snapAdapter: SnapAssetsAdapter; + coreAdapter: CoreAssetsAdapter; + }) { this.#snapAdapter = snapAdapter; + this.#coreAdapter = coreAdapter; this.cacheTtlsMilliseconds = this.#snapAdapter.cacheTtlsMilliseconds; } diff --git a/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts b/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts new file mode 100644 index 00000000..fce4600e --- /dev/null +++ b/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts @@ -0,0 +1,248 @@ +import type { Caip19AssetId } from '@metamask/assets-controller'; +import { KeyringEvent } from '@metamask/keyring-api'; +import type { + AccountAssetListUpdatedEvent, + AccountBalancesUpdatedEvent, + KeyringAccount, +} from '@metamask/keyring-api'; +import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; +import type { AssetsProvider } from '@metamask/snap-networks-utils'; + +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 { createPrefixedLogger } from '../../../utils/logger'; +import type { ILogger } from '../../../utils/logger'; +import { mapControllerAsset } from '../utils/mapControllerAsset'; +import { buildAccountResources } from '../utils/buildAccountResources'; +import { buildStakedData } from '../utils/buildStakedData'; +import { extractBandwidth } from '../utils/extractBandwidth'; +import { extractEnergy } from '../utils/extractEnergy'; +import { extractInLockPeriodAsset } from '../utils/extractInLockPeriodAsset'; +import { extractReadyForWithdrawalAsset } from '../utils/extractReadyForWithdrawalAsset'; +import { extractStakedNativeAssets } from '../utils/extractStakedNativeAssets'; +import { extractStakingRewardsAsset } from '../utils/extractStakingRewardsAsset'; +import { isSnapOwnedAsset } from '../utils/isSnapOwnedAsset'; + +/** + * Uses the AssetsController for fungible reads. Snap-owned (special) assets are + * published via keyring events without local persistence when migration is active. + */ +export class CoreAssetsAdapter { + readonly #logger: ILogger; + + readonly #assetsProvider: AssetsProvider; + + readonly #trongridApiClient: TrongridApiClient; + + readonly #tronHttpClient: TronHttpClient; + + constructor({ + logger, + assetsProvider, + trongridApiClient, + tronHttpClient, + }: { + logger: ILogger; + assetsProvider: AssetsProvider; + trongridApiClient: TrongridApiClient; + tronHttpClient: TronHttpClient; + }) { + this.#logger = createPrefixedLogger(logger, '[CoreAssetsAdapter]'); + this.#assetsProvider = assetsProvider; + this.#trongridApiClient = trongridApiClient; + this.#tronHttpClient = tronHttpClient; + } + + async getAccountAssetByID( + accountId: string, + assetId: string, + ): Promise { + this.#logger.info('Getting account asset by ID', { accountId, assetId }); + const asset = await this.#assetsProvider.getAccountAssetByID( + accountId, + assetId as Caip19AssetId, + ); + + if (!asset) { + return null; + } + + return mapControllerAsset(accountId, asset); + } + + async getAccountAssetsByIDs( + accountId: string, + assetIds: string[], + ): Promise<(AssetEntity | null)[]> { + this.#logger.info('Getting account assets by IDs', { accountId, assetIds }); + const assets = await this.#assetsProvider.getAccountAssetsByIDs( + accountId, + assetIds as Caip19AssetId[], + ); + + return assetIds.map((assetId) => { + const asset = assets[assetId as Caip19AssetId]; + return asset ? mapControllerAsset(accountId, asset) : null; + }); + } + + async getAccountAssetsByScope( + scope: Network, + keyringAccountId: string, + ): Promise { + this.#logger.info('Getting account assets by scope', { + scope, + keyringAccountId, + }); + const controllerAssets = await this.#assetsProvider.getAccountAssetsByScope( + scope, + keyringAccountId, + ); + + return Object.values(controllerAssets).map((asset) => + mapControllerAsset(keyringAccountId, asset), + ); + } + + async getAccountAssets(accountId: string): Promise { + this.#logger.info('Getting account assets', { accountId }); + const [mainnetAssets, nileAssets, shastaAssets] = await Promise.all([ + this.#assetsProvider.getAccountAssetsByScope(Network.Mainnet, accountId), + this.#assetsProvider.getAccountAssetsByScope(Network.Nile, accountId), + this.#assetsProvider.getAccountAssetsByScope(Network.Shasta, accountId), + ]); + + const allUnmappedAssets = [ + ...Object.values(mainnetAssets), + ...Object.values(nileAssets), + ...Object.values(shastaAssets), + ]; + const allAssets = allUnmappedAssets.map((asset) => + mapControllerAsset(accountId, asset), + ); + + return allAssets; + } + + /** + * We used to fetch all assets and balances but now the Snap is only responsible for fetching snap-owned assets. + * - Energy & Bandwidth + * - Staked TRX and full staking lifecycle (rewards, in lock period, ready for withdrawal) + * + * @param scope - The network to query. + * @param account - The keyring account. + * @returns Promise - Array of assets with balances. + */ + async fetchAssetsAndBalancesForAccount( + scope: Network, + account: KeyringAccount, + ): Promise { + this.#logger.info('Fetching assets and balances for account', { + scope, + account, + }); + + const [ + tronAccountInfoRequest, + tronAccountResourcesRequest, + stakingRewardsRequest, + ] = await Promise.allSettled([ + this.#trongridApiClient.getAccountInfoByAddress(scope, account.address), + this.#tronHttpClient.getAccountResources(scope, account.address), + this.#tronHttpClient.getReward(scope, account.address), + ]); + + if (tronAccountInfoRequest.status === 'rejected') { + this.#logger.info( + 'Account info request failed, treating as inactive account', + { account, scope }, + ); + } + + const stakedData = buildStakedData(tronAccountInfoRequest); + const resources = buildAccountResources(tronAccountResourcesRequest); + const stakingRewards = + stakingRewardsRequest.status === 'fulfilled' + ? Math.max(0, stakingRewardsRequest.value) + : 0; + + return [ + ...extractStakedNativeAssets(account, scope, stakedData), + extractReadyForWithdrawalAsset(account, scope, stakedData), + extractInLockPeriodAsset(account, scope, stakedData), + extractStakingRewardsAsset(account, scope, stakingRewards), + ...extractBandwidth({ + account, + scope, + tronAccountResources: resources, + }), + ...extractEnergy({ + account, + scope, + tronAccountResources: resources, + }), + ]; + } + + /** + * Publishes snap-owned assets to the extension without persisting locally. + * + * Filters to snap-owned assets, reports each as `added`, and emits balance + * updates for those assets. + * + * @param assets - Assets to publish (non snap-owned entries are ignored). + */ + async saveMany(assets: AssetEntity[]): Promise { + this.#logger.info('Publishing snap-owned assets', assets); + + const snapOwnedAssets = assets.filter((asset) => + isSnapOwnedAsset(asset.assetType), + ); + + if (snapOwnedAssets.length === 0) { + return; + } + + const assetListUpdatedPayload = snapOwnedAssets.reduce< + AccountAssetListUpdatedEvent['params']['assets'] + >( + (acc, asset) => ({ + ...acc, + [asset.keyringAccountId]: { + added: [ + ...(acc[asset.keyringAccountId]?.added ?? []), + asset.assetType, + ], + removed: [], + }, + }), + {}, + ); + + await emitSnapKeyringEvent(snap, KeyringEvent.AccountAssetListUpdated, { + assets: assetListUpdatedPayload, + }); + + const balancesUpdatedPayload = snapOwnedAssets.reduce< + AccountBalancesUpdatedEvent['params']['balances'] + >( + (acc, asset) => ({ + ...acc, + [asset.keyringAccountId]: { + ...(acc[asset.keyringAccountId] ?? {}), + [asset.assetType]: { + unit: asset.symbol, + amount: asset.uiAmount, + }, + }, + }), + {}, + ); + + await emitSnapKeyringEvent(snap, KeyringEvent.AccountBalancesUpdated, { + balances: balancesUpdatedPayload, + }); + } +} diff --git a/packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.test.ts b/packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.test.ts new file mode 100644 index 00000000..2e997fca --- /dev/null +++ b/packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.test.ts @@ -0,0 +1,61 @@ +import type { Asset } from '@metamask/assets-controller'; +import type { CaipAssetType } from '@metamask/utils'; + +import { Network } from '../../../constants'; +import { mapControllerAsset } from './mapControllerAsset'; + +describe('mapControllerAsset', () => { + const accountId = 'account-id'; + const unknownAssetId = 'tron:728126428/trc20:unknown'; + + it('maps controller metadata when present', () => { + const asset = { + id: unknownAssetId, + chainId: Network.Mainnet, + balance: { amount: '1234567' }, + metadata: { + type: 'fungible', + symbol: 'TKN', + name: 'Token', + decimals: 6, + image: 'https://example.com/token.png', + }, + price: { price: 0, lastUpdated: 0 }, + fiatValue: 0, + } as unknown as Asset; + + expect(mapControllerAsset(accountId, asset)).toStrictEqual({ + assetType: unknownAssetId, + keyringAccountId: accountId, + network: Network.Mainnet, + symbol: 'TKN', + decimals: 6, + rawAmount: '1234567', + uiAmount: '1.234567', + iconUrl: 'https://example.com/token.png', + }); + }); + + it('uses empty defaults when metadata is missing', () => { + const assetId = 'tron:728126428/trc20:missing' as CaipAssetType; + const asset = { + id: assetId, + chainId: Network.Mainnet, + balance: { amount: '42' }, + metadata: { type: 'fungible', name: 'Missing' }, + price: { price: 0, lastUpdated: 0 }, + fiatValue: 0, + } as unknown as Asset; + + expect(mapControllerAsset(accountId, asset)).toStrictEqual({ + assetType: assetId, + keyringAccountId: accountId, + network: Network.Mainnet, + symbol: '', + decimals: 0, + rawAmount: '42', + uiAmount: '42', + iconUrl: '', + }); + }); +}); diff --git a/packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.ts b/packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.ts new file mode 100644 index 00000000..57fa305a --- /dev/null +++ b/packages/tron-wallet-snap/src/services/assets/utils/mapControllerAsset.ts @@ -0,0 +1,34 @@ +import type { Asset } from '@metamask/assets-controller'; + +import { Network } from '../../../constants'; +import type { AssetEntity } from '../../../entities/assets'; +import { toUiAmount } from '../../../utils/conversion'; + +/** + * Maps an AssetsController asset to the Snap's {@link AssetEntity} shape. + * + * @param accountId - Keyring account ID. + * @param asset - Asset returned by AssetsController. + * @returns Mapped asset entity. + */ +export function mapControllerAsset( + accountId: string, + asset: Asset, +): AssetEntity { + const assetId = asset.id; + const decimals = asset.metadata.decimals ?? 0; + const symbol = asset.metadata.symbol ?? ''; + const iconUrl = asset.metadata.image ?? ''; + const { amount } = asset.balance; + + return { + assetType: assetId, + keyringAccountId: accountId, + network: asset.chainId as Network, + symbol, + decimals, + rawAmount: amount, + uiAmount: toUiAmount(amount, decimals).toString(), + iconUrl, + } as AssetEntity; +} diff --git a/yarn.lock b/yarn.lock index 977f170b..dc15c7fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3781,6 +3781,7 @@ __metadata: "@metamask/snaps-jest": "npm:^10.2.0" "@metamask/snaps-sdk": "npm:^11.2.0" "@metamask/superstruct": "npm:^3.4.1" + "@metamask/utils": "npm:^11.9.0" "@types/lodash": "npm:^4.17.15" async-mutex: "npm:^0.5.0" bignumber.js: "npm:^9.3.1" From 8aa238467d238365663ecbda8280a230d0d7423a Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 15:13:45 +0100 Subject: [PATCH 2/7] chore(tron-wallet-snap): link changelog entry to #144 --- packages/tron-wallet-snap/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tron-wallet-snap/CHANGELOG.md b/packages/tron-wallet-snap/CHANGELOG.md index a076b594..b8b01826 100644 --- a/packages/tron-wallet-snap/CHANGELOG.md +++ b/packages/tron-wallet-snap/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `CoreAssetsAdapter` and `mapControllerAsset` for AssetsController integration (wired unused until routing lands) +- Add `CoreAssetsAdapter` and `mapControllerAsset` for AssetsController integration (wired unused until routing lands) ([#144](https://github.com/MetaMask/internal-snaps/pull/144)) ### Changed From 5b1292d385171ea3f6a86ad8d3b656badd398f22 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 16:07:54 +0100 Subject: [PATCH 3/7] fix(tron-wallet-snap): sync manifest shasum after CoreAssetsAdapter rebase --- packages/tron-wallet-snap/snap.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tron-wallet-snap/snap.manifest.json b/packages/tron-wallet-snap/snap.manifest.json index 0f47ebb3..d3793927 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": "NpxOo6DkB0sBh8xpisBu3o+7G6MJriDNsADcHVb/Qp8=", + "shasum": "0YaD1wEj/s5NH8rqIzGKWfTyJCn7Cx46maqhOr8nXos=", "location": { "npm": { "filePath": "dist/bundle.js", From 01427883e575a104445547f1fde3687ac47977f2 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 16:12:38 +0100 Subject: [PATCH 4/7] fix(tron-wallet-snap): sync CI manifest shasum and format CoreAssetsAdapter --- packages/tron-wallet-snap/snap.manifest.json | 2 +- .../src/services/assets/adapters/CoreAssetsAdapter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tron-wallet-snap/snap.manifest.json b/packages/tron-wallet-snap/snap.manifest.json index d3793927..375f632c 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": "0YaD1wEj/s5NH8rqIzGKWfTyJCn7Cx46maqhOr8nXos=", + "shasum": "rzJ7T7soBpaaRbny2ZCZqKMw+gLK0L1q4LuV3fLFqVk=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts b/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts index fce4600e..1d1f3458 100644 --- a/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts +++ b/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.ts @@ -14,7 +14,6 @@ import { Network } from '../../../constants'; import type { AssetEntity } from '../../../entities/assets'; import { createPrefixedLogger } from '../../../utils/logger'; import type { ILogger } from '../../../utils/logger'; -import { mapControllerAsset } from '../utils/mapControllerAsset'; import { buildAccountResources } from '../utils/buildAccountResources'; import { buildStakedData } from '../utils/buildStakedData'; import { extractBandwidth } from '../utils/extractBandwidth'; @@ -24,6 +23,7 @@ import { extractReadyForWithdrawalAsset } from '../utils/extractReadyForWithdraw import { extractStakedNativeAssets } from '../utils/extractStakedNativeAssets'; import { extractStakingRewardsAsset } from '../utils/extractStakingRewardsAsset'; import { isSnapOwnedAsset } from '../utils/isSnapOwnedAsset'; +import { mapControllerAsset } from '../utils/mapControllerAsset'; /** * Uses the AssetsController for fungible reads. Snap-owned (special) assets are From 4f2679b1e6d156475a69e87e28b3bf5417654881 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 16:17:17 +0100 Subject: [PATCH 5/7] fix(tron-wallet-snap): add test return types and sync manifest shasum for CI --- packages/tron-wallet-snap/snap.manifest.json | 2 +- .../src/services/assets/AssetsService.test.ts | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/tron-wallet-snap/snap.manifest.json b/packages/tron-wallet-snap/snap.manifest.json index 375f632c..f7baff20 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": "rzJ7T7soBpaaRbny2ZCZqKMw+gLK0L1q4LuV3fLFqVk=", + "shasum": "3Mn3cc4eA7OmuOAA4zs+kMMQGCj1gs982z50E37I+pc=", "location": { "npm": { "filePath": "dist/bundle.js", 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 7f261b99..0dffc832 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts @@ -28,7 +28,16 @@ type MockState = { jest.mock('../../context', () => ({ configProvider: { - get() { + get(): { + priceApi: { + cacheTtlsMilliseconds: { + fiatExchangeRates: number; + spotPrices: number; + historicalPrices: number; + }; + }; + activeNetworks: never[]; + } { return { priceApi: { cacheTtlsMilliseconds: { @@ -143,7 +152,9 @@ const minimalTronAccount = createMockTronAccount({ * @param overrides - Account-specific fields to set. * @returns A mock AccountResources object. */ -function getMockAccountResources(overrides: Record = {}) { +function getMockAccountResources( + overrides: Record = {}, +): Record { return { freeNetLimit: 600, TotalNetLimit: 0, @@ -161,7 +172,10 @@ function getMockAccountResources(overrides: Record = {}) { * @param assetType - The CAIP-19 asset type to match. * @returns The matching asset, or undefined. */ -function findAsset(assets: AssetEntity[], assetType: KnownCaip19Id) { +function findAsset( + assets: AssetEntity[], + assetType: KnownCaip19Id, +): AssetEntity | undefined { return assets.find((a: AssetEntity) => a.assetType === assetType); } From f0886917a300b55815f727f0f96ddc88cf74545a Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 16:24:52 +0100 Subject: [PATCH 6/7] chore: prune stale eslint suppressions in AssetsService.test.ts --- eslint-suppressions.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 279fd759..a3fcdc51 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1408,9 +1408,6 @@ } }, "packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts": { - "@typescript-eslint/explicit-function-return-type": { - "count": 2 - }, "@typescript-eslint/no-explicit-any": { "count": 1 } @@ -1520,4 +1517,4 @@ "count": 2 } } -} +} \ No newline at end of file From 176828a6de99eef283fad3e825470387c787ef98 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Mon, 10 Aug 2026 16:29:34 +0100 Subject: [PATCH 7/7] chore: format eslint-suppressions.json --- eslint-suppressions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index a3fcdc51..9492bd3d 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1517,4 +1517,4 @@ "count": 2 } } -} \ No newline at end of file +}