From 9683edf050feb44803e319ef6e0ae09ced8b3c10 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 17:29:54 -0300 Subject: [PATCH] Convert token metadata logic into SWR fetcher --- src/TokenTransferItem.tsx | 11 ++--- src/transaction/Details.tsx | 6 +-- src/types.ts | 1 - src/useErigonHooks.ts | 82 ++++++++++++++++++++++++------------- 4 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/TokenTransferItem.tsx b/src/TokenTransferItem.tsx index 0284a58..5fba3ca 100644 --- a/src/TokenTransferItem.tsx +++ b/src/TokenTransferItem.tsx @@ -6,24 +6,21 @@ import TransactionAddress from "./components/TransactionAddress"; import ValueHighlighter from "./components/ValueHighlighter"; import FormattedBalance from "./components/FormattedBalance"; import USDAmount from "./components/USDAmount"; -import { AddressContext, TokenMeta, TokenTransfer } from "./types"; import { RuntimeContext } from "./useRuntime"; import { useBlockNumberContext } from "./useBlockTagContext"; +import { useTokenMetadata } from "./useErigonHooks"; import { useTokenUSDOracle } from "./usePriceOracle"; +import { AddressContext, TokenTransfer } from "./types"; type TokenTransferItemProps = { t: TokenTransfer; - tokenMeta?: TokenMeta | null | undefined; }; -// TODO: handle partial -const TokenTransferItem: React.FC = ({ - t, - tokenMeta, -}) => { +const TokenTransferItem: React.FC = ({ t }) => { const { provider } = useContext(RuntimeContext); const blockNumber = useBlockNumberContext(); const [quote, decimals] = useTokenUSDOracle(provider, blockNumber, t.token); + const tokenMeta = useTokenMetadata(provider, t.token); return (
diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 6932f5c..bb41b04 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -285,11 +285,7 @@ const Details: React.FC = ({ txData }) => { {txData.tokenTransfers.length > 0 && ( {txData.tokenTransfers.map((t, i) => ( - + ))} )} diff --git a/src/types.ts b/src/types.ts index 44a134b..06d7c69 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,7 +37,6 @@ export type TransactionData = { to?: string; value: BigNumber; tokenTransfers: TokenTransfer[]; - tokenMetas: TokenMetas; type: number; maxFeePerGas?: BigNumber | undefined; maxPriorityFeePerGas?: BigNumber | undefined; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index a3620b7..61037fe 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -13,13 +13,13 @@ import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes"; import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; import { - TokenMetas, TokenTransfer, TransactionData, InternalOperation, ProcessedTransaction, OperationType, ChecksummedAddress, + TokenMeta, } from "./types"; import erc20 from "./erc20.json"; @@ -216,40 +216,12 @@ export const useTxData = ( } } - // Extract token meta - const tokenMetas: TokenMetas = {}; - for (const t of tokenTransfers) { - if (tokenMetas[t.token] !== undefined) { - continue; - } - const erc20Contract = new Contract(t.token, erc20, provider); - try { - const [name, symbol, decimals] = await Promise.all([ - erc20Contract.name(), - erc20Contract.symbol(), - erc20Contract.decimals(), - ]); - tokenMetas[t.token] = { - name, - symbol, - decimals, - }; - } catch (err) { - tokenMetas[t.token] = null; - console.warn( - `Couldn't get token ${t.token} metadata; ignoring`, - err - ); - } - } - setTxData({ transactionHash: _response.hash, from: _response.from, to: _response.to, value: _response.value, tokenTransfers, - tokenMetas, type: _response.type ?? 0, maxFeePerGas: _response.maxFeePerGas, maxPriorityFeePerGas: _response.maxPriorityFeePerGas, @@ -665,3 +637,55 @@ export const useHasCode = ( } return data as boolean | undefined; }; + +const tokenMetadataFetcher = + (provider: JsonRpcProvider | undefined) => + async ( + _: "tokenmeta", + address: ChecksummedAddress + ): Promise => { + const erc20Contract = new Contract(address, erc20, provider); + try { + const name = (await erc20Contract.name()) as string; + if (!name.trim()) { + return null; + } + + const [symbol, decimals] = (await Promise.all([ + erc20Contract.symbol(), + erc20Contract.decimals(), + ])) as [string, number]; + + // Prevent faulty tokens with empty name/symbol + if (!symbol.trim()) { + return null; + } + + return { + name, + symbol, + decimals, + }; + } catch (err) { + // Ignore on purpose; this indicates the probe failed and the address + // is not a token + return null; + } + }; + +export const useTokenMetadata = ( + provider: JsonRpcProvider | undefined, + address: ChecksummedAddress | undefined +): TokenMeta | null | undefined => { + const fetcher = tokenMetadataFetcher(provider); + const { data, error } = useSWRImmutable( + provider !== undefined && address !== undefined + ? ["tokenmeta", address] + : null, + fetcher + ); + if (error) { + return undefined; + } + return data; +};