Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
bdad28b
chore: add assets-controller messenger dependencies
ulissesferreira Jul 30, 2026
575e11d
feat: add Core messenger types and endowment
ulissesferreira Jul 30, 2026
9def87a
feat: add SNAP_OWNED_ASSETS and isSnapOwnedAsset
ulissesferreira Jul 30, 2026
46679cc
feat: add mapControllerAsset helper
ulissesferreira Jul 30, 2026
a586cc8
feat: add assets migration mode resolver
ulissesferreira Jul 30, 2026
1113cb2
feat: wire Core messenger into AssetsService context
ulissesferreira Jul 30, 2026
22b868c
feat: route AssetsService fungibles via feature flag
ulissesferreira Jul 30, 2026
f42952b
feat: always use AssetsController for fungibles (no feature flag)
ulissesferreira Jul 31, 2026
193d4a6
feat(tron-wallet-snap): allow AssetsController:getAssets messenger ac…
ulissesferreira Jul 31, 2026
4eec75a
perf(tron-wallet-snap): batch fungible reads via AssetsController:get…
ulissesferreira Jul 31, 2026
6728e72
refactor(tron-wallet-snap): fetch only snap-owned assets during accou…
ulissesferreira Jul 31, 2026
eb0b11c
refactor(tron-wallet-snap): drop assets migration mode branching
ulissesferreira Jul 31, 2026
97da74e
docs(tron-wallet-snap): changelog for AssetsController batch reads
ulissesferreira Jul 31, 2026
747ac0e
test(tron-wallet-snap): restore coverage after assets fetch simplific…
ulissesferreira Jul 31, 2026
e25b2e0
fix(tron-wallet-snap): persist only snap-owned assets in saveMany
ulissesferreira Jul 31, 2026
89fb41a
refactor(tron-wallet-snap): organize handlers and rename asset readers
cursoragent Jul 31, 2026
afbea9b
feat(tron-wallet-snap): add getAccountAssetsByScope for controller reads
ulissesferreira Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,16 @@
"count": 2
}
},
"packages/tron-wallet-snap/src/services/assets/mapControllerAsset.test.ts": {
"import-x/no-extraneous-dependencies": {
"count": 1
}
},
"packages/tron-wallet-snap/src/services/assets/mapControllerAsset.ts": {
"import-x/no-extraneous-dependencies": {
"count": 1
}
},
"packages/tron-wallet-snap/src/services/config/ConfigProvider.ts": {
"import-x/no-extraneous-dependencies": {
"count": 1
Expand Down
8 changes: 8 additions & 0 deletions packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `AssetsService.getAccountAssetsByScope` for instant controller-backed fungible asset reads by network.
- Route fungible asset reads (TRX, TRC10, TRC20) through `AssetsController:getAsset`. Protocol assets (energy, bandwidth, staking, lock/withdrawal, rewards) remain Snap-owned.

### Changed

- Batch fungible asset reads through `AssetsController:getAssets` instead of per-asset `getAsset` loops. Account sync now fetches and persists only snap-owned protocol assets; fungibles are owned by Core `AssetsController`.
- Reorganize snap handlers into `assets`, `cronjob`, `keyring`, and `user-input` folders.
- Rename `AssetsService` account asset readers to `getAccountAssetByID`, `getAccountAssetsByIDs`, and `getAccountAssets`.
- Bump `@metamask/keyring-api` from `^23.2.0` to `^23.7.0` ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
- Bump `@metamask/keyring-snap-sdk` from `^8.0.0` to `^9.2.1` ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
- Bump `@metamask/snaps-cli` from `^8.3.0` to `^8.4.1` ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
Expand Down
2 changes: 2 additions & 0 deletions packages/tron-wallet-snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"devDependencies": {
"@metamask/assets-controller": "11.2.0",
"@metamask/auto-changelog": "^6.1.1",
"@metamask/key-tree": "^10.1.1",
"@metamask/keyring-api": "^23.7.0",
"@metamask/keyring-snap-sdk": "^9.2.1",
"@metamask/messenger": "^2.0.0",
"@metamask/snaps-cli": "^8.4.1",
"@metamask/snaps-jest": "^10.2.0",
"@metamask/snaps-sdk": "^11.2.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/tron-wallet-snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "tcnp/6xxntfergEzeQYc7k96SfveVo+5kBQHGEXqZkQ=",
"shasum": "8SP19+soWqCyOukLqweBmUY1FK0v/fVvVhpf4/OiP58=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down Expand Up @@ -48,6 +48,9 @@
},
"endowment:assets": {
"scopes": ["tron:728126428"]
},
"endowment:messenger": {
"actions": ["AssetsController:getAsset", "AssetsController:getAssets"]
}
},
"platformVersion": "11.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mapKeys } from 'lodash';

import type { ICache } from '../../caching/ICache';
import { useCache } from '../../caching/useCache';
import { SPECIAL_ASSETS } from '../../constants';
import { SNAP_OWNED_ASSETS } from '../../constants';
import type { ConfigProvider } from '../../services/config';
import { buildUrl } from '../../utils/buildUrl';
import type { ILogger } from '../../utils/logger';
Expand Down Expand Up @@ -257,7 +257,7 @@ export class PriceApiClient {
assert(vsCurrency, VsCurrencyParamStruct);

const filteredTokens = tokenCaip19Types.filter(
(tokenCaip19Type) => !SPECIAL_ASSETS.includes(tokenCaip19Type),
(tokenCaip19Type) => !SNAP_OWNED_ASSETS.includes(tokenCaip19Type),
);

return this.#getMultipleSpotPrices_CACHE(filteredTokens, vsCurrency);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { array, assert } from '@metamask/superstruct';
import type { Infer } from '@metamask/superstruct';
import { CaipAssetTypeStruct, parseCaipAssetType } from '@metamask/utils';

import { Network, SPECIAL_ASSETS } from '../../constants';
import { Network, SNAP_OWNED_ASSETS } from '../../constants';
import type { TokenCaipAssetType } from '../../services/assets/types';
import { TokenCaipAssetTypeStruct } from '../../services/assets/types';
import type { ConfigProvider } from '../../services/config';
Expand Down Expand Up @@ -94,7 +94,7 @@ export class TokenApiClient {
* Exclude TRON resource tokens (energy and bandwidth), staked tokens, and tokens not from supported networks.
*/
const supportedAssetTypes = assetTypes.filter((assetType) => {
if (SPECIAL_ASSETS.includes(assetType)) {
if (SNAP_OWNED_ASSETS.includes(assetType)) {
return false;
}
const { chainId } = parseCaipAssetType(assetType);
Expand Down
7 changes: 5 additions & 2 deletions packages/tron-wallet-snap/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ export const Networks = {
},
} as const;

export const SPECIAL_ASSETS: string[] = [
export const SNAP_OWNED_ASSETS: string[] = [
KnownCaip19Id.TrxStakedForBandwidthMainnet,
KnownCaip19Id.TrxStakedForBandwidthNile,
KnownCaip19Id.TrxStakedForBandwidthShasta,
Expand Down Expand Up @@ -419,9 +419,12 @@ export const SPECIAL_ASSETS: string[] = [
KnownCaip19Id.MaximumEnergyShasta,
];

/** @deprecated Use {@link SNAP_OWNED_ASSETS} instead. */
export const SPECIAL_ASSETS = SNAP_OWNED_ASSETS;

export const ESSENTIAL_ASSETS: string[] = [
KnownCaip19Id.TrxMainnet,
KnownCaip19Id.TrxNile,
KnownCaip19Id.TrxShasta,
...SPECIAL_ASSETS,
...SNAP_OWNED_ASSETS,
];
14 changes: 10 additions & 4 deletions packages/tron-wallet-snap/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getMessenger } from '@metamask/snaps-sdk';

import { InMemoryCache } from './caching/InMemoryCache';
import { StateCache } from './caching/StateCache';
import { PriceApiClient } from './clients/price-api/PriceApiClient';
Expand All @@ -7,12 +9,12 @@ import { TokenApiClient } from './clients/token-api/TokenApiClient';
import { TronHttpClient } from './clients/tron-http/TronHttpClient';
import { TrongridApiClient } from './clients/trongrid/TrongridApiClient';
import { TronWebFactory } from './clients/tronweb/TronWebFactory';
import { AssetsHandler } from './handlers/assets';
import { AssetsHandler } from './handlers/assets/assets';
import { ClientRequestHandler } from './handlers/clientRequest/clientRequest';
import { CronHandler } from './handlers/cronjob';
import { KeyringHandler } from './handlers/keyring';
import { CronHandler } from './handlers/cronjob/cronjob';
import { KeyringHandler } from './handlers/keyring/keyring';
import { RpcHandler } from './handlers/rpc/rpc';
import { UserInputHandler } from './handlers/userInput';
import { UserInputHandler } from './handlers/user-input/userInput';
import { AccountsRepository } from './services/accounts/AccountsRepository';
import { AccountsService } from './services/accounts/AccountsService';
import { AssetsRepository } from './services/assets/AssetsRepository';
Expand All @@ -29,6 +31,7 @@ import { TransactionScanService } from './services/transaction-scan/TransactionS
import { TransactionsRepository } from './services/transactions/TransactionsRepository';
import { TransactionsService } from './services/transactions/TransactionsService';
import { WalletService } from './services/wallet/WalletService';
import type { CoreMessenger } from './types/core-messenger';
import logger, { noOpLogger } from './utils/logger';

/**
Expand Down Expand Up @@ -82,6 +85,8 @@ const priceApiClient = new PriceApiClient(configProvider, priceCache);
// Token API client
const tokenApiClient = new TokenApiClient(configProvider);

const coreMessenger = getMessenger<CoreMessenger>();

// Security Alerts API client
const securityAlertsApiClient = new SecurityAlertsApiClient(
configProvider,
Expand All @@ -98,6 +103,7 @@ const assetsService = new AssetsService({
priceApiClient,
tokenApiClient,
snapClient,
coreMessenger,
});

const transactionsService = new TransactionsService({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import type {
OnAssetsMarketDataResponse,
} from '@metamask/snaps-sdk';

import type { AssetsService } from '../services/assets/AssetsService';
import type { ILogger } from '../utils/logger';
import { createPrefixedLogger } from '../utils/logger';
import type { AssetsService } from '../../services/assets/AssetsService';
import type { ILogger } from '../../utils/logger';
import { createPrefixedLogger } from '../../utils/logger';

export class AssetsHandler {
readonly #logger: ILogger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import type { TransactionRawData } from '../../services/transaction-expiration-r
import type { TransactionsService } from '../../services/transactions/TransactionsService';
import { trxToSun } from '../../utils/conversion';
import { mockLogger } from '../../utils/mockLogger';
import { BackgroundEventMethod } from '../cronjob';
import { BackgroundEventMethod } from '../cronjob/cronjob';
import { ClientRequestHandler } from './clientRequest';
import { ClientRequestMethod, SendErrorCodes } from './types';
import type { OnAmountInputRequestStruct } from './validation';
Expand Down Expand Up @@ -115,7 +115,7 @@ type WithClientRequestHandlerCallback<ReturnValue> = (payload: {
Pick<AccountsService, 'findById' | 'findByIdOrThrow' | 'deriveTronKeypair'>
>;
mockAssetsService: jest.Mocked<
Pick<AssetsService, 'getAssetsByAccountId' | 'getAssetByAccountId'>
Pick<AssetsService, 'getAccountAssetsByIDs' | 'getAccountAssetByID'>
>;
mockSendService: jest.Mocked<
Pick<
Expand Down Expand Up @@ -165,10 +165,10 @@ async function withClientRequestHandler<ReturnValue>(
};

const mockAssetsService: jest.Mocked<
Pick<AssetsService, 'getAssetsByAccountId' | 'getAssetByAccountId'>
Pick<AssetsService, 'getAccountAssetsByIDs' | 'getAccountAssetByID'>
> = {
getAssetsByAccountId: jest.fn(),
getAssetByAccountId: jest.fn(),
getAccountAssetsByIDs: jest.fn(),
getAccountAssetByID: jest.fn(),
};

const mockSendService: jest.Mocked<
Expand Down Expand Up @@ -304,7 +304,7 @@ describe('ClientRequestHandler', () => {
} as unknown as jest.Mocked<AccountsService>;

mockAssetsService = {
getAssetsByAccountId: jest.fn(),
getAccountAssetsByIDs: jest.fn(),
} as unknown as jest.Mocked<AssetsService>;

mockSendService = {} as unknown as jest.Mocked<SendService>;
Expand Down Expand Up @@ -736,7 +736,7 @@ describe('ClientRequestHandler', () => {
mockTronWeb.trx.sign.mockResolvedValue(signedTransaction);

// Mock available resources
mockAssetsService.getAssetsByAccountId.mockResolvedValue([
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue([
{ rawAmount: '5000' }, // Bandwidth
{ rawAmount: '100000' }, // Energy
] as any);
Expand Down Expand Up @@ -781,7 +781,7 @@ describe('ClientRequestHandler', () => {
).toHaveBeenCalledWith('TriggerSmartContract', expect.any(String));
// trx.sign is NOT called - fee computation uses unsigned transactions
expect(mockTronWeb.trx.sign).not.toHaveBeenCalled();
expect(mockAssetsService.getAssetsByAccountId).toHaveBeenCalledWith(
expect(mockAssetsService.getAccountAssetsByIDs).toHaveBeenCalledWith(
TEST_ACCOUNT_ID,
[Networks[scope].bandwidth.id, Networks[scope].energy.id],
);
Expand Down Expand Up @@ -873,7 +873,7 @@ describe('ClientRequestHandler', () => {
};
mockTronWeb.trx.sign.mockResolvedValue(signedTransaction);

mockAssetsService.getAssetsByAccountId.mockResolvedValue([
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue([
{ rawAmount: '1000' }, // Bandwidth
{ rawAmount: '0' }, // Energy (not needed for native transfer)
] as any);
Expand Down Expand Up @@ -966,7 +966,7 @@ describe('ClientRequestHandler', () => {
});

// No resources available
mockAssetsService.getAssetsByAccountId.mockResolvedValue([
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue([
undefined, // No bandwidth asset
undefined, // No energy asset
] as any);
Expand Down Expand Up @@ -1490,7 +1490,7 @@ describe('ClientRequestHandler - signAndSendTransaction', () => {
} as unknown as jest.Mocked<AccountsService>;

mockAssetsService = {
getAssetsByAccountId: jest.fn(),
getAccountAssetsByIDs: jest.fn(),
} as unknown as jest.Mocked<AssetsService>;

mockSendService = {} as unknown as jest.Mocked<SendService>;
Expand Down Expand Up @@ -1762,7 +1762,7 @@ describe('ClientRequestHandler - onAmountInput', () => {
];

mockAccountsService.findById.mockResolvedValue(mockAccount);
mockAssetsService.getAssetsByAccountId.mockResolvedValue(mockAssets);
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue(mockAssets);

const result = await handler.handle(request);

Expand Down Expand Up @@ -1820,7 +1820,7 @@ describe('ClientRequestHandler - onAmountInput', () => {
];

mockAccountsService.findById.mockResolvedValue(mockAccount);
mockAssetsService.getAssetsByAccountId.mockResolvedValue(mockAssets);
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue(mockAssets);
mockSendService.buildTransaction.mockResolvedValue(builtTransaction);
mockFeeCalculatorService.computeFee.mockResolvedValue(mockFees);

Expand Down Expand Up @@ -1892,7 +1892,7 @@ describe('ClientRequestHandler - onAmountInput', () => {
];

mockAccountsService.findById.mockResolvedValue(mockAccount);
mockAssetsService.getAssetsByAccountId.mockResolvedValue(mockAssets);
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue(mockAssets);
mockSendService.buildTransaction.mockResolvedValue(builtTransaction);
mockFeeCalculatorService.computeFee.mockResolvedValue(mockFees);

Expand Down Expand Up @@ -1943,7 +1943,7 @@ describe('ClientRequestHandler - onAmountInput', () => {
];

mockAccountsService.findById.mockResolvedValue(mockAccount);
mockAssetsService.getAssetsByAccountId.mockResolvedValue(mockAssets);
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue(mockAssets);

const result = await handler.handle(request);

Expand Down Expand Up @@ -2004,7 +2004,7 @@ describe('ClientRequestHandler - onAmountInput', () => {
];

mockAccountsService.findById.mockResolvedValue(mockAccount);
mockAssetsService.getAssetsByAccountId.mockResolvedValue(mockAssets);
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue(mockAssets);
mockSendService.buildTransaction.mockResolvedValue(builtTransaction);
mockFeeCalculatorService.computeFee.mockResolvedValue(mockFees);

Expand Down Expand Up @@ -2094,10 +2094,10 @@ describe('ClientRequestHandler - computeStakeFee', () => {
const nativeAssetId = Networks[scope].nativeToken.id;

// Mock native balance and resources
mockAssetsService.getAssetByAccountId.mockResolvedValue({
mockAssetsService.getAccountAssetByID.mockResolvedValue({
uiAmount: '100',
} as AssetEntity);
mockAssetsService.getAssetsByAccountId.mockResolvedValue([
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue([
{ rawAmount: '5000' }, // Bandwidth
{ rawAmount: '100000' }, // Energy
] as AssetEntity[]);
Expand Down Expand Up @@ -2130,11 +2130,11 @@ describe('ClientRequestHandler - computeStakeFee', () => {
'ENERGY',
'TGJn1wnUYHJbvN88cynZbsAz2EMeZq73yx',
);
expect(mockAssetsService.getAssetByAccountId).toHaveBeenCalledWith(
expect(mockAssetsService.getAccountAssetByID).toHaveBeenCalledWith(
TEST_ACCOUNT_ID,
nativeAssetId,
);
expect(mockAssetsService.getAssetsByAccountId).toHaveBeenCalledWith(
expect(mockAssetsService.getAccountAssetsByIDs).toHaveBeenCalledWith(
TEST_ACCOUNT_ID,
[Networks[scope].bandwidth.id, Networks[scope].energy.id],
);
Expand Down Expand Up @@ -2179,7 +2179,7 @@ describe('ClientRequestHandler - computeStakeFee', () => {
} as any);

// Account has only 5 TRX
mockAssetsService.getAssetByAccountId.mockResolvedValue({
mockAssetsService.getAccountAssetByID.mockResolvedValue({
uiAmount: '5',
} as AssetEntity);

Expand Down Expand Up @@ -2237,7 +2237,7 @@ describe('ClientRequestHandler - confirmSend validation', () => {
uiAmount: '100',
rawAmount: '100000000',
} as NativeAsset;
mockAssetsService.getAssetByAccountId.mockResolvedValue(mockAsset);
mockAssetsService.getAccountAssetByID.mockResolvedValue(mockAsset);

// validateSend returns insufficient balance
mockSendService.validateSend.mockResolvedValue({
Expand Down Expand Up @@ -2306,7 +2306,7 @@ describe('ClientRequestHandler - confirmSend validation', () => {
uiAmount: '100',
rawAmount: '100000000',
} as NativeAsset;
mockAssetsService.getAssetByAccountId.mockResolvedValue(mockAsset);
mockAssetsService.getAccountAssetByID.mockResolvedValue(mockAsset);

// validateSend returns insufficient balance to cover fee
mockSendService.validateSend.mockResolvedValue({
Expand Down Expand Up @@ -2368,13 +2368,13 @@ describe('ClientRequestHandler - confirmSend validation', () => {
uiAmount: '100',
rawAmount: '100000000',
} as NativeAsset;
mockAssetsService.getAssetByAccountId.mockResolvedValue(mockAsset);
mockAssetsService.getAccountAssetByID.mockResolvedValue(mockAsset);

// validateSend returns valid.
mockSendService.validateSend.mockResolvedValue({ valid: true });

// Mock the rest of the flow.
mockAssetsService.getAssetsByAccountId.mockResolvedValue([
mockAssetsService.getAccountAssetsByIDs.mockResolvedValue([
{ rawAmount: '1000' }, // Bandwidth
{ rawAmount: '50000' }, // Energy
] as any);
Expand Down Expand Up @@ -2530,7 +2530,7 @@ describe('ClientRequestHandler - confirmSend validation', () => {
} as any);

// Asset not found
(mockAssetsService.getAssetByAccountId as jest.Mock).mockResolvedValue(
(mockAssetsService.getAccountAssetByID as jest.Mock).mockResolvedValue(
null,
);

Expand Down
Loading