From 247d384653f40c240e570b79e3b428a1b4abc4c1 Mon Sep 17 00:00:00 2001 From: Mullapudi Pruthvik Date: Thu, 21 Nov 2024 14:23:31 +0530 Subject: [PATCH] fix: handle duplicate token contract addresses Ticket: COIN-2317 --- .../BuildUnsignedSweepCoin.tsx | 19 ++++- src/helpers/index.ts | 78 ++++++++++++++++++- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx b/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx index 00da3d67..22166531 100644 --- a/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx +++ b/src/containers/BuildUnsignedSweepCoin/BuildUnsignedSweepCoin.tsx @@ -2,7 +2,8 @@ import { useNavigate, useParams } from 'react-router-dom'; import { CoinsSelectAutocomplete } from '~/components'; import { useAlertBanner } from '~/contexts'; import { - assert, getEip1559Params, + assert, + getEip1559Params, getEthLikeRecoveryChainId, getTokenChain, includePubsFor, @@ -13,6 +14,7 @@ import { toWei, updateKeysFromIds, updateKeysFromIdsWithToken, + getTickerByCoinFamily, } from '~/helpers'; import { useLocalStorageState } from '~/hooks'; import { AvalancheCForm } from './AvalancheCForm'; @@ -138,7 +140,11 @@ function Form() { await updateKeysFromIds(coin, values); const recoverData = await window.commands.recover(coin, { ...rest, - eip1559: getEip1559Params(coin, maxFeePerGas, maxPriorityFeePerGas), + eip1559: getEip1559Params( + coin, + maxFeePerGas, + maxPriorityFeePerGas + ), replayProtectionOptions: { chain: getEthLikeRecoveryChainId(coin, bitGoEnvironment), hardfork: 'london', @@ -917,8 +923,13 @@ function Form() { values ); + const tokenTicker = getTickerByCoinFamily( + values.tokenContractAddress, + parentCoin + ); + const recoverData = await recoverWithToken( - values.tokenContractAddress.toLowerCase(), + tokenTicker, parentCoin, { ...rest, @@ -1213,7 +1224,7 @@ function Form() { seed: values.seed, ignoreAddressTypes: [], userKey: '', - backupKey: '' + backupKey: '', }); assert( isRecoveryTransaction(recoverData), diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 362afccf..4ded89cb 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -2,7 +2,7 @@ import type { BackupKeyRecoveryTransansaction, FormattedOfflineVaultTxInfo, } from '@bitgo/abstract-utxo'; -import { coins, EthereumNetwork } from '@bitgo/statics' +import { coins, EthereumNetwork, BaseCoin, ContractAddressDefinedToken } from '@bitgo/statics'; import { EvmCcrNonBitgoCoin, evmCcrNonBitgoCoinConfig, @@ -11,6 +11,34 @@ import { } from '~/helpers/config'; const GWEI = 10 ** 9; +const INTERNAL_TEST_TOKENS = new Set([ + '0xerc721:bsctoken', + '0xterc721:bsctoken', + '0xerc1155:bsctoken', + '0xterc1155:bsctoken', + '0xerc721:opethtoken', + '0xterc721:opethtoken', + '0xerc1155:opethtoken', + '0xterc1155:opethtoken', + '0xerc721:arbethtoken', + '0xterc721:arbethtoken', + '0xerc1155:arbethtoken', + '0xterc1155:arbethtoken', + '0xerc721:token', + '0xterc721:token', + '0xerc1155:token', + '0xterc1155:token', + '0xnonstandard:token', + '0xtnonstandard:token', + '0xerc721:polygontoken', + '0xterc721:polygontoken', + '0xerc1155:polygontoken', + '0xterc1155:polygontoken', + '0xerc721:beratoken', + '0xterc721:beratoken', + '0xerc1155:beratoken', + '0xterc1155:beratoken', +]); export async function recoverWithToken( token: string, @@ -279,3 +307,51 @@ export function getEip1559Params(coin: string, maxFeePerGas: number, maxPriority export function isAvaxcCoin(chainName: string) { return (chainName === 'tavaxc' || chainName === 'avaxc') ; } + +function tokenHash(str: string): string { + // token hash must be 0x-prefixed lowercase hex string + str = str.toLowerCase(); + if (INTERNAL_TEST_TOKENS.has(str)) { + return str; + } + if (!str.match(/^0x[0-9a-f]{40}$/)) { + throw new Error(`invalid hash format: ${str}`); + } + return str; +} + + +const tokenTickerMapByCoinFamily = coins.reduce( + (acc: Record>, coin: Readonly) => { + if (coin instanceof ContractAddressDefinedToken) { + const coinFamily = coin.network.family; + if (!acc[coinFamily.toLowerCase()]) { + acc[coinFamily.toLowerCase()] = {}; + } + acc[coinFamily.toLowerCase()][ + tokenHash(coin.contractAddress.toString()) + ] = coin.name.toLowerCase(); + } + return acc; + }, + {}, +); + +export function getTickerByCoinFamily( + hash: string, + coinFamily: string, +): string { + + console.log("hash and conFamily") + console.log(hash, coinFamily) + if ( + tokenTickerMapByCoinFamily[coinFamily.toLowerCase()] && + tokenTickerMapByCoinFamily[coinFamily.toLowerCase()][tokenHash(hash)] + ) { + return tokenTickerMapByCoinFamily[coinFamily.toLowerCase()][ + tokenHash(hash) + ]; + } + + throw new Error(`token not found for hash: ${hash}`); +}