@metamask-previews/transaction-pay-controller 30.1.0-preview-173a9edb1 → 30.2.0-preview-55fcf3ef7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [30.2.0]
11
+
10
12
  ### Added
11
13
 
12
14
  - Support Predict withdraw in `ServerStrategy` ([#10702](https://github.com/MetaMask/core/pull/10702))
@@ -22,9 +24,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
24
  - Bump `@metamask/accounts-controller` from `^40.0.0` to `^40.1.0` ([#10851](https://github.com/MetaMask/core/pull/10851))
23
25
  - Bump `@metamask/keyring-controller` from `^28.1.0` to `^28.2.0` ([#10851](https://github.com/MetaMask/core/pull/10851))
24
26
  - Bump `@metamask/base-controller` from `^10.0.0` to `^10.1.0` ([#10864](https://github.com/MetaMask/core/pull/10864))
25
- - Bump `@metamask/network-controller` from `^37.0.1` to `^37.1.0` ([#10864](https://github.com/MetaMask/core/pull/10864))
27
+ - Bump `@metamask/network-controller` from `^37.0.1` to `^37.1.1` ([#10864](https://github.com/MetaMask/core/pull/10864), [#10982](https://github.com/MetaMask/core/pull/10982))
26
28
  - Bump `@metamask/assets-controller` from `^19.0.0` to `^19.1.0` ([#10876](https://github.com/MetaMask/core/pull/10876))
27
- - Bump `@metamask/ramps-controller` from `^27.0.1` to `^27.0.2` ([#10925](https://github.com/MetaMask/core/pull/10925))
29
+ - Bump `@metamask/ramps-controller` from `^27.0.1` to `^27.1.0` ([#10925](https://github.com/MetaMask/core/pull/10925), [#10985](https://github.com/MetaMask/core/pull/10985))
30
+
31
+ ### Fixed
32
+
33
+ - Prefer the checksummed `AssetsController` asset ID over a lowercase duplicate when resolving token metadata, balances, and prices ([#10967](https://github.com/MetaMask/core/pull/10967))
34
+ - A stale lowercase `assetsInfo` entry without price data previously shadowed the checksummed entry for lowercase token addresses, dropping the token from required tokens so no quote was requested.
35
+ - Lowercase-only matches are no longer cached, so the checksummed entry is picked up once indexed.
28
36
 
29
37
  ## [30.1.0]
30
38
 
@@ -1723,7 +1731,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1723
1731
 
1724
1732
  - Initial release ([#6820](https://github.com/MetaMask/core/pull/6820))
1725
1733
 
1726
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@30.1.0...HEAD
1734
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@30.2.0...HEAD
1735
+ [30.2.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@30.1.0...@metamask/transaction-pay-controller@30.2.0
1727
1736
  [30.1.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@30.0.3...@metamask/transaction-pay-controller@30.1.0
1728
1737
  [30.0.3]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@30.0.2...@metamask/transaction-pay-controller@30.0.3
1729
1738
  [30.0.2]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@30.0.1...@metamask/transaction-pay-controller@30.0.2
@@ -300,8 +300,9 @@ export function normalizeTokenAddress(tokenAddress, chainId, target) {
300
300
  * its identifier would be spelled. That check is a keyed read, so the cache
301
301
  * saves the scan below without letting a stale identifier through.
302
302
  *
303
- * Only resolved identifiers are cached, so an asset missing at first lookup is
304
- * retried and picked up once `AssetsController` indexes it.
303
+ * Only resolved checksummed identifiers are cached, so an asset missing at
304
+ * first lookup, or resolved only to a lowercase duplicate, is retried and
305
+ * picked up once `AssetsController` indexes its checksummed entry.
305
306
  *
306
307
  * @param assetsInfo - Asset metadata keyed by CAIP-19 ID.
307
308
  * @param chainId - Hex chain ID.
@@ -316,7 +317,9 @@ function getControllerAssetId(assetsInfo, chainId, tokenAddress) {
316
317
  return cached;
317
318
  }
318
319
  const assetId = resolveControllerAssetId(assetsInfo, chainId, tokenAddress);
319
- if (assetId) {
320
+ // Only cache checksummed (mixed-case) keys, so a lowercase duplicate seen
321
+ // before the canonical entry is indexed cannot stick for the session.
322
+ if (assetId && assetId !== assetId.toLowerCase()) {
320
323
  assetIds.set(cacheKey, assetId);
321
324
  }
322
325
  return assetId;
@@ -324,11 +327,13 @@ function getControllerAssetId(assetsInfo, chainId, tokenAddress) {
324
327
  /**
325
328
  * Locate an asset's key in `AssetsController` state.
326
329
  *
327
- * An ERC-20 identifier is fully derivable from the chain and address, so the
328
- * derived form is tried against state first — client-selected tokens are
329
- * already checksummed and hit immediately. Only addresses in another case, such
330
- * as those recovered from transaction calldata, fall through to a
331
- * case-insensitive search, which avoids paying for a checksum on the hot path.
330
+ * An ERC-20 identifier is fully derivable from the chain and address, so for a
331
+ * checksummed address the derived form is tried against state first —
332
+ * client-selected tokens are already checksummed and hit immediately. Other
333
+ * addresses, such as lowercase ones recovered from transaction calldata, fall
334
+ * through to a case-insensitive search, which avoids paying for a checksum on
335
+ * the hot path. That search prefers the checksummed key, since state may also
336
+ * hold a stale lowercase duplicate without price or balance data.
332
337
  *
333
338
  * A native identifier cannot be derived, because MetaMask keys natives
334
339
  * inconsistently across chains — `slip44:`, `erc20:` with the zero address, or
@@ -356,15 +361,22 @@ function resolveControllerAssetId(assetsInfo, chainId, tokenAddress) {
356
361
  return undefined;
357
362
  }
358
363
  const derivedAssetId = `${chainPrefix}erc20:${tokenAddress}`;
359
- if (assetsInfo[derivedAssetId]) {
364
+ const target = derivedAssetId.toLowerCase();
365
+ // Mixed-case input is already checksummed, so an exact hit is canonical.
366
+ if (derivedAssetId !== target && assetsInfo[derivedAssetId]) {
360
367
  return derivedAssetId;
361
368
  }
362
- const target = derivedAssetId.toLowerCase();
369
+ // Prefer the checksummed (mixed-case) key over a lowercase duplicate.
370
+ let lowercaseMatch;
363
371
  for (const key of Object.keys(assetsInfo)) {
364
- if (key.toLowerCase() === target) {
372
+ if (key.toLowerCase() !== target) {
373
+ continue;
374
+ }
375
+ if (key !== target) {
365
376
  return key;
366
377
  }
378
+ lowercaseMatch = key;
367
379
  }
368
- return undefined;
380
+ return lowercaseMatch;
369
381
  }
370
382
  //# sourceMappingURL=token.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/utils/token.ts"],"names":[],"mappings":"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","sourcesContent":["import { Interface } from '@ethersproject/abi';\nimport type { AssetsControllerState } from '@metamask/assets-controller';\nimport { abiERC20 } from '@metamask/metamask-eth-abis';\nimport { createModuleLogger } from '@metamask/utils';\nimport type { CaipAssetType, Hex } from '@metamask/utils';\nimport { hexToBigInt, toCaipAssetType } from '@metamask/utils';\nimport { BigNumber } from 'bignumber.js';\n\nimport {\n CHAIN_ID_POLYGON,\n NATIVE_TOKEN_ADDRESS,\n SLIP44_COIN_TYPE_BY_CHAIN,\n} from '../constants.js';\nimport { projectLogger } from '../logger.js';\nimport type { FiatRates, TransactionPayControllerMessenger } from '../types.js';\nimport { getStablecoins, isChainExcludedFromInfura } from './feature-flags.js';\nimport { rpcRequest } from './provider.js';\n\nconst log = createModuleLogger(projectLogger, 'token');\n\n/**\n * Resolved CAIP-19 asset IDs, keyed by chain ID and lower-cased address.\n *\n * An asset ID is a static property of its chain and address, so one resolved\n * from any state snapshot stays correct for the lifetime of the process.\n */\nconst assetIds = new Map<string, CaipAssetType>();\n\n/**\n * Check if two tokens are the same (same address and chain).\n *\n * @param token1 - First token identifier.\n * @param token1.address - Token address.\n * @param token1.chainId - Token chain ID.\n * @param token2 - Second token identifier.\n * @param token2.address - Token address.\n * @param token2.chainId - Token chain ID.\n * @returns True if tokens are the same, false otherwise.\n */\nexport function isSameToken(\n token1: { address: Hex; chainId: Hex },\n token2: { address: Hex; chainId: Hex },\n): boolean {\n return (\n token1.address.toLowerCase() === token2.address.toLowerCase() &&\n token1.chainId === token2.chainId\n );\n}\n\n/**\n * Get the token balance for a specific account and token.\n *\n * @param messenger - Controller messenger.\n * @param account - Address of the account.\n * @param chainId - Id of the chain.\n * @param tokenAddress - Address of the token contract.\n * @returns Raw token balance as a decimal string.\n */\nexport function getTokenBalance(\n messenger: TransactionPayControllerMessenger,\n account: Hex,\n chainId: Hex,\n tokenAddress: Hex,\n): string {\n const { accountIdByAddress } = messenger.call('AccountsController:getState');\n\n // Fall back to the lower-cased address, as a miss is usually attributable to\n // a checksummed address being passed. Mirrors AccountsController#getAccountByAddress.\n const accountId =\n accountIdByAddress[account] ??\n accountIdByAddress[account.toLowerCase() as Hex];\n\n if (!accountId) {\n return '0';\n }\n\n const { assetsBalance, assetsInfo } = messenger.call(\n 'AssetsController:getState',\n );\n const assetId = getControllerAssetId(assetsInfo, chainId, tokenAddress);\n\n if (!assetId) {\n return '0';\n }\n\n const amount = assetsBalance[accountId]?.[assetId]?.amount;\n\n if (amount === undefined) {\n return '0';\n }\n\n // AssetsController stores human-readable decimal amounts, whereas callers of\n // this function expect raw base units. Shift by the token decimals to convert.\n // The asset ID resolved above is a key of `assetsInfo`, so its metadata is\n // read from the snapshot already in hand rather than re-fetched.\n const { decimals } = assetsInfo[assetId];\n\n return new BigNumber(amount)\n .shiftedBy(Number(decimals))\n .toFixed(0, BigNumber.ROUND_DOWN);\n}\n\n/**\n * Get the decimals and symbol for a specific token.\n *\n * `AssetsController` is the sole source of truth: a token absent from unified\n * state is treated as non-existent rather than being reconstructed from network\n * configuration. Consumers must therefore ensure unified asset state covers\n * every chain Pay is used on.\n *\n * @param messenger - Controller messenger.\n * @param tokenAddress - Address of the token contract.\n * @param chainId - Id of the chain.\n * @returns The token decimals and symbol, or undefined if the token is not found.\n */\nexport function getTokenInfo(\n messenger: TransactionPayControllerMessenger,\n tokenAddress: Hex,\n chainId: Hex,\n): { decimals: number; symbol: string } | undefined {\n const { assetsInfo } = messenger.call('AssetsController:getState');\n const assetId = getControllerAssetId(assetsInfo, chainId, tokenAddress);\n const token = assetId ? assetsInfo[assetId] : undefined;\n\n if (!token) {\n return undefined;\n }\n\n return { decimals: Number(token.decimals), symbol: token.symbol };\n}\n\n/**\n * Calculate fiat rates for a specific token.\n *\n * @param messenger - Controller messenger.\n * @param tokenAddress - Address of the token contract.\n * @param chainId - Id of the chain.\n * @returns An object containing the USD and fiat rates, or undefined if rates are not available.\n */\nexport function getTokenFiatRate(\n messenger: TransactionPayControllerMessenger,\n tokenAddress: Hex,\n chainId: Hex,\n): FiatRates | undefined {\n const { assetsInfo, assetsPrice } = messenger.call(\n 'AssetsController:getState',\n );\n const assetId = getControllerAssetId(assetsInfo, chainId, tokenAddress);\n const price = assetId ? assetsPrice[assetId] : undefined;\n\n if (price?.assetPriceType !== 'fungible') {\n return undefined;\n }\n\n const isStablecoin = getStablecoins(messenger)[chainId]?.includes(\n tokenAddress.toLowerCase() as Hex,\n );\n\n return {\n fiatRate: String(price.price),\n usdRate: isStablecoin ? '1' : String(price.usdPrice),\n };\n}\n\n/**\n * Calculate the human-readable, raw, USD, and fiat representations of a token amount.\n *\n * @param rawInput - Raw token amount (decimal string, hex, or BigNumber).\n * @param decimals - Number of decimals for the token.\n * @param fiatRates - Fiat rates for the token.\n * @returns Object containing the amount in raw, human-readable, USD, and fiat formats.\n */\nexport function computeTokenAmounts(\n rawInput: BigNumber.Value,\n decimals: number,\n fiatRates: FiatRates,\n): {\n raw: string;\n human: string;\n usd: string;\n fiat: string;\n} {\n const rawValue = new BigNumber(rawInput);\n const humanValue = rawValue.shiftedBy(-decimals);\n\n return {\n raw: rawValue.toFixed(0),\n human: humanValue.toString(10),\n usd: humanValue.multipliedBy(fiatRates.usdRate).toString(10),\n fiat: humanValue.multipliedBy(fiatRates.fiatRate).toString(10),\n };\n}\n\n/**\n * Compute a raw token amount from a fiat (USD) amount.\n * This is the inverse of `computeTokenAmounts` — it goes from USD to raw.\n *\n * @param fiatAmount - Amount in fiat/USD.\n * @param decimals - Token decimals.\n * @param usdRate - USD rate for the token (price per one unit of the token).\n * @returns Raw token amount string, or undefined if the conversion produces an invalid result.\n */\nexport function computeRawFromFiatAmount(\n fiatAmount: BigNumber.Value,\n decimals: number,\n usdRate: BigNumber.Value,\n): string | undefined {\n const rate = new BigNumber(usdRate);\n if (!rate.isFinite() || !rate.gt(0)) {\n return undefined;\n }\n\n const humanAmount = new BigNumber(fiatAmount).dividedBy(rate);\n if (!humanAmount.isFinite() || !humanAmount.gt(0)) {\n return undefined;\n }\n\n const raw = humanAmount\n .shiftedBy(decimals)\n .decimalPlaces(0, BigNumber.ROUND_DOWN)\n .toFixed(0);\n\n return new BigNumber(raw).gt(0) ? raw : undefined;\n}\n\n/**\n * Get the native token address for a given chain ID.\n *\n * @param chainId - Chain ID.\n * @returns - Native token address for the given chain ID.\n */\nexport function getNativeToken(chainId: Hex): Hex {\n switch (chainId) {\n case '0x89':\n return '0x0000000000000000000000000000000000001010';\n default:\n return NATIVE_TOKEN_ADDRESS;\n }\n}\n\n/**\n * Get the live on-chain token balance via an RPC `eth_call` to the ERC-20\n * `balanceOf` function, or `eth_getBalance` for native tokens.\n *\n * Unlike {@link getTokenBalance}, this bypasses cached AssetsController state\n * and reads directly from the chain.\n *\n * Uses the Infura RPC endpoint for the chain when one is configured, falling\n * back to the chain's default endpoint. This avoids errors on custom mainnet\n * RPC endpoints that may not support pending block queries.\n *\n * @param messenger - Controller messenger.\n * @param account - Address of the account.\n * @param chainId - Chain ID.\n * @param tokenAddress - Address of the token contract.\n * @returns Raw token balance as a decimal string.\n */\nexport async function getLiveTokenBalance(\n messenger: TransactionPayControllerMessenger,\n account: Hex,\n chainId: Hex,\n tokenAddress: Hex,\n): Promise<string> {\n const options = {\n preferInfura: !isChainExcludedFromInfura(messenger, chainId),\n };\n const isNative =\n tokenAddress.toLowerCase() === getNativeToken(chainId).toLowerCase();\n\n if (isNative) {\n const result = await requestBalanceWithFallback((blockTag) =>\n rpcRequest<string>({\n messenger,\n chainId,\n method: 'eth_getBalance',\n params: [account, blockTag],\n options,\n }),\n );\n\n return new BigNumber(result, 16).toString(10);\n }\n\n const calldata = new Interface(abiERC20).encodeFunctionData('balanceOf', [\n account,\n ]) as Hex;\n\n const result = await requestBalanceWithFallback((blockTag) =>\n rpcRequest<string>({\n messenger,\n chainId,\n method: 'eth_call',\n params: [{ to: tokenAddress, data: calldata }, blockTag],\n options,\n }),\n );\n\n return new BigNumber(result, 16).toString(10);\n}\n\n/**\n * Request a balance using the `pending` block tag, falling back to `latest`\n * if the `pending` query throws.\n *\n * Some custom RPC endpoints do not support `pending` block queries. When the\n * `pending` request fails, retry with `latest` so a live balance can still be\n * resolved.\n *\n * @param request - Function that performs the balance request for a given\n * block tag.\n * @returns Raw balance result as a hex string.\n */\nasync function requestBalanceWithFallback(\n request: (blockTag: 'pending' | 'latest') => Promise<string>,\n): Promise<string> {\n try {\n return await request('pending');\n } catch (error) {\n log('Pending balance query failed, falling back to latest', error);\n return request('latest');\n }\n}\n\n/**\n * Build a CAIP-19 asset type identifier for an EVM token.\n *\n * The identifier is derived purely from its arguments, so it describes the\n * token as the wider ecosystem names it rather than as this wallet happens to\n * have indexed it. Use it for identifiers sent to third parties, such as Ramps\n * order assets; use `AssetsController` state for anything read back from the\n * wallet's own asset metadata, balances, or prices.\n *\n * For native tokens the SLIP-44 coin type is resolved automatically from\n * a built-in chain→coin-type map, falling back to 60 (ETH). Callers can\n * override via the optional third parameter.\n *\n * @param chainId - Hex chain ID (e.g. `0x1`).\n * @param tokenAddress - Token contract address, or the native token address.\n * @param slip44CoinType - Optional SLIP-44 coin type override for native tokens.\n * @returns CAIP-19 asset type string.\n */\nexport function buildCaipAssetType(\n chainId: Hex,\n tokenAddress: Hex,\n slip44CoinType?: number,\n): CaipAssetType {\n const chainReference = String(hexToBigInt(chainId));\n const isNative =\n tokenAddress.toLowerCase() === getNativeToken(chainId).toLowerCase();\n\n if (isNative) {\n const coinType = slip44CoinType ?? SLIP44_COIN_TYPE_BY_CHAIN[chainId] ?? 60;\n\n return toCaipAssetType(\n 'eip155',\n chainReference,\n 'slip44',\n String(coinType),\n );\n }\n\n return toCaipAssetType('eip155', chainReference, 'erc20', tokenAddress);\n}\n\nexport enum TokenAddressTarget {\n Relay = 'relay',\n MetaMask = 'metamask',\n}\n\n/**\n * Normalize token address formats between MetaMask and Relay for Polygon native\n * token handling.\n *\n * MetaMask uses Polygon's native token contract-like address (`0x...1010`),\n * while Relay expects the zero address for native tokens.\n *\n * @param tokenAddress - Token address to normalize.\n * @param chainId - Chain ID for the token.\n * @param target - Optional target system format.\n * @returns Normalized token address for the target system, or the original\n * address if no target is provided.\n */\nexport function normalizeTokenAddress(\n tokenAddress: Hex,\n chainId: Hex,\n target?: TokenAddressTarget,\n): Hex {\n if (chainId !== CHAIN_ID_POLYGON) {\n return tokenAddress;\n }\n\n const nativeTokenAddress = getNativeToken(chainId).toLowerCase() as Hex;\n const normalizedTokenAddress = tokenAddress.toLowerCase();\n\n if (\n target === TokenAddressTarget.Relay &&\n normalizedTokenAddress === nativeTokenAddress\n ) {\n return NATIVE_TOKEN_ADDRESS;\n }\n\n if (\n target === TokenAddressTarget.MetaMask &&\n normalizedTokenAddress === NATIVE_TOKEN_ADDRESS.toLowerCase()\n ) {\n return nativeTokenAddress;\n }\n\n return tokenAddress;\n}\n\n/**\n * Locate an asset's key in `AssetsController` state, reusing a previously\n * resolved identifier.\n *\n * A cached identifier is still confirmed against the given state, since an\n * asset `AssetsController` has not indexed yet is unresolvable no matter how\n * its identifier would be spelled. That check is a keyed read, so the cache\n * saves the scan below without letting a stale identifier through.\n *\n * Only resolved identifiers are cached, so an asset missing at first lookup is\n * retried and picked up once `AssetsController` indexes it.\n *\n * @param assetsInfo - Asset metadata keyed by CAIP-19 ID.\n * @param chainId - Hex chain ID.\n * @param tokenAddress - Token address, or the native token address.\n * @returns The CAIP-19 asset ID as keyed in state, or undefined when the asset\n * is absent.\n */\nfunction getControllerAssetId(\n assetsInfo: AssetsControllerState['assetsInfo'],\n chainId: Hex,\n tokenAddress: Hex,\n): CaipAssetType | undefined {\n const cacheKey = `${chainId}:${tokenAddress.toLowerCase()}`;\n const cached = assetIds.get(cacheKey);\n\n if (cached && assetsInfo[cached]) {\n return cached;\n }\n\n const assetId = resolveControllerAssetId(assetsInfo, chainId, tokenAddress);\n\n if (assetId) {\n assetIds.set(cacheKey, assetId);\n }\n\n return assetId;\n}\n\n/**\n * Locate an asset's key in `AssetsController` state.\n *\n * An ERC-20 identifier is fully derivable from the chain and address, so the\n * derived form is tried against state first — client-selected tokens are\n * already checksummed and hit immediately. Only addresses in another case, such\n * as those recovered from transaction calldata, fall through to a\n * case-insensitive search, which avoids paying for a checksum on the hot path.\n *\n * A native identifier cannot be derived, because MetaMask keys natives\n * inconsistently across chains — `slip44:`, `erc20:` with the zero address, or\n * a chain-specific sentinel address — so the chain's sole entry of type\n * `native` is searched for instead.\n *\n * An asset absent from state is unresolvable rather than assumed, so a derived\n * identifier state cannot confirm is never returned.\n *\n * @param assetsInfo - Asset metadata keyed by CAIP-19 ID.\n * @param chainId - Hex chain ID.\n * @param tokenAddress - Token address, or the native token address.\n * @returns The CAIP-19 asset ID as keyed in state, or undefined when the asset\n * is absent.\n */\nfunction resolveControllerAssetId(\n assetsInfo: AssetsControllerState['assetsInfo'],\n chainId: Hex,\n tokenAddress: Hex,\n): CaipAssetType | undefined {\n const chainPrefix = `eip155:${hexToBigInt(chainId)}/`;\n const isNative =\n tokenAddress.toLowerCase() === getNativeToken(chainId).toLowerCase();\n\n if (isNative) {\n for (const [key, metadata] of Object.entries(assetsInfo)) {\n if (key.startsWith(chainPrefix) && metadata.type === 'native') {\n return key as CaipAssetType;\n }\n }\n\n return undefined;\n }\n\n const derivedAssetId = `${chainPrefix}erc20:${tokenAddress}` as CaipAssetType;\n\n if (assetsInfo[derivedAssetId]) {\n return derivedAssetId;\n }\n\n const target = derivedAssetId.toLowerCase();\n\n for (const key of Object.keys(assetsInfo)) {\n if (key.toLowerCase() === target) {\n return key as CaipAssetType;\n }\n }\n\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/utils/token.ts"],"names":[],"mappings":"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","sourcesContent":["import { Interface } from '@ethersproject/abi';\nimport type { AssetsControllerState } from '@metamask/assets-controller';\nimport { abiERC20 } from '@metamask/metamask-eth-abis';\nimport { createModuleLogger } from '@metamask/utils';\nimport type { CaipAssetType, Hex } from '@metamask/utils';\nimport { hexToBigInt, toCaipAssetType } from '@metamask/utils';\nimport { BigNumber } from 'bignumber.js';\n\nimport {\n CHAIN_ID_POLYGON,\n NATIVE_TOKEN_ADDRESS,\n SLIP44_COIN_TYPE_BY_CHAIN,\n} from '../constants.js';\nimport { projectLogger } from '../logger.js';\nimport type { FiatRates, TransactionPayControllerMessenger } from '../types.js';\nimport { getStablecoins, isChainExcludedFromInfura } from './feature-flags.js';\nimport { rpcRequest } from './provider.js';\n\nconst log = createModuleLogger(projectLogger, 'token');\n\n/**\n * Resolved CAIP-19 asset IDs, keyed by chain ID and lower-cased address.\n *\n * An asset ID is a static property of its chain and address, so one resolved\n * from any state snapshot stays correct for the lifetime of the process.\n */\nconst assetIds = new Map<string, CaipAssetType>();\n\n/**\n * Check if two tokens are the same (same address and chain).\n *\n * @param token1 - First token identifier.\n * @param token1.address - Token address.\n * @param token1.chainId - Token chain ID.\n * @param token2 - Second token identifier.\n * @param token2.address - Token address.\n * @param token2.chainId - Token chain ID.\n * @returns True if tokens are the same, false otherwise.\n */\nexport function isSameToken(\n token1: { address: Hex; chainId: Hex },\n token2: { address: Hex; chainId: Hex },\n): boolean {\n return (\n token1.address.toLowerCase() === token2.address.toLowerCase() &&\n token1.chainId === token2.chainId\n );\n}\n\n/**\n * Get the token balance for a specific account and token.\n *\n * @param messenger - Controller messenger.\n * @param account - Address of the account.\n * @param chainId - Id of the chain.\n * @param tokenAddress - Address of the token contract.\n * @returns Raw token balance as a decimal string.\n */\nexport function getTokenBalance(\n messenger: TransactionPayControllerMessenger,\n account: Hex,\n chainId: Hex,\n tokenAddress: Hex,\n): string {\n const { accountIdByAddress } = messenger.call('AccountsController:getState');\n\n // Fall back to the lower-cased address, as a miss is usually attributable to\n // a checksummed address being passed. Mirrors AccountsController#getAccountByAddress.\n const accountId =\n accountIdByAddress[account] ??\n accountIdByAddress[account.toLowerCase() as Hex];\n\n if (!accountId) {\n return '0';\n }\n\n const { assetsBalance, assetsInfo } = messenger.call(\n 'AssetsController:getState',\n );\n const assetId = getControllerAssetId(assetsInfo, chainId, tokenAddress);\n\n if (!assetId) {\n return '0';\n }\n\n const amount = assetsBalance[accountId]?.[assetId]?.amount;\n\n if (amount === undefined) {\n return '0';\n }\n\n // AssetsController stores human-readable decimal amounts, whereas callers of\n // this function expect raw base units. Shift by the token decimals to convert.\n // The asset ID resolved above is a key of `assetsInfo`, so its metadata is\n // read from the snapshot already in hand rather than re-fetched.\n const { decimals } = assetsInfo[assetId];\n\n return new BigNumber(amount)\n .shiftedBy(Number(decimals))\n .toFixed(0, BigNumber.ROUND_DOWN);\n}\n\n/**\n * Get the decimals and symbol for a specific token.\n *\n * `AssetsController` is the sole source of truth: a token absent from unified\n * state is treated as non-existent rather than being reconstructed from network\n * configuration. Consumers must therefore ensure unified asset state covers\n * every chain Pay is used on.\n *\n * @param messenger - Controller messenger.\n * @param tokenAddress - Address of the token contract.\n * @param chainId - Id of the chain.\n * @returns The token decimals and symbol, or undefined if the token is not found.\n */\nexport function getTokenInfo(\n messenger: TransactionPayControllerMessenger,\n tokenAddress: Hex,\n chainId: Hex,\n): { decimals: number; symbol: string } | undefined {\n const { assetsInfo } = messenger.call('AssetsController:getState');\n const assetId = getControllerAssetId(assetsInfo, chainId, tokenAddress);\n const token = assetId ? assetsInfo[assetId] : undefined;\n\n if (!token) {\n return undefined;\n }\n\n return { decimals: Number(token.decimals), symbol: token.symbol };\n}\n\n/**\n * Calculate fiat rates for a specific token.\n *\n * @param messenger - Controller messenger.\n * @param tokenAddress - Address of the token contract.\n * @param chainId - Id of the chain.\n * @returns An object containing the USD and fiat rates, or undefined if rates are not available.\n */\nexport function getTokenFiatRate(\n messenger: TransactionPayControllerMessenger,\n tokenAddress: Hex,\n chainId: Hex,\n): FiatRates | undefined {\n const { assetsInfo, assetsPrice } = messenger.call(\n 'AssetsController:getState',\n );\n const assetId = getControllerAssetId(assetsInfo, chainId, tokenAddress);\n const price = assetId ? assetsPrice[assetId] : undefined;\n\n if (price?.assetPriceType !== 'fungible') {\n return undefined;\n }\n\n const isStablecoin = getStablecoins(messenger)[chainId]?.includes(\n tokenAddress.toLowerCase() as Hex,\n );\n\n return {\n fiatRate: String(price.price),\n usdRate: isStablecoin ? '1' : String(price.usdPrice),\n };\n}\n\n/**\n * Calculate the human-readable, raw, USD, and fiat representations of a token amount.\n *\n * @param rawInput - Raw token amount (decimal string, hex, or BigNumber).\n * @param decimals - Number of decimals for the token.\n * @param fiatRates - Fiat rates for the token.\n * @returns Object containing the amount in raw, human-readable, USD, and fiat formats.\n */\nexport function computeTokenAmounts(\n rawInput: BigNumber.Value,\n decimals: number,\n fiatRates: FiatRates,\n): {\n raw: string;\n human: string;\n usd: string;\n fiat: string;\n} {\n const rawValue = new BigNumber(rawInput);\n const humanValue = rawValue.shiftedBy(-decimals);\n\n return {\n raw: rawValue.toFixed(0),\n human: humanValue.toString(10),\n usd: humanValue.multipliedBy(fiatRates.usdRate).toString(10),\n fiat: humanValue.multipliedBy(fiatRates.fiatRate).toString(10),\n };\n}\n\n/**\n * Compute a raw token amount from a fiat (USD) amount.\n * This is the inverse of `computeTokenAmounts` — it goes from USD to raw.\n *\n * @param fiatAmount - Amount in fiat/USD.\n * @param decimals - Token decimals.\n * @param usdRate - USD rate for the token (price per one unit of the token).\n * @returns Raw token amount string, or undefined if the conversion produces an invalid result.\n */\nexport function computeRawFromFiatAmount(\n fiatAmount: BigNumber.Value,\n decimals: number,\n usdRate: BigNumber.Value,\n): string | undefined {\n const rate = new BigNumber(usdRate);\n if (!rate.isFinite() || !rate.gt(0)) {\n return undefined;\n }\n\n const humanAmount = new BigNumber(fiatAmount).dividedBy(rate);\n if (!humanAmount.isFinite() || !humanAmount.gt(0)) {\n return undefined;\n }\n\n const raw = humanAmount\n .shiftedBy(decimals)\n .decimalPlaces(0, BigNumber.ROUND_DOWN)\n .toFixed(0);\n\n return new BigNumber(raw).gt(0) ? raw : undefined;\n}\n\n/**\n * Get the native token address for a given chain ID.\n *\n * @param chainId - Chain ID.\n * @returns - Native token address for the given chain ID.\n */\nexport function getNativeToken(chainId: Hex): Hex {\n switch (chainId) {\n case '0x89':\n return '0x0000000000000000000000000000000000001010';\n default:\n return NATIVE_TOKEN_ADDRESS;\n }\n}\n\n/**\n * Get the live on-chain token balance via an RPC `eth_call` to the ERC-20\n * `balanceOf` function, or `eth_getBalance` for native tokens.\n *\n * Unlike {@link getTokenBalance}, this bypasses cached AssetsController state\n * and reads directly from the chain.\n *\n * Uses the Infura RPC endpoint for the chain when one is configured, falling\n * back to the chain's default endpoint. This avoids errors on custom mainnet\n * RPC endpoints that may not support pending block queries.\n *\n * @param messenger - Controller messenger.\n * @param account - Address of the account.\n * @param chainId - Chain ID.\n * @param tokenAddress - Address of the token contract.\n * @returns Raw token balance as a decimal string.\n */\nexport async function getLiveTokenBalance(\n messenger: TransactionPayControllerMessenger,\n account: Hex,\n chainId: Hex,\n tokenAddress: Hex,\n): Promise<string> {\n const options = {\n preferInfura: !isChainExcludedFromInfura(messenger, chainId),\n };\n const isNative =\n tokenAddress.toLowerCase() === getNativeToken(chainId).toLowerCase();\n\n if (isNative) {\n const result = await requestBalanceWithFallback((blockTag) =>\n rpcRequest<string>({\n messenger,\n chainId,\n method: 'eth_getBalance',\n params: [account, blockTag],\n options,\n }),\n );\n\n return new BigNumber(result, 16).toString(10);\n }\n\n const calldata = new Interface(abiERC20).encodeFunctionData('balanceOf', [\n account,\n ]) as Hex;\n\n const result = await requestBalanceWithFallback((blockTag) =>\n rpcRequest<string>({\n messenger,\n chainId,\n method: 'eth_call',\n params: [{ to: tokenAddress, data: calldata }, blockTag],\n options,\n }),\n );\n\n return new BigNumber(result, 16).toString(10);\n}\n\n/**\n * Request a balance using the `pending` block tag, falling back to `latest`\n * if the `pending` query throws.\n *\n * Some custom RPC endpoints do not support `pending` block queries. When the\n * `pending` request fails, retry with `latest` so a live balance can still be\n * resolved.\n *\n * @param request - Function that performs the balance request for a given\n * block tag.\n * @returns Raw balance result as a hex string.\n */\nasync function requestBalanceWithFallback(\n request: (blockTag: 'pending' | 'latest') => Promise<string>,\n): Promise<string> {\n try {\n return await request('pending');\n } catch (error) {\n log('Pending balance query failed, falling back to latest', error);\n return request('latest');\n }\n}\n\n/**\n * Build a CAIP-19 asset type identifier for an EVM token.\n *\n * The identifier is derived purely from its arguments, so it describes the\n * token as the wider ecosystem names it rather than as this wallet happens to\n * have indexed it. Use it for identifiers sent to third parties, such as Ramps\n * order assets; use `AssetsController` state for anything read back from the\n * wallet's own asset metadata, balances, or prices.\n *\n * For native tokens the SLIP-44 coin type is resolved automatically from\n * a built-in chain→coin-type map, falling back to 60 (ETH). Callers can\n * override via the optional third parameter.\n *\n * @param chainId - Hex chain ID (e.g. `0x1`).\n * @param tokenAddress - Token contract address, or the native token address.\n * @param slip44CoinType - Optional SLIP-44 coin type override for native tokens.\n * @returns CAIP-19 asset type string.\n */\nexport function buildCaipAssetType(\n chainId: Hex,\n tokenAddress: Hex,\n slip44CoinType?: number,\n): CaipAssetType {\n const chainReference = String(hexToBigInt(chainId));\n const isNative =\n tokenAddress.toLowerCase() === getNativeToken(chainId).toLowerCase();\n\n if (isNative) {\n const coinType = slip44CoinType ?? SLIP44_COIN_TYPE_BY_CHAIN[chainId] ?? 60;\n\n return toCaipAssetType(\n 'eip155',\n chainReference,\n 'slip44',\n String(coinType),\n );\n }\n\n return toCaipAssetType('eip155', chainReference, 'erc20', tokenAddress);\n}\n\nexport enum TokenAddressTarget {\n Relay = 'relay',\n MetaMask = 'metamask',\n}\n\n/**\n * Normalize token address formats between MetaMask and Relay for Polygon native\n * token handling.\n *\n * MetaMask uses Polygon's native token contract-like address (`0x...1010`),\n * while Relay expects the zero address for native tokens.\n *\n * @param tokenAddress - Token address to normalize.\n * @param chainId - Chain ID for the token.\n * @param target - Optional target system format.\n * @returns Normalized token address for the target system, or the original\n * address if no target is provided.\n */\nexport function normalizeTokenAddress(\n tokenAddress: Hex,\n chainId: Hex,\n target?: TokenAddressTarget,\n): Hex {\n if (chainId !== CHAIN_ID_POLYGON) {\n return tokenAddress;\n }\n\n const nativeTokenAddress = getNativeToken(chainId).toLowerCase() as Hex;\n const normalizedTokenAddress = tokenAddress.toLowerCase();\n\n if (\n target === TokenAddressTarget.Relay &&\n normalizedTokenAddress === nativeTokenAddress\n ) {\n return NATIVE_TOKEN_ADDRESS;\n }\n\n if (\n target === TokenAddressTarget.MetaMask &&\n normalizedTokenAddress === NATIVE_TOKEN_ADDRESS.toLowerCase()\n ) {\n return nativeTokenAddress;\n }\n\n return tokenAddress;\n}\n\n/**\n * Locate an asset's key in `AssetsController` state, reusing a previously\n * resolved identifier.\n *\n * A cached identifier is still confirmed against the given state, since an\n * asset `AssetsController` has not indexed yet is unresolvable no matter how\n * its identifier would be spelled. That check is a keyed read, so the cache\n * saves the scan below without letting a stale identifier through.\n *\n * Only resolved checksummed identifiers are cached, so an asset missing at\n * first lookup, or resolved only to a lowercase duplicate, is retried and\n * picked up once `AssetsController` indexes its checksummed entry.\n *\n * @param assetsInfo - Asset metadata keyed by CAIP-19 ID.\n * @param chainId - Hex chain ID.\n * @param tokenAddress - Token address, or the native token address.\n * @returns The CAIP-19 asset ID as keyed in state, or undefined when the asset\n * is absent.\n */\nfunction getControllerAssetId(\n assetsInfo: AssetsControllerState['assetsInfo'],\n chainId: Hex,\n tokenAddress: Hex,\n): CaipAssetType | undefined {\n const cacheKey = `${chainId}:${tokenAddress.toLowerCase()}`;\n const cached = assetIds.get(cacheKey);\n\n if (cached && assetsInfo[cached]) {\n return cached;\n }\n\n const assetId = resolveControllerAssetId(assetsInfo, chainId, tokenAddress);\n\n // Only cache checksummed (mixed-case) keys, so a lowercase duplicate seen\n // before the canonical entry is indexed cannot stick for the session.\n if (assetId && assetId !== assetId.toLowerCase()) {\n assetIds.set(cacheKey, assetId);\n }\n\n return assetId;\n}\n\n/**\n * Locate an asset's key in `AssetsController` state.\n *\n * An ERC-20 identifier is fully derivable from the chain and address, so for a\n * checksummed address the derived form is tried against state first —\n * client-selected tokens are already checksummed and hit immediately. Other\n * addresses, such as lowercase ones recovered from transaction calldata, fall\n * through to a case-insensitive search, which avoids paying for a checksum on\n * the hot path. That search prefers the checksummed key, since state may also\n * hold a stale lowercase duplicate without price or balance data.\n *\n * A native identifier cannot be derived, because MetaMask keys natives\n * inconsistently across chains — `slip44:`, `erc20:` with the zero address, or\n * a chain-specific sentinel address — so the chain's sole entry of type\n * `native` is searched for instead.\n *\n * An asset absent from state is unresolvable rather than assumed, so a derived\n * identifier state cannot confirm is never returned.\n *\n * @param assetsInfo - Asset metadata keyed by CAIP-19 ID.\n * @param chainId - Hex chain ID.\n * @param tokenAddress - Token address, or the native token address.\n * @returns The CAIP-19 asset ID as keyed in state, or undefined when the asset\n * is absent.\n */\nfunction resolveControllerAssetId(\n assetsInfo: AssetsControllerState['assetsInfo'],\n chainId: Hex,\n tokenAddress: Hex,\n): CaipAssetType | undefined {\n const chainPrefix = `eip155:${hexToBigInt(chainId)}/`;\n const isNative =\n tokenAddress.toLowerCase() === getNativeToken(chainId).toLowerCase();\n\n if (isNative) {\n for (const [key, metadata] of Object.entries(assetsInfo)) {\n if (key.startsWith(chainPrefix) && metadata.type === 'native') {\n return key as CaipAssetType;\n }\n }\n\n return undefined;\n }\n\n const derivedAssetId = `${chainPrefix}erc20:${tokenAddress}` as CaipAssetType;\n const target = derivedAssetId.toLowerCase();\n\n // Mixed-case input is already checksummed, so an exact hit is canonical.\n if (derivedAssetId !== target && assetsInfo[derivedAssetId]) {\n return derivedAssetId;\n }\n\n // Prefer the checksummed (mixed-case) key over a lowercase duplicate.\n let lowercaseMatch: CaipAssetType | undefined;\n\n for (const key of Object.keys(assetsInfo)) {\n if (key.toLowerCase() !== target) {\n continue;\n }\n\n if (key !== target) {\n return key as CaipAssetType;\n }\n\n lowercaseMatch = key as CaipAssetType;\n }\n\n return lowercaseMatch;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/transaction-pay-controller",
3
- "version": "30.1.0-preview-173a9edb1",
3
+ "version": "30.2.0-preview-55fcf3ef7",
4
4
  "description": "Manages alternate payment strategies to provide required funds for transactions in MetaMask",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -62,8 +62,8 @@
62
62
  "@metamask/keyring-controller": "^28.2.0",
63
63
  "@metamask/messenger": "^3.0.0",
64
64
  "@metamask/metamask-eth-abis": "^3.1.1",
65
- "@metamask/network-controller": "^37.1.0",
66
- "@metamask/ramps-controller": "^27.0.2",
65
+ "@metamask/network-controller": "^37.1.1",
66
+ "@metamask/ramps-controller": "^27.1.0",
67
67
  "@metamask/remote-feature-flag-controller": "^7.0.0",
68
68
  "@metamask/sentinel-api-service": "^2.1.0",
69
69
  "@metamask/transaction-controller": "^72.2.1",