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
3 changes: 3 additions & 0 deletions packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Change the last behaviours missing for the `AssetsController` migration to happen ([#424](https://github.com/MetaMask/internal-snaps/pull/424))
- When the assets migration feature flag is active, the snap no longer fetches, persists, or publishes assets: the periodic sync cronjob and the synchronization triggered by selected account changes no longer touch assets or emit `AccountAssetListUpdated` and `AccountBalancesUpdated` events, leaving asset syncing to the `AssetsController`
- When the live assets and balances are fetched by `getAccountAssets` and `getAccountBalances`, they are persisted to local state without emitting `AccountAssetListUpdated` and `AccountBalancesUpdated` events
- Validate network and CAIP asset type values coming from untrusted boundaries (RPC request params, the Keyring API, the AssetsController) instead of force-casting them, so unsupported values fail fast with an `Invalid params` error ([#403](https://github.com/MetaMask/internal-snaps/pull/403))
- 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
Expand Down
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": "https://github.com/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "Ts+SpwPXHGuDCZNLn1PfTPwMX8ECWdp46bKrlYUr1vc=",
"shasum": "oUs09g+NmBge4wEQKoT1spo/E1CWpr4vVI6VeXBxmZQ=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ describe('KeyringHandler', () => {
fetchAccountAssetsFromTrongrid: jest
.fn()
.mockResolvedValue([mockLiveAsset, mockShastaAsset]),
saveMany: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<AssetsService>;
mockTransactionsService = {
checkAddressActivity: jest.fn(),
Expand Down Expand Up @@ -700,6 +701,10 @@ describe('KeyringHandler', () => {
expect(
mockAssetsService.fetchAccountAssetsFromTrongrid,
).toHaveBeenCalledWith(mockAccount);
expect(mockAssetsService.saveMany).toHaveBeenCalledWith([
mockLiveAsset,
mockShastaAsset,
]);
});

it('propagates fetch failures', async () => {
Expand Down Expand Up @@ -736,6 +741,10 @@ describe('KeyringHandler', () => {
expect(
mockAssetsService.fetchAccountAssetsFromTrongrid,
).toHaveBeenCalledWith(mockAccount);
expect(mockAssetsService.saveMany).toHaveBeenCalledWith([
mockLiveAsset,
mockShastaAsset,
]);
});

it('excludes token assets with zero balance', async () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/tron-wallet-snap/src/handlers/keyring/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ export class KeyringHandler implements KeyringSnapRpc {

const assetEntities =
await this.#assetsService.fetchAccountAssetsFromTrongrid(account);

await this.#assetsService.saveMany(assetEntities);

const result = assetEntities
.filter(
(asset) =>
Expand Down Expand Up @@ -278,6 +281,8 @@ export class KeyringHandler implements KeyringSnapRpc {
const assetsList =
await this.#assetsService.fetchAccountAssetsFromTrongrid(account);

await this.#assetsService.saveMany(assetsList);

const assetsToUse = assetsList
.filter((asset) => assets.includes(asset.assetType))
// Remove token assets with zero balance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ type WithAccountsServiceCallback = (payload: {
mockConfigProvider: { config: Config };
mockLogger: Logger;
mockAssetsService: jest.Mocked<
Pick<AssetsService, 'fetchAccountAssets' | 'saveMany'>
Pick<
AssetsService,
'isAssetsMigrationEnabled' | 'fetchAccountAssets' | 'saveManyAndEmit'
>
>;
mockSnapClient: jest.Mocked<
Pick<SnapClient, 'getBip32Entropy' | 'listEntropySources'>
Expand Down Expand Up @@ -266,10 +269,14 @@ async function withAccountsService(
};

const mockAssetsService: jest.Mocked<
Pick<AssetsService, 'fetchAccountAssets' | 'saveMany'>
Pick<
AssetsService,
'isAssetsMigrationEnabled' | 'fetchAccountAssets' | 'saveManyAndEmit'
>
> = {
isAssetsMigrationEnabled: jest.fn().mockResolvedValue(false),
fetchAccountAssets: jest.fn().mockResolvedValue([]),
saveMany: jest.fn().mockResolvedValue(undefined),
saveManyAndEmit: jest.fn().mockResolvedValue(undefined),
};

const mockTransactionsService: jest.Mocked<
Expand Down Expand Up @@ -1055,7 +1062,7 @@ describe('AccountsService', () => {
expect(mockAssetsService.fetchAccountAssets).toHaveBeenCalledWith(
account,
);
expect(mockAssetsService.saveMany).toHaveBeenCalledWith(
expect(mockAssetsService.saveManyAndEmit).toHaveBeenCalledWith(
expect.arrayContaining(mockAssets),
);
expect(trackError).not.toHaveBeenCalled();
Expand Down Expand Up @@ -1083,7 +1090,7 @@ describe('AccountsService', () => {
activeNetworks: [Network.Mainnet, Network.Shasta],
};
const saveError = new Error('storage full');
mockAssetsService.saveMany.mockRejectedValue(saveError);
mockAssetsService.saveManyAndEmit.mockRejectedValue(saveError);

// Save failures are batch-level: tracked as their own error (not
// attributed to individual accounts), and the method still resolves.
Expand Down Expand Up @@ -1155,7 +1162,7 @@ describe('AccountsService', () => {
'sync-fail-id: Error: grpc unavailable',
);
// The healthy account's assets are still saved.
expect(mockAssetsService.saveMany).toHaveBeenCalledWith(
expect(mockAssetsService.saveManyAndEmit).toHaveBeenCalledWith(
healthyAssets,
);
},
Expand Down Expand Up @@ -1192,7 +1199,7 @@ describe('AccountsService', () => {
// still reported; the sync completes and the save still runs.
await accountsService.synchronize([account]);

expect(mockAssetsService.saveMany).toHaveBeenCalledWith([]);
expect(mockAssetsService.saveManyAndEmit).toHaveBeenCalledWith([]);
expect(trackError).toHaveBeenCalledTimes(1);
const tracked = (trackError as jest.Mock).mock.calls[0][0] as Error;
expect(tracked.message).toBe(
Expand Down Expand Up @@ -1225,7 +1232,33 @@ describe('AccountsService', () => {
expect(mockAssetsService.fetchAccountAssets).toHaveBeenCalledWith(
account,
);
expect(mockAssetsService.saveMany).toHaveBeenCalledWith([]);
expect(mockAssetsService.saveManyAndEmit).toHaveBeenCalledWith([]);
},
);
});

it('skips fetching and saving assets while the assets migration is active', async () => {
const account: TronKeyringAccount = {
id: 'sync-migration-id',
address: 'TSyncMigration123456789012345',
type: TrxAccountType.Eoa,
options: {},
methods: [],
scopes: [],
entropySource: 'e1',
derivationPath: "m/44'/195'/0'/0/0",
index: 0,
};

await withAccountsService(
async ({ accountsService, mockAssetsService }) => {
mockAssetsService.isAssetsMigrationEnabled.mockResolvedValue(true);

await accountsService.synchronize([account]);

expect(mockAssetsService.fetchAccountAssets).not.toHaveBeenCalled();
expect(mockAssetsService.saveManyAndEmit).not.toHaveBeenCalled();
expect(trackError).not.toHaveBeenCalled();
},
);
});
Expand Down Expand Up @@ -1451,7 +1484,35 @@ describe('AccountsService', () => {
expect(
mockTransactionsService.fetchNewTransactionsForAccount,
).toHaveBeenCalledTimes(1);
expect(mockAssetsService.saveMany).toHaveBeenCalledTimes(1);
expect(mockAssetsService.saveManyAndEmit).toHaveBeenCalledTimes(1);
expect(mockTransactionsService.saveMany).toHaveBeenCalledTimes(1);
},
);
});

it('skips asset syncing while the assets migration is active, but still syncs transactions', async () => {
const account = makeSyncAccount('migration-id', 0);

await withAccountsService(
async ({
accountsService,
mockConfigProvider,
mockAssetsService,
mockTransactionsService,
}) => {
mockConfigProvider.config = {
...MOCK_CONFIG,
activeNetworks: [Network.Mainnet],
};
mockAssetsService.isAssetsMigrationEnabled.mockResolvedValue(true);

await accountsService.synchronize([account]);

expect(mockAssetsService.fetchAccountAssets).not.toHaveBeenCalled();
expect(mockAssetsService.saveManyAndEmit).not.toHaveBeenCalled();
expect(
mockTransactionsService.fetchNewTransactionsForAccount,
).toHaveBeenCalledWith(Network.Mainnet, account);
expect(mockTransactionsService.saveMany).toHaveBeenCalledTimes(1);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,22 @@ export class AccountsService {
* Synchronizes only assets for the given accounts.
* This method can be called independently to sync assets without syncing transactions.
*
* A no-op while the assets migration is active: the AssetsController owns
* asset syncing then, so the snap neither fetches nor emits assets.
*
* Fetch failures are reported to Sentry in one `SynchronizationError`,
* attributed to their account; save failures are tracked standalone.
*
* @param accounts - The accounts to synchronize assets for.
*/
async synchronizeAssets(accounts: TronKeyringAccount[]): Promise<void> {
if (await this.#assetsService.isAssetsMigrationEnabled()) {
/**
* No-op when AssetsController is already handling assets
*/
return;
}

Comment on lines +521 to +527

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.

We are fine here that nothing is done at the moment?

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.

Yes indeed! If the AssetsMigration is on we are not supposed to do anything

const assetResponses = await Promise.allSettled(
accounts.map((account) =>
this.#assetsService.fetchAccountAssets(account),
Expand All @@ -532,7 +542,7 @@ export class AccountsService {
);

try {
await this.#assetsService.saveMany(assets);
await this.#assetsService.saveManyAndEmit(assets);
} catch (error) {
// Save failures are batch-level (not attributable to one account), so
// they are tracked standalone.
Expand Down
Loading
Loading