Skip to content
This repository was archived by the owner on Jul 31, 2026. It is now read-only.
Closed
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
8 changes: 4 additions & 4 deletions packages/snap/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ const config = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 68.73,
functions: 74.82,
lines: 81.74,
statements: 81.72,
branches: 69.35,
functions: 76.08,
lines: 82.03,
statements: 82.01,
},
},

Expand Down
2 changes: 1 addition & 1 deletion packages/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/snap-tron-wallet.git"
},
"source": {
"shasum": "iqzrIMsXdwSkcx/1uHjSll/HZ+3Ot9KOXHF42auXXbU=",
"shasum": "N2OkDab/a9f409c+eaizFXQam3uFx+CZ2JaBkvAcWKQ=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
29 changes: 29 additions & 0 deletions packages/snap/src/clients/wallet/WalletMessengerClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { WalletMessengerClient } from './WalletMessengerClient';
import type { WalletMessenger } from '../../types/wallet-messenger';

describe('WalletMessengerClient', () => {
it('calls AssetsController:getAsset with typed arguments', async () => {
const asset = {
amount: '1000000',
metadata: {
symbol: 'TRX',
name: 'Tron',
decimals: 6,
},
};
const messenger: WalletMessenger = {
call: jest.fn().mockResolvedValue(asset),
};
const client = new WalletMessengerClient(messenger);

expect(
await client.getAsset('account-id', 'tron:728126428/slip44:195'),
).toStrictEqual(asset);

expect(messenger.call).toHaveBeenCalledWith(
'AssetsController:getAsset',
'account-id',
'tron:728126428/slip44:195',
);
});
});
71 changes: 71 additions & 0 deletions packages/snap/src/clients/wallet/WalletMessengerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type {
RemoteFeatureFlagControllerState,
SnapAssetUpdate,
WalletMessenger,
WalletMessengerActionType,
WalletMessengerCallArgs,
WalletMessengerCallReturn,
} from '../../types/wallet-messenger';

/** Thin, typed wrapper around the messenger endowment supplied by MetaMask. */
export class WalletMessengerClient {
readonly #messenger: WalletMessenger | undefined;

constructor(messenger: WalletMessenger | undefined) {
this.#messenger = messenger;
}

isAvailable(): boolean {
return typeof this.#messenger?.call === 'function';
}

call<Action extends WalletMessengerActionType>(
action: Action,
...args: WalletMessengerCallArgs<Action>
): WalletMessengerCallReturn<Action> {
const messenger = this.#messenger;

if (!messenger || typeof messenger.call !== 'function') {
throw new Error('Wallet messenger is not available');
}

return messenger.call(action, ...args);
}

async upsertSnapAssets(
accountId: string,
chainId: string,
assets: SnapAssetUpdate[],
): Promise<void> {
await this.call(
'AssetsController:upsertSnapAssets',
accountId,
chainId,
assets,
);
}

async getAsset(
accountId: string,
assetId: string,
): Promise<
| {
amount: string;
metadata?: {
symbol: string;
name: string;
decimals: number;
image?: string;
};
}
| undefined
> {
return await this.call('AssetsController:getAsset', accountId, assetId);
}

async getRemoteFeatureFlagState(): Promise<RemoteFeatureFlagControllerState> {
return await Promise.resolve(
this.call('RemoteFeatureFlagController:getState'),
);
}
}
13 changes: 13 additions & 0 deletions packages/snap/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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 { WalletMessengerClient } from './clients/wallet/WalletMessengerClient';
import { AssetsHandler } from './handlers/assets';
import { ClientRequestHandler } from './handlers/clientRequest/clientRequest';
import { CronHandler } from './handlers/cronjob';
Expand All @@ -19,6 +20,8 @@ import { AssetsRepository } from './services/assets/AssetsRepository';
import { AssetsService } from './services/assets/AssetsService';
import { ConfigProvider } from './services/config';
import { ConfirmationHandler } from './services/confirmation/ConfirmationHandler';
import { createStageResolver } from './services/migration/stage';
import { TronAssetsControllerAdapter } from './services/migration/TronAssetsControllerAdapter';
import { FeeCalculatorService } from './services/send/FeeCalculatorService';
import { SendService } from './services/send/SendService';
import { StakingService } from './services/staking/StakingService';
Expand All @@ -29,6 +32,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 { WalletMessenger } from './types/wallet-messenger';
import logger, { noOpLogger } from './utils/logger';

/**
Expand All @@ -54,6 +58,15 @@ const state = new State({
});

const snapClient = new SnapClient({ logger });
const walletMessengerClient = new WalletMessengerClient(
(globalThis as unknown as { messenger?: WalletMessenger }).messenger,
);
const resolveMigrationStage = createStageResolver(walletMessengerClient);
export const tronAssetsControllerAdapter = new TronAssetsControllerAdapter(
walletMessengerClient,
resolveMigrationStage,
);
export { resolveMigrationStage };

// Repositories - depend on State
const accountsRepository = new AccountsRepository(state);
Expand Down
104 changes: 104 additions & 0 deletions packages/snap/src/services/migration/TronAssetsControllerAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { parseCaipAssetType } from '@metamask/utils';
import type { CaipAssetType } from '@metamask/utils';

import { MigrationStage, type StageResolver } from './stage';
import type { WalletMessengerClient } from '../../clients/wallet/WalletMessengerClient';
import { type Network, TokenMetadata } from '../../constants';
import type { AssetEntity } from '../../entities/assets';
import type { SnapAssetUpdate } from '../../types/wallet-messenger';
import { toUiAmount } from '../../utils/conversion';

/**
* Boundary between Tron asset synchronization and the host AssetsController.
*
* Resource and staking pseudo-assets are deliberately not filtered here: the
* controller receives the complete snapshot supplied by the Snap.
*/
export class TronAssetsControllerAdapter {
readonly #walletMessengerClient: WalletMessengerClient;

readonly #resolveStage: StageResolver;

#currentStage: MigrationStage = MigrationStage.Off;

constructor(
walletMessengerClient: WalletMessengerClient,
resolveStage: StageResolver,
) {
this.#walletMessengerClient = walletMessengerClient;
this.#resolveStage = resolveStage;
}

async getMigrationStage(chainId: string): Promise<MigrationStage> {
await this.resolveAndSetStage(chainId);
return this.getCurrentStage();
}

async resolveAndSetStage(chainId: string): Promise<void> {
this.#currentStage = await this.#resolveStage(chainId);
}

getCurrentStage(): MigrationStage {
return this.#currentStage;
}

async pushAssetSnapshot(
accountId: string,
chainId: string,
assets: SnapAssetUpdate[],
): Promise<void> {
await this.#walletMessengerClient.upsertSnapAssets(
accountId,
chainId,
assets,
);
}

async getAsset(
accountId: string,
assetId: string,
): Promise<AssetEntity | null> {
const result = await this.#walletMessengerClient.getAsset(
accountId,
assetId,
);

if (!result) {
return null;
}

return this.#mapToAssetEntity(accountId, assetId, result);
}

#mapToAssetEntity(
accountId: string,
assetId: string,
result: {
amount: string;
metadata?: {
symbol: string;
name: string;
decimals: number;
image?: string;
};
},
): AssetEntity {
const { chainId } = parseCaipAssetType(assetId as CaipAssetType);
const knownMetadata = TokenMetadata[assetId as keyof typeof TokenMetadata];

const decimals = result.metadata?.decimals ?? knownMetadata?.decimals ?? 0;
const symbol = result.metadata?.symbol ?? knownMetadata?.symbol ?? '';
const iconUrl = result.metadata?.image ?? knownMetadata?.iconUrl ?? '';

return {
assetType: assetId,
keyringAccountId: accountId,
network: chainId as Network,
symbol,
decimals,
rawAmount: result.amount,
uiAmount: toUiAmount(result.amount, decimals).toString(),
iconUrl,
} as AssetEntity;
}
}
Loading
Loading