Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Render the confirmation "Estimated changes" section with the shared `EstimatedChanges` component from `@metamask/snap-networks-utils` ([#396](https://github.com/MetaMask/internal-snaps/pull/396))
- Estimated changes from the previous scan now stay visible while a confirmation re-scans, instead of showing a loading skeleton
- `listAccountAssets` and `getAccountBalances` now fetch live assets and balances from the chain through the Snap's TronGrid clients instead of returning persisted state. ([#388](https://github.com/MetaMask/internal-snaps/pull/388))

### Fixed

- Show "Estimated changes are not available" instead of "No estimated changes" when the transaction scan returns an error result, such as for a malformed transaction ([#396](https://github.com/MetaMask/internal-snaps/pull/396))
- Report the MetaMask origin as lowercase `metamask` instead of `MetaMask` for MetaMask-initiated operations, so the origin matches the value used by the other non-EVM snaps and granted to the keyring methods, and so transaction scan requests are attributed to `https://metamask.io`. The confirmation UI keeps displaying `MetaMask`. ([#392](https://github.com/MetaMask/internal-snaps/pull/392))
- Show "Estimated changes are not available" instead of "No estimated changes" when the transaction scan returns an error result, such as for a malformed transaction ([#396](https://github.com/MetaMask/internal-snaps/pull/396))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main has this line duplicated


## [4.0.0]

Expand Down
8 changes: 4 additions & 4 deletions packages/tron-wallet-snap/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 72.49,
functions: 79.91,
lines: 85.79,
statements: 85.79,
branches: 71.4,
functions: 81,
lines: 85.9,
statements: 85.9,
},
},
};
2 changes: 1 addition & 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": "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/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "XvgNUT01gJK88k3Ffk8xYCldFKl+BKPLCaEAoAUzRHQ=",
"shasum": "lgMdbXW2azl1A32pRmed4B/Te5j8mQs5uim9HXUUSWs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
5 changes: 1 addition & 4 deletions packages/tron-wallet-snap/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,7 @@ const coreAssetsAdapter = new CoreAssetsAdapter({
assetsProvider.getAccountAssetsByIDs.bind(assetsProvider),
getAccountAssetsByScope:
assetsProvider.getAccountAssetsByScope.bind(assetsProvider),
getAddressInfo:
trongridApiClient.getAccountInfoByAddress.bind(trongridApiClient),
getAddressResources: tronHttpClient.getAccountResources.bind(tronHttpClient),
getAddressStakingRewards: tronHttpClient.getReward.bind(tronHttpClient),
getAssets: assetsProvider.getAssets.bind(assetsProvider),
});

// Business Services
Expand Down
111 changes: 105 additions & 6 deletions packages/tron-wallet-snap/src/handlers/keyring/keyring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from '@metamask/snaps-sdk';

import type { SnapClient } from '../../clients/snap/SnapClient';
import { Network } from '../../constants';
import { KnownCaip19Id, Network } from '../../constants';
import type { AssetEntity } from '../../entities/assets';
import type { AccountsService } from '../../services/accounts/AccountsService';
import type { AssetsService } from '../../services/assets/AssetsService';
import type { ConfirmationHandler } from '../../services/confirmation/ConfirmationHandler';
Expand Down Expand Up @@ -65,6 +66,23 @@ describe('KeyringHandler', () => {
let mockWalletService: jest.Mocked<WalletService>;
let mockConfirmationHandler: jest.Mocked<ConfirmationHandler>;

const mockLiveAsset: AssetEntity = {
assetType: KnownCaip19Id.TrxMainnet,
keyringAccountId: mockAccount.id,
network: Network.Mainnet,
symbol: 'TRX',
decimals: 6,
rawAmount: '1000000',
uiAmount: '1',
iconUrl: '',
};

const mockShastaAsset: AssetEntity = {
...mockLiveAsset,
assetType: KnownCaip19Id.TrxShasta,
network: Network.Shasta,
};

beforeEach(() => {
mockSnapClient = {
scheduleBackgroundEvent: jest.fn().mockResolvedValue(undefined),
Expand All @@ -84,7 +102,9 @@ describe('KeyringHandler', () => {
}),
} as unknown as jest.Mocked<AccountsService>;
mockAssetsService = {
getAccountAssets: jest.fn().mockResolvedValue([]),
fetchAccountAssetsFromTrongrid: jest
.fn()
.mockResolvedValue([mockLiveAsset, mockShastaAsset]),
} as unknown as jest.Mocked<AssetsService>;
mockTransactionsService = {
checkAddressActivity: jest.fn(),
Expand Down Expand Up @@ -670,13 +690,26 @@ describe('KeyringHandler', () => {
});

describe('getAccountAssets', () => {
it('returns asset types for an account', async () => {
it('fetches live assets for all account scopes and returns their asset types', async () => {
const result = await keyringHandler.getAccountAssets(mockAccount.id);

expect(result).toStrictEqual([]);
expect(mockAssetsService.getAccountAssets).toHaveBeenCalledWith(
mockAccount.id,
expect(result).toStrictEqual([
KnownCaip19Id.TrxMainnet,
KnownCaip19Id.TrxShasta,
]);
expect(
mockAssetsService.fetchAccountAssetsFromTrongrid,
).toHaveBeenCalledWith(mockAccount);
});

it('propagates fetch failures', async () => {
mockAssetsService.fetchAccountAssetsFromTrongrid.mockRejectedValue(
new Error('network error'),
);

await expect(
keyringHandler.getAccountAssets(mockAccount.id),
).rejects.toThrow('network error');
});

it('throws when the account is not found', async () => {
Expand All @@ -688,6 +721,72 @@ describe('KeyringHandler', () => {
});
});

describe('getAccountBalances', () => {
it('fetches live balances for the account and returns the balance record', async () => {
const result = await keyringHandler.getAccountBalances(mockAccount.id, [
KnownCaip19Id.TrxMainnet,
]);

expect(result).toStrictEqual({
[KnownCaip19Id.TrxMainnet]: {
amount: '1',
unit: 'TRX',
},
});
expect(
mockAssetsService.fetchAccountAssetsFromTrongrid,
).toHaveBeenCalledWith(mockAccount);
});

it('excludes token assets with zero balance', async () => {
const zeroBalanceToken: AssetEntity = {
...mockLiveAsset,
assetType: KnownCaip19Id.UsdtMainnet,
rawAmount: '0',
uiAmount: '0',
};

mockAssetsService.fetchAccountAssetsFromTrongrid.mockResolvedValue([
mockLiveAsset,
zeroBalanceToken,
]);

const result = await keyringHandler.getAccountBalances(mockAccount.id, [
KnownCaip19Id.TrxMainnet,
KnownCaip19Id.UsdtMainnet,
]);

expect(result).toStrictEqual({
[KnownCaip19Id.TrxMainnet]: {
amount: '1',
unit: 'TRX',
},
});
});

it('propagates fetch failures', async () => {
mockAssetsService.fetchAccountAssetsFromTrongrid.mockRejectedValue(
new Error('network error'),
);

await expect(
keyringHandler.getAccountBalances(mockAccount.id, [
KnownCaip19Id.TrxMainnet,
]),
).rejects.toThrow('network error');
});

it('throws when the account is not found', async () => {
mockAccountsService.findById.mockResolvedValue(null);

await expect(
keyringHandler.getAccountBalances(mockAccount.id, [
KnownCaip19Id.TrxMainnet,
]),
).rejects.toThrow('not found');
});
});

describe('getAccountTransactions', () => {
it('returns paginated transactions for an account', async () => {
const result = await keyringHandler.getAccountTransactions(
Expand Down
25 changes: 13 additions & 12 deletions packages/tron-wallet-snap/src/handlers/keyring/keyring.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ListAccountAssetsResponseStruct } from '@metamask/keyring-api';
import type {
Balance,
CreateAccountOptions as KeyringBatchCreateAccountOptions,
Expand Down Expand Up @@ -53,11 +52,12 @@ import {
GetAccounBalancesResponseStruct,
GetAccountBalancesStruct,
GetAccountStruct,
ListAccountAssetsStruct,
ListAccountTransactionsStruct,
GetAccountAssetsStruct,
GetAccountAssetsResponseStruct,
PrivateKeyHexStruct,
SignTransactionRequestStruct,
TronKeyringRequestStruct,
GetAccountTransactionsStruct,
} from '../../validation/structs';
import type { TronWalletKeyringRequest } from '../../validation/structs';
import { validateRequest, validateResponse } from '../../validation/validators';
Expand Down Expand Up @@ -181,14 +181,14 @@ export class KeyringHandler implements KeyringSnapRpc {

async getAccountAssets(accountId: string): Promise<CaipAssetTypeOrId[]> {
try {
validateRequest({ accountId }, ListAccountAssetsStruct);
validateRequest({ accountId }, GetAccountAssetsStruct);

await this.#getAccountOrThrow(accountId);
const account = await this.#getAccountOrThrow(accountId);

this.#logger.info('Listing account assets', { accountId });
this.#logger.info('Getting account assets', { accountId });

const assetEntities =
await this.#assetsService.getAccountAssets(accountId);
await this.#assetsService.fetchAccountAssetsFromTrongrid(account);
const result = assetEntities
.filter(
(asset) =>
Expand All @@ -199,7 +199,7 @@ export class KeyringHandler implements KeyringSnapRpc {

this.#logger.info('Account assets', { accountId, result });

validateResponse(result, ListAccountAssetsResponseStruct);
validateResponse(result, GetAccountAssetsResponseStruct);
return result;
} catch (error: unknown) {
this.#logger.error({ error }, 'Error listing account assets');
Expand All @@ -224,9 +224,9 @@ export class KeyringHandler implements KeyringSnapRpc {
next: string | null;
}> {
try {
validateRequest({ accountId, pagination }, ListAccountTransactionsStruct);
validateRequest({ accountId, pagination }, GetAccountTransactionsStruct);

this.#logger.info('Listing account transactions...');
this.#logger.info('Getting account transactions...');
const { limit, next } = pagination;

const keyringAccount = await this.#getAccount(accountId);
Expand Down Expand Up @@ -275,9 +275,10 @@ export class KeyringHandler implements KeyringSnapRpc {

this.#logger.info('Getting account balances', { accountId, assets });

await this.#getAccountOrThrow(accountId);
const account = await this.#getAccountOrThrow(accountId);

const assetsList = await this.#assetsService.getAccountAssets(accountId);
const assetsList =
await this.#assetsService.fetchAccountAssetsFromTrongrid(account);

@stanleyyconsensys stanleyyconsensys Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

shall we consider adding this.#assetsService.fetchAccountAssetsFromTrongrid with InMemoryCache

as Caller do listAccountAsset first then getAccountBalances

So those SNAP api method use the same tron grid API, we can have a bit optizme to reduce the network call?
e.g

  • listAccountAsset: always return live data + save into in memory cache with expire
  • getAccountBalances: read cache first then read live data if cache not exist

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would start without it. Just to make sure everything works on main. And then optimize it


const assetsToUse = assetsList
.filter((asset) => assets.includes(asset.assetType))
Expand Down
Loading
Loading