From 5ab0e9ce509b6be07ad8054509d52ba705b98595 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Thu, 1 Oct 2026 18:03:05 +0100 Subject: [PATCH] refactor(tron-wallet-snap): guarantee local type consistency for networks --- .../src/clients/token-api/TokenApiClient.ts | 7 +-- .../src/clients/tron-http/TronHttpClient.ts | 6 +-- .../src/clients/trongrid/TrongridApiClient.ts | 6 +-- .../tron-wallet-snap/src/constants/index.ts | 14 ++++++ .../src/entities/tronKeyringAccount.ts | 7 +++ .../src/handlers/cronjob/cronjob.tsx | 17 ++++--- .../src/handlers/keyring/keyring.ts | 19 ++++---- .../services/accounts/AccountsRepository.ts | 34 ++++++------- .../src/services/accounts/AccountsService.ts | 48 +++++++++---------- .../src/services/assets/types.ts | 21 ++++---- .../src/services/config/ConfigProvider.ts | 2 +- .../src/services/staking/StakingService.ts | 14 +++--- .../src/services/state/stateTypes.ts | 4 +- .../src/services/wallet/WalletService.ts | 14 +++--- .../views/ConfirmSignMessage/render.tsx | 7 ++- .../views/ConfirmSignTransaction/render.tsx | 23 +++++---- packages/tron-wallet-snap/src/utils/caip.ts | 40 ++++++++++++++++ .../src/utils/getAssetNetwork.ts | 17 +++++++ .../src/validation/structs.ts | 13 +++++ 19 files changed, 198 insertions(+), 115 deletions(-) create mode 100644 packages/tron-wallet-snap/src/entities/tronKeyringAccount.ts create mode 100644 packages/tron-wallet-snap/src/utils/caip.ts create mode 100644 packages/tron-wallet-snap/src/utils/getAssetNetwork.ts diff --git a/packages/tron-wallet-snap/src/clients/token-api/TokenApiClient.ts b/packages/tron-wallet-snap/src/clients/token-api/TokenApiClient.ts index 9d4119803..70ff9aa57 100644 --- a/packages/tron-wallet-snap/src/clients/token-api/TokenApiClient.ts +++ b/packages/tron-wallet-snap/src/clients/token-api/TokenApiClient.ts @@ -3,7 +3,8 @@ import type { Logger } from '@metamask/snap-networks-utils'; import type { FungibleAssetMetadata } from '@metamask/snaps-sdk'; import { array, assert } from '@metamask/superstruct'; import type { Infer } from '@metamask/superstruct'; -import { CaipAssetTypeStruct, parseCaipAssetType } from '@metamask/utils'; +import { CaipAssetTypeStruct } from '@metamask/utils'; +import { parseTronCaipAssetType } from '../../utils/caip'; import { Network, SNAP_OWNED_ASSETS } from '../../constants'; import type { TokenCaipAssetType } from '../../services/assets/types'; @@ -96,8 +97,8 @@ export class TokenApiClient { if (SNAP_OWNED_ASSETS.includes(assetType)) { return false; } - const { chainId } = parseCaipAssetType(assetType); - return TokenApiClient.supportedNetworks.includes(chainId as Network); + const { chainId } = parseTronCaipAssetType(assetType); + return TokenApiClient.supportedNetworks.includes(chainId); }); if (supportedAssetTypes.length !== assetTypes.length) { diff --git a/packages/tron-wallet-snap/src/clients/tron-http/TronHttpClient.ts b/packages/tron-wallet-snap/src/clients/tron-http/TronHttpClient.ts index 7a4bfbed3..d372ad31a 100644 --- a/packages/tron-wallet-snap/src/clients/tron-http/TronHttpClient.ts +++ b/packages/tron-wallet-snap/src/clients/tron-http/TronHttpClient.ts @@ -1,7 +1,7 @@ import { buildUrl } from '@metamask/snap-networks-utils'; import { assert } from '@metamask/superstruct'; -import type { Network } from '../../constants'; +import { Network } from '../../constants'; import type { ConfigProvider } from '../../services/config'; import { hexToString } from '../../utils/hex'; import { @@ -43,14 +43,14 @@ export class TronHttpClient { const { baseUrls } = configProvider.config.tronHttpApi; // Initialize clients for all networks - Object.entries(baseUrls).forEach(([network, baseUrl]) => { + Object.values(Network).forEach((network) => { const headers: Record = { 'Content-Type': 'application/json', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Origin': '*', }; - this.#clients.set(network as Network, { baseUrl, headers }); + this.#clients.set(network, { baseUrl: baseUrls[network], headers }); }); } diff --git a/packages/tron-wallet-snap/src/clients/trongrid/TrongridApiClient.ts b/packages/tron-wallet-snap/src/clients/trongrid/TrongridApiClient.ts index fb852a9a0..73b3034ca 100644 --- a/packages/tron-wallet-snap/src/clients/trongrid/TrongridApiClient.ts +++ b/packages/tron-wallet-snap/src/clients/trongrid/TrongridApiClient.ts @@ -6,7 +6,7 @@ import type { } from '@metamask/snap-networks-utils'; import { assert } from '@metamask/superstruct'; -import type { Network } from '../../constants'; +import { Network } from '../../constants'; import type { ConfigProvider } from '../../services/config'; import type { TronHttpClient } from '../tron-http/TronHttpClient'; import type { ChainParameter } from '../tron-http/types'; @@ -80,14 +80,14 @@ export class TrongridApiClient { const { baseUrls } = configProvider.config.trongridApi; // Initialize clients for all networks - Object.entries(baseUrls).forEach(([network, baseUrl]) => { + Object.values(Network).forEach((network) => { const headers: Record = { 'Content-Type': 'application/json', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Origin': '*', }; - this.#clients.set(network as Network, { baseUrl, headers }); + this.#clients.set(network, { baseUrl: baseUrls[network], headers }); }); this.#tronHttpClient = tronHttpClient; diff --git a/packages/tron-wallet-snap/src/constants/index.ts b/packages/tron-wallet-snap/src/constants/index.ts index b288f05be..37ebc57f7 100644 --- a/packages/tron-wallet-snap/src/constants/index.ts +++ b/packages/tron-wallet-snap/src/constants/index.ts @@ -1,3 +1,4 @@ +import { InvalidParamsError } from '@metamask/snaps-sdk'; import { BigNumber } from 'bignumber.js'; export const ZERO = BigNumber(0); @@ -66,6 +67,19 @@ export const Network = { export type Network = (typeof Network)[keyof typeof Network]; +const NETWORK_SCOPES: readonly string[] = Object.values(Network); + +export const isSupportedNetwork = (scope: string): scope is Network => + NETWORK_SCOPES.includes(scope); + +export const assertSupportedNetwork = (scope: string): Network => { + if (!isSupportedNetwork(scope)) { + throw new InvalidParamsError(`Unsupported network: "${scope}"`) as Error; + } + + return scope; +}; + export const KnownCaip19Id = { TrxMainnet: `${Network.Mainnet}/slip44:195`, TrxNile: `${Network.Nile}/slip44:195`, diff --git a/packages/tron-wallet-snap/src/entities/tronKeyringAccount.ts b/packages/tron-wallet-snap/src/entities/tronKeyringAccount.ts new file mode 100644 index 000000000..2c4050d8d --- /dev/null +++ b/packages/tron-wallet-snap/src/entities/tronKeyringAccount.ts @@ -0,0 +1,7 @@ +import type { ExtendedKeyringAccount } from '@metamask/snap-networks-utils'; + +import type { Network } from '../constants'; + +export type TronKeyringAccount = Omit & { + scopes: Network[]; +}; diff --git a/packages/tron-wallet-snap/src/handlers/cronjob/cronjob.tsx b/packages/tron-wallet-snap/src/handlers/cronjob/cronjob.tsx index 65ab9b813..f21e4c8f4 100644 --- a/packages/tron-wallet-snap/src/handlers/cronjob/cronjob.tsx +++ b/packages/tron-wallet-snap/src/handlers/cronjob/cronjob.tsx @@ -32,6 +32,8 @@ import type { ConfirmSignTransactionContext } from '../../ui/confirmation/views/ import { ConfirmTransactionRequest } from '../../ui/confirmation/views/ConfirmTransactionRequest/ConfirmTransactionRequest'; import { CONFIRM_TRANSACTION_INTERFACE_NAME } from '../../ui/confirmation/views/ConfirmTransactionRequest/types'; import type { ConfirmTransactionRequestContext } from '../../ui/confirmation/views/ConfirmTransactionRequest/types'; +import { TrackTransactionParamsStruct } from '../../validation/structs'; +import { validateRequest } from '../../validation/validators'; export const CronjobMethod = { ContinuouslySynchronizeSelectedAccounts: @@ -142,15 +144,7 @@ export class CronHandler { await this.refreshSignTransaction(); break; case BackgroundEventMethod.TrackTransaction: - await this.trackTransaction( - params as { - txId: string; - scope: Network; - accountIds: string[]; - attempt: number; - transactionType?: TransactionType; - }, - ); + await this.handleTrackTransaction(request); break; default: throw new Error(`Unknown cronjob method: ${method}`); @@ -654,6 +648,11 @@ export class CronHandler { } } + async handleTrackTransaction(request: JsonRpcRequest): Promise { + validateRequest(request.params, TrackTransactionParamsStruct); + await this.trackTransaction(request.params); + } + /** * Background job to track a transaction's confirmation status. * Continues polling until confirmed, then syncs accounts and emits finalized event. diff --git a/packages/tron-wallet-snap/src/handlers/keyring/keyring.ts b/packages/tron-wallet-snap/src/handlers/keyring/keyring.ts index 01507b548..cf5a8a1f2 100644 --- a/packages/tron-wallet-snap/src/handlers/keyring/keyring.ts +++ b/packages/tron-wallet-snap/src/handlers/keyring/keyring.ts @@ -19,10 +19,7 @@ import { asStrictKeyringAccount, validateOrigin, } from '@metamask/snap-networks-utils'; -import type { - ExtendedKeyringAccount, - Logger, -} from '@metamask/snap-networks-utils'; +import type { Logger } from '@metamask/snap-networks-utils'; import { InvalidParamsError, SnapError, @@ -39,7 +36,7 @@ import { sortBy } from 'lodash'; import type { SnapClient } from '../../clients/snap/SnapClient'; import { ESSENTIAL_ASSETS } from '../../constants'; -import type { Network } from '../../constants'; +import type { TronKeyringAccount } from '../../entities/tronKeyringAccount'; import { originPermissions } from '../../permissions'; import type { AccountsService } from '../../services/accounts/AccountsService'; import type { AssetsService } from '../../services/assets/AssetsService'; @@ -56,6 +53,7 @@ import { ListAccountAssetsStruct, ListAccountTransactionsStruct, PrivateKeyHexStruct, + ResolveAccountAddressStruct, SignTransactionRequestStruct, TronKeyringRequestStruct, } from '../../validation/structs'; @@ -111,7 +109,7 @@ export class KeyringHandler implements KeyringSnapRpc { return result ?? null; } - async #listAccounts(): Promise { + async #listAccounts(): Promise { try { const keyringAccounts = await this.#accountsService.getAll(); @@ -133,7 +131,7 @@ export class KeyringHandler implements KeyringSnapRpc { async #getAccount( accountId: string, - ): Promise { + ): Promise { try { const account = (await this.#accountsService.findById(accountId)) ?? undefined; @@ -158,7 +156,7 @@ export class KeyringHandler implements KeyringSnapRpc { } } - async #getAccountOrThrow(accountId: string): Promise { + async #getAccountOrThrow(accountId: string): Promise { const account = await this.#getAccount(accountId); if (!account) { @@ -321,6 +319,9 @@ export class KeyringHandler implements KeyringSnapRpc { scope: CaipChainId, request: JsonRpcRequest, ): Promise { + const params = { scope }; + validateRequest(params, ResolveAccountAddressStruct); + this.#logger.info('Resolving account address', { scope, request }); // Get all keyring accounts @@ -329,7 +330,7 @@ export class KeyringHandler implements KeyringSnapRpc { // Resolve the address using the wallet service const caip10Address = await this.#walletService.resolveAccountAddress( keyringAccounts, - scope as Network, + params.scope, request, ); diff --git a/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.ts b/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.ts index fb5b386ff..45a26a253 100644 --- a/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.ts +++ b/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.ts @@ -1,9 +1,7 @@ import type { EntropySourceId } from '@metamask/keyring-api'; -import type { - ExtendedKeyringAccount, - IStateManager, -} from '@metamask/snap-networks-utils'; +import type { IStateManager } from '@metamask/snap-networks-utils'; +import type { TronKeyringAccount } from '../../entities/tronKeyringAccount'; import type { UnencryptedStateValue } from '../state/stateTypes'; /** @@ -17,7 +15,7 @@ type AccountCreationRange = { to: number; }; -type KeyringAccountsState = Record; +type KeyringAccountsState = Record; /** * Result of merging accounts into `keyringAccounts`. @@ -27,8 +25,8 @@ type KeyringAccountsState = Record; * conflict losers are omitted (their winners are present in `merged`). */ export type KeyringAccountsMergeResult = { - merged: Record; - added: Record; + merged: Record; + added: Record; }; /** @@ -37,7 +35,7 @@ export type KeyringAccountsMergeResult = { * @param account - The account to key. * @returns A stable conflict key for the account. */ -function getAccountIndexKey(account: ExtendedKeyringAccount): string { +function getAccountIndexKey(account: TronKeyringAccount): string { return `${account.entropySource}:${account.index}`; } @@ -51,7 +49,7 @@ function getAccountIndexKey(account: ExtendedKeyringAccount): string { function findAccountByIndexKey( accounts: KeyringAccountsState, indexKey: string, -): ExtendedKeyringAccount | undefined { +): TronKeyringAccount | undefined { return Object.values(accounts).find( (account) => getAccountIndexKey(account) === indexKey, ); @@ -104,7 +102,7 @@ export class AccountsRepository { * * @returns All accounts from the state. */ - async getAll(): Promise { + async getAll(): Promise { const accounts = await this.#state.getKey< UnencryptedStateValue['keyringAccounts'] >(this.#storageKey); @@ -122,7 +120,7 @@ export class AccountsRepository { async findByEntropySourceAndRange( entropySource: EntropySourceId, range: AccountCreationRange, - ): Promise { + ): Promise { const accounts = await this.getAll(); return accounts @@ -135,7 +133,7 @@ export class AccountsRepository { .sort((first, second) => first.index - second.index); } - async findById(id: string): Promise { + async findById(id: string): Promise { const accounts = await this.getAll(); return accounts.find((account) => account.id === id) ?? null; } @@ -147,13 +145,13 @@ export class AccountsRepository { * @returns The matching accounts. Result ordering follows stored account * ordering, not input ordering. */ - async findByIds(ids: string[]): Promise { + async findByIds(ids: string[]): Promise { const accounts = await this.getAll(); const normalizedIds = new Set(); ids.forEach((id) => normalizedIds.add(id.toLowerCase())); - const matchedAccounts: ExtendedKeyringAccount[] = []; + const matchedAccounts: TronKeyringAccount[] = []; accounts.forEach((account) => { if (normalizedIds.has(account.id.toLowerCase())) { matchedAccounts.push(account); @@ -163,15 +161,13 @@ export class AccountsRepository { return matchedAccounts; } - async findByAddress(address: string): Promise { + async findByAddress(address: string): Promise { const accounts = await this.getAll(); return accounts.find((account) => account.address === address) ?? null; } - async create( - account: ExtendedKeyringAccount, - ): Promise { + async create(account: TronKeyringAccount): Promise { let persistedAccount = account; await this.#state.setKeyWith( @@ -204,7 +200,7 @@ export class AccountsRepository { * without re-reading state. */ async mergeKeyringAccounts( - newAccounts: Record, + newAccounts: Record, ): Promise { let result: KeyringAccountsMergeResult = { merged: {}, added: {} }; diff --git a/packages/tron-wallet-snap/src/services/accounts/AccountsService.ts b/packages/tron-wallet-snap/src/services/accounts/AccountsService.ts index d5bcfe83a..8cb39ccb5 100644 --- a/packages/tron-wallet-snap/src/services/accounts/AccountsService.ts +++ b/packages/tron-wallet-snap/src/services/accounts/AccountsService.ts @@ -16,10 +16,7 @@ import { asStrictKeyringAccount, getSyncFailuresFromSettledResult, } from '@metamask/snap-networks-utils'; -import type { - ExtendedKeyringAccount, - Logger, -} from '@metamask/snap-networks-utils'; +import type { Logger } from '@metamask/snap-networks-utils'; import { assert } from '@metamask/superstruct'; import { hexToBytes } from '@metamask/utils'; import { computeAddress } from 'ethers'; @@ -27,7 +24,9 @@ import { TronWeb } from 'tronweb'; import snapManifest from '../../../snap.manifest.json'; import type { SnapClient } from '../../clients/snap/SnapClient'; -import { Network } from '../../constants'; +import { assertSupportedNetwork } from '../../constants'; +import type { Network } from '../../constants'; +import type { TronKeyringAccount } from '../../entities/tronKeyringAccount'; import type { DerivedTronKeypair } from '../../utils/deriveTronFromCoinTypeNode'; import { createTronBip44AddressDeriver, @@ -50,9 +49,10 @@ const CURVE = 'secp256k1' as const; */ const MAX_BIP44_ACCOUNT_INDEX = 0x7fffffff; -export const SUPPORTED_SCOPES = snapManifest.initialPermissions[ - 'endowment:keyring' -].capabilities.scopes as readonly Network[]; +export const SUPPORTED_SCOPES: readonly Network[] = + snapManifest.initialPermissions['endowment:keyring'].capabilities.scopes.map( + assertSupportedNetwork, + ); /** * Range of inclusive account indices to create. @@ -238,12 +238,12 @@ export class AccountsService { * @returns One derivation result per account, in input order. */ async deriveTronKeypairs( - accounts: ExtendedKeyringAccount[], + accounts: TronKeyringAccount[], ): Promise { const results: DerivedTronKeypairBatchResult[] = new Array(accounts.length); const accountsByEntropySource = new Map< EntropySourceId, - { index: number; account: ExtendedKeyringAccount }[] + { index: number; account: TronKeyringAccount }[] >(); accounts.forEach((account, index) => { @@ -345,7 +345,7 @@ export class AccountsService { ]); const readAndEntropyMs = Date.now() - startMs; - const allAccounts = new Map(); + const allAccounts = new Map(); for (const account of existingAccounts) { allAccounts.set(account.index, account); } @@ -357,7 +357,7 @@ export class AccountsService { } } - const newAccounts: Record = {}; + const newAccounts: Record = {}; let created = 0; let deriveMs = 0; let mergeMs = 0; @@ -371,14 +371,14 @@ export class AccountsService { AccountsService.getDefaultDerivationPath(groupIndex); const { address } = await tronAddressDeriver(groupIndex); - const tronKeyringAccount: ExtendedKeyringAccount = { + const tronKeyringAccount: TronKeyringAccount = { id, entropySource, derivationPath, index: groupIndex, type: TrxAccountType.Eoa, address, - scopes: SUPPORTED_SCOPES as unknown as Network[], + scopes: [...SUPPORTED_SCOPES], options: { entropy: { type: 'mnemonic', @@ -441,11 +441,11 @@ export class AccountsService { return result; } - async getAll(): Promise { + async getAll(): Promise { return this.#accountsRepository.getAll(); } - async getAllSelected(): Promise { + async getAllSelected(): Promise { const [allAccounts, selectedAccountIds] = await Promise.all([ this.#accountsRepository.getAll(), getSelectedAccounts(snap), @@ -456,7 +456,7 @@ export class AccountsService { ); } - async findById(id: string): Promise { + async findById(id: string): Promise { return this.#accountsRepository.findById(id); } @@ -468,7 +468,7 @@ export class AccountsService { * @returns The account if found. * @throws {Error} If the account is not found. */ - async findByIdOrThrow(id: string): Promise { + async findByIdOrThrow(id: string): Promise { const account = await this.#accountsRepository.findById(id); if (!account) { @@ -487,7 +487,7 @@ export class AccountsService { * @param ids - Account IDs to resolve. * @returns The matching accounts. */ - async findByIds(ids: string[]): Promise { + async findByIds(ids: string[]): Promise { const accounts = await this.#accountsRepository.findByIds(ids); if (ids.length !== accounts.length) { @@ -497,7 +497,7 @@ export class AccountsService { return accounts; } - async findByAddress(address: string): Promise { + async findByAddress(address: string): Promise { return this.#accountsRepository.findByAddress(address); } @@ -514,7 +514,7 @@ export class AccountsService { * * @param accounts - The accounts to synchronize assets for. */ - async synchronizeAssets(accounts: ExtendedKeyringAccount[]): Promise { + async synchronizeAssets(accounts: TronKeyringAccount[]): Promise { const scopes = this.#configProvider.config.activeNetworks; const combinations = accounts.flatMap((account) => scopes.map((scope) => ({ account, scope })), @@ -558,9 +558,7 @@ export class AccountsService { * * @param accounts - The accounts to synchronize transactions for. */ - async synchronizeTransactions( - accounts: ExtendedKeyringAccount[], - ): Promise { + async synchronizeTransactions(accounts: TronKeyringAccount[]): Promise { const scopes = this.#configProvider.config.activeNetworks; const combinations = accounts.flatMap((account) => scopes.map((scope) => ({ account, scope })), @@ -604,7 +602,7 @@ export class AccountsService { * * @param accounts - The accounts to synchronize. */ - async synchronize(accounts: ExtendedKeyringAccount[]): Promise { + async synchronize(accounts: TronKeyringAccount[]): Promise { // Sync triggers stack up (60s cronjob, a background event scheduled by // every `setSelectedAccounts` call, post-transaction refreshes), so // concurrent invocations for the same accounts share one run instead of diff --git a/packages/tron-wallet-snap/src/services/assets/types.ts b/packages/tron-wallet-snap/src/services/assets/types.ts index d03b20a4e..ebbe158dc 100644 --- a/packages/tron-wallet-snap/src/services/assets/types.ts +++ b/packages/tron-wallet-snap/src/services/assets/types.ts @@ -1,5 +1,4 @@ import { CaipAssetTypeStruct } from '@metamask/keyring-api'; -import type { TrxScope } from '@metamask/keyring-api'; import { pattern } from '@metamask/superstruct'; import type { TronAccount } from '../../clients/trongrid/types'; @@ -12,17 +11,19 @@ export type StakedData = { }; export type NativeCaipAssetType = `${Network}/slip44:195`; -export type StakedCaipAssetType = - `${TrxScope}/slip44:195-staked-for-${'energy' | 'bandwidth'}`; +export type StakedCaipAssetType = `${Network}/slip44:195-staked-for-${ + | 'energy' + | 'bandwidth'}`; export type ReadyForWithdrawalCaipAssetType = - `${TrxScope}/slip44:195-ready-for-withdrawal`; + `${Network}/slip44:195-ready-for-withdrawal`; export type StakingRewardsCaipAssetType = - `${TrxScope}/slip44:195-staking-rewards`; -export type InLockPeriodCaipAssetType = `${TrxScope}/slip44:195-in-lock-period`; -export type ResourceCaipAssetType = - `${TrxScope}/slip44:${'energy' | 'bandwidth'}`; -export type TokenCaipAssetType = `${TrxScope}/${'trc10' | 'trc20'}:${string}`; -export type NftCaipAssetType = `${TrxScope}/trc721:${string}`; + `${Network}/slip44:195-staking-rewards`; +export type InLockPeriodCaipAssetType = `${Network}/slip44:195-in-lock-period`; +export type ResourceCaipAssetType = `${Network}/slip44:${ + | 'energy' + | 'bandwidth'}`; +export type TokenCaipAssetType = `${Network}/${'trc10' | 'trc20'}:${string}`; +export type NftCaipAssetType = `${Network}/trc721:${string}`; /** * Validates a TRON native CAIP-19 ID (e.g., "tron:728126428/slip44:195") diff --git a/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts b/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts index d78e5dd8d..f8ea6fb07 100644 --- a/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts +++ b/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts @@ -12,7 +12,7 @@ import { Duration } from '@metamask/utils'; import { Network } from '../../constants'; -const NetworkStruct = enums(Object.values(Network) as [Network, ...Network[]]); +const NetworkStruct = enums(Object.values(Network)); /** * The default base URLs for the price, token, and static APIs, used when diff --git a/packages/tron-wallet-snap/src/services/staking/StakingService.ts b/packages/tron-wallet-snap/src/services/staking/StakingService.ts index 8281308e4..0927f7fda 100644 --- a/packages/tron-wallet-snap/src/services/staking/StakingService.ts +++ b/packages/tron-wallet-snap/src/services/staking/StakingService.ts @@ -2,7 +2,7 @@ import type { ExtendedKeyringAccount, Logger, } from '@metamask/snap-networks-utils'; -import { parseCaipAssetType } from '@metamask/utils'; +import { parseTronCaipAssetType } from '../../utils/caip'; import { BigNumber } from 'bignumber.js'; import type { Types as TronwebTypes } from 'tronweb'; @@ -58,13 +58,13 @@ export class StakingService { */ srNodeAddress?: string; }): Promise { - const { chainId } = parseCaipAssetType(assetId); + const { chainId: scope } = parseTronCaipAssetType(assetId); const amountInSun = Number(trxToSun(amount)); const availableVotes = amount.integerValue(BigNumber.ROUND_DOWN).toNumber(); const voteRecipient = srNodeAddress ?? CONSENSYS_SR_NODE_ADDRESS; this.#logger.info( - `Staking ${amount.toString()} ${assetId} for ${purpose} for ${account.address} on ${chainId}...`, + `Staking ${amount.toString()} ${assetId} for ${purpose} for ${account.address} on ${scope}...`, ); await executeOnChainActions({ @@ -72,7 +72,7 @@ export class StakingService { tronWebFactory: this.#tronWebFactory, snapClient: this.#snapClient, account, - scope: chainId as Network, + scope, buildTransactions: async (tronWeb) => [ await tronWeb.transactionBuilder.freezeBalanceV2( amountInSun, @@ -96,7 +96,7 @@ export class StakingService { assetId: StakedCaipAssetType; amount: BigNumber; }): Promise { - const { chainId } = parseCaipAssetType(assetId); + const { chainId: scope } = parseTronCaipAssetType(assetId); /** * Check which resource we are unstaking. @@ -134,7 +134,7 @@ export class StakingService { const amountInSun = Number(trxToSun(amount)); this.#logger.info( - `Unstaking ${amount.toString()} ${assetId} for ${account.address} on ${chainId}...`, + `Unstaking ${amount.toString()} ${assetId} for ${account.address} on ${scope}...`, ); await executeOnChainActions({ @@ -142,7 +142,7 @@ export class StakingService { tronWebFactory: this.#tronWebFactory, snapClient: this.#snapClient, account, - scope: chainId as Network, + scope, buildTransactions: async (tronWeb) => [ await tronWeb.transactionBuilder.unfreezeBalanceV2( amountInSun, diff --git a/packages/tron-wallet-snap/src/services/state/stateTypes.ts b/packages/tron-wallet-snap/src/services/state/stateTypes.ts index f6648e56a..b9e214b2f 100644 --- a/packages/tron-wallet-snap/src/services/state/stateTypes.ts +++ b/packages/tron-wallet-snap/src/services/state/stateTypes.ts @@ -1,10 +1,10 @@ import type { Transaction } from '@metamask/keyring-api'; -import type { ExtendedKeyringAccount } from '@metamask/snap-networks-utils'; import type { AssetEntity } from '../../entities/assets'; +import type { TronKeyringAccount } from '../../entities/tronKeyringAccount'; export type UnencryptedStateValue = { - keyringAccounts: Record; + keyringAccounts: Record; assets: Record; transactions: Record; mapInterfaceNameToId: Record; diff --git a/packages/tron-wallet-snap/src/services/wallet/WalletService.ts b/packages/tron-wallet-snap/src/services/wallet/WalletService.ts index 3f7fbab41..b4dc82e58 100644 --- a/packages/tron-wallet-snap/src/services/wallet/WalletService.ts +++ b/packages/tron-wallet-snap/src/services/wallet/WalletService.ts @@ -1,14 +1,12 @@ import type { ResolvedAccountAddress } from '@metamask/keyring-api'; -import type { - ExtendedKeyringAccount, - Logger, -} from '@metamask/snap-networks-utils'; +import type { Logger } from '@metamask/snap-networks-utils'; import { SnapError } from '@metamask/snaps-sdk'; import type { Json, JsonRpcRequest } from '@metamask/snaps-sdk'; import { bytesToHex, hexToBytes, sha256 } from '@metamask/utils'; import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory'; import type { Network } from '../../constants'; +import type { TronKeyringAccount } from '../../entities/tronKeyringAccount'; import { TronMultichainErrors, TronMultichainMethod, @@ -67,7 +65,7 @@ export class WalletService { method, params, }: { - account: ExtendedKeyringAccount; + account: TronKeyringAccount; scope: Network; method: TronMultichainMethod; params: Json; @@ -135,7 +133,7 @@ export class WalletService { scope, params, }: { - account: ExtendedKeyringAccount; + account: TronKeyringAccount; scope: Network; params: Json; }): Promise<{ signature: string }> { @@ -197,7 +195,7 @@ export class WalletService { scope, params, }: { - account: ExtendedKeyringAccount; + account: TronKeyringAccount; scope: Network; params: Json; }): Promise<{ signature: string }> { @@ -292,7 +290,7 @@ export class WalletService { * @throws If the request is invalid or no matching account is found. */ async resolveAccountAddress( - keyringAccounts: ExtendedKeyringAccount[], + keyringAccounts: TronKeyringAccount[], scope: Network, request: JsonRpcRequest, ): Promise { diff --git a/packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignMessage/render.tsx b/packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignMessage/render.tsx index 865d9b89c..4515d0ee8 100644 --- a/packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignMessage/render.tsx +++ b/packages/tron-wallet-snap/src/ui/confirmation/views/ConfirmSignMessage/render.tsx @@ -1,14 +1,13 @@ -import type { KeyringRequest } from '@metamask/keyring-api'; import type { ExtendedKeyringAccount } from '@metamask/snap-networks-utils'; import type { DialogResult } from '@metamask/snaps-sdk'; import { assert } from '@metamask/superstruct'; -import type { Network } from '../../../../constants'; import snapContext from '../../../../context'; import { TRX_IMAGE_SVG } from '../../../../static/tron-logo'; import { formatOrigin } from '../../../../utils/formatOrigin'; import { FALLBACK_LANGUAGE } from '../../../../utils/i18n'; import { SignMessageRequestStruct } from '../../../../validation/structs'; +import type { TronWalletKeyringRequest } from '../../../../validation/structs'; import { ConfirmSignMessage } from './ConfirmSignMessage'; /** @@ -19,7 +18,7 @@ import { ConfirmSignMessage } from './ConfirmSignMessage'; * @returns The confirmation dialog result. */ export async function render( - request: KeyringRequest, + request: TronWalletKeyringRequest, account: ExtendedKeyringAccount, ): Promise { assert(request.request.params, SignMessageRequestStruct); @@ -45,7 +44,7 @@ export async function render( { @@ -79,7 +79,7 @@ export async function render( // Build initial context const context: ConfirmSignTransactionContext = { ...DEFAULT_CONTEXT, - scope: scope as Network, + scope, account, transaction, origin: origin ?? 'Unknown', @@ -94,9 +94,9 @@ export async function render( const [preferences, accountAssets] = await Promise.all([ snapClient.getPreferences().catch(() => DEFAULT_CONTEXT.preferences), assetsService.getAccountAssetsByIDs(account.id, [ - Networks[scope as Network].nativeToken.id, - Networks[scope as Network].bandwidth.id, - Networks[scope as Network].energy.id, + Networks[scope].nativeToken.id, + Networks[scope].bandwidth.id, + Networks[scope].energy.id, ]), ]); @@ -131,7 +131,7 @@ export async function render( }; const fees = await feeCalculatorService.computeFee({ - scope: scope as Network, + scope, transaction: transactionObj, availableEnergy, availableBandwidth, @@ -157,9 +157,8 @@ export async function render( context.fees = fees; context.feesFetchStatus = FetchStatus.Fetched; const trxFee = new BigNumber( - fees.find( - (fee) => fee.asset.type === Networks[scope as Network].nativeToken.id, - )?.asset.amount ?? '0', + fees.find((fee) => fee.asset.type === Networks[scope].nativeToken.id) + ?.asset.amount ?? '0', ); const trxBalance = nativeTokenAsset ? sunToTrx(nativeTokenAsset.rawAmount) @@ -214,7 +213,7 @@ export async function render( accountAddress: account.address, transactionRawData: rawData, origin, - scope: scope as Network, + scope, options, }); } catch { @@ -231,7 +230,7 @@ export async function render( const expired = await snapContext.transactionExpirationRefresherService.isTransactionExpired( { - scope: scope as Network, + scope, rawData, }, ); diff --git a/packages/tron-wallet-snap/src/utils/caip.ts b/packages/tron-wallet-snap/src/utils/caip.ts new file mode 100644 index 000000000..34c6fd0d9 --- /dev/null +++ b/packages/tron-wallet-snap/src/utils/caip.ts @@ -0,0 +1,40 @@ +import { assert } from '@metamask/superstruct'; +import type { CaipAssetType } from '@metamask/utils'; +import { parseCaipAssetType } from '@metamask/utils'; + +import { Network } from '../constants'; +import { TronCaipAssetTypeStruct } from '../validation/structs'; +import type { + NativeCaipAssetType, + NftCaipAssetType, + ReadyForWithdrawalCaipAssetType, + ResourceCaipAssetType, + StakedCaipAssetType, + StakingRewardsCaipAssetType, + TokenCaipAssetType, +} from '../services/assets/types'; + +export type TronCaipAssetType = + | NativeCaipAssetType + | StakedCaipAssetType + | ReadyForWithdrawalCaipAssetType + | StakingRewardsCaipAssetType + | ResourceCaipAssetType + | TokenCaipAssetType + | NftCaipAssetType; + +export type ParsedTronCaipAssetType = ReturnType & { + chainId: Network; +}; + +export function parseTronCaipAssetType( + assetId: string, +): ParsedTronCaipAssetType { + assert(assetId, TronCaipAssetTypeStruct); + const parsed = parseCaipAssetType(assetId as CaipAssetType); + + return { + ...parsed, + chainId: parsed.chainId as Network, + }; +} diff --git a/packages/tron-wallet-snap/src/utils/getAssetNetwork.ts b/packages/tron-wallet-snap/src/utils/getAssetNetwork.ts new file mode 100644 index 000000000..e6a61379b --- /dev/null +++ b/packages/tron-wallet-snap/src/utils/getAssetNetwork.ts @@ -0,0 +1,17 @@ +import { parseCaipAssetType } from '@metamask/utils'; +import type { CaipAssetType } from '@metamask/utils'; + +import { assertSupportedNetwork } from '../constants'; +import type { Network } from '../constants'; + +/** + * Returns the supported network a CAIP-19 asset type belongs to. + * + * @param assetType - The CAIP-19 asset type. + * @returns The network of the asset. + * @throws If the asset type's chain is not a supported network. + */ +export function getAssetNetwork(assetType: CaipAssetType): Network { + const { chainId } = parseCaipAssetType(assetType); + return assertSupportedNetwork(chainId); +} diff --git a/packages/tron-wallet-snap/src/validation/structs.ts b/packages/tron-wallet-snap/src/validation/structs.ts index adb05c4ec..41456726b 100644 --- a/packages/tron-wallet-snap/src/validation/structs.ts +++ b/packages/tron-wallet-snap/src/validation/structs.ts @@ -2,6 +2,7 @@ import { CaipAssetTypeStruct, KeyringRequestStruct, SolMethod, + TransactionType, TrxAccountType, } from '@metamask/keyring-api'; import { ExportAccountOptionsStruct } from '@metamask/keyring-api/v2'; @@ -72,6 +73,18 @@ export const ExportAccountRequestStruct = object({ export const NetworkStruct = enums(Object.values(Network)); +export const ResolveAccountAddressStruct = object({ + scope: NetworkStruct, +}); + +export const TrackTransactionParamsStruct = object({ + txId: string(), + scope: NetworkStruct, + accountIds: array(string()), + attempt: integer(), + transactionType: optional(enums(Object.values(TransactionType))), +}); + /** * Validates createAccount options. * - entropySource: Optional string for the entropy source (UUID or ULID format)