Convert token metadata logic into SWR fetcher

This commit is contained in:
Willian Mitsuda 2022-08-23 17:29:54 -03:00
parent 21a549b3e1
commit 9683edf050
No known key found for this signature in database
4 changed files with 58 additions and 42 deletions

View File

@ -6,24 +6,21 @@ import TransactionAddress from "./components/TransactionAddress";
import ValueHighlighter from "./components/ValueHighlighter"; import ValueHighlighter from "./components/ValueHighlighter";
import FormattedBalance from "./components/FormattedBalance"; import FormattedBalance from "./components/FormattedBalance";
import USDAmount from "./components/USDAmount"; import USDAmount from "./components/USDAmount";
import { AddressContext, TokenMeta, TokenTransfer } from "./types";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { useBlockNumberContext } from "./useBlockTagContext"; import { useBlockNumberContext } from "./useBlockTagContext";
import { useTokenMetadata } from "./useErigonHooks";
import { useTokenUSDOracle } from "./usePriceOracle"; import { useTokenUSDOracle } from "./usePriceOracle";
import { AddressContext, TokenTransfer } from "./types";
type TokenTransferItemProps = { type TokenTransferItemProps = {
t: TokenTransfer; t: TokenTransfer;
tokenMeta?: TokenMeta | null | undefined;
}; };
// TODO: handle partial const TokenTransferItem: React.FC<TokenTransferItemProps> = ({ t }) => {
const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
t,
tokenMeta,
}) => {
const { provider } = useContext(RuntimeContext); const { provider } = useContext(RuntimeContext);
const blockNumber = useBlockNumberContext(); const blockNumber = useBlockNumberContext();
const [quote, decimals] = useTokenUSDOracle(provider, blockNumber, t.token); const [quote, decimals] = useTokenUSDOracle(provider, blockNumber, t.token);
const tokenMeta = useTokenMetadata(provider, t.token);
return ( return (
<div className="flex items-baseline space-x-2 px-2 py-1 truncate hover:bg-gray-100"> <div className="flex items-baseline space-x-2 px-2 py-1 truncate hover:bg-gray-100">

View File

@ -285,11 +285,7 @@ const Details: React.FC<DetailsProps> = ({ txData }) => {
{txData.tokenTransfers.length > 0 && ( {txData.tokenTransfers.length > 0 && (
<InfoRow title={`Tokens Transferred (${txData.tokenTransfers.length})`}> <InfoRow title={`Tokens Transferred (${txData.tokenTransfers.length})`}>
{txData.tokenTransfers.map((t, i) => ( {txData.tokenTransfers.map((t, i) => (
<TokenTransferItem <TokenTransferItem key={i} t={t} />
key={i}
t={t}
tokenMeta={txData.tokenMetas[t.token]}
/>
))} ))}
</InfoRow> </InfoRow>
)} )}

View File

@ -37,7 +37,6 @@ export type TransactionData = {
to?: string; to?: string;
value: BigNumber; value: BigNumber;
tokenTransfers: TokenTransfer[]; tokenTransfers: TokenTransfer[];
tokenMetas: TokenMetas;
type: number; type: number;
maxFeePerGas?: BigNumber | undefined; maxFeePerGas?: BigNumber | undefined;
maxPriorityFeePerGas?: BigNumber | undefined; maxPriorityFeePerGas?: BigNumber | undefined;

View File

@ -13,13 +13,13 @@ import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes";
import useSWR from "swr"; import useSWR from "swr";
import useSWRImmutable from "swr/immutable"; import useSWRImmutable from "swr/immutable";
import { import {
TokenMetas,
TokenTransfer, TokenTransfer,
TransactionData, TransactionData,
InternalOperation, InternalOperation,
ProcessedTransaction, ProcessedTransaction,
OperationType, OperationType,
ChecksummedAddress, ChecksummedAddress,
TokenMeta,
} from "./types"; } from "./types";
import erc20 from "./erc20.json"; 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({ setTxData({
transactionHash: _response.hash, transactionHash: _response.hash,
from: _response.from, from: _response.from,
to: _response.to, to: _response.to,
value: _response.value, value: _response.value,
tokenTransfers, tokenTransfers,
tokenMetas,
type: _response.type ?? 0, type: _response.type ?? 0,
maxFeePerGas: _response.maxFeePerGas, maxFeePerGas: _response.maxFeePerGas,
maxPriorityFeePerGas: _response.maxPriorityFeePerGas, maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
@ -665,3 +637,55 @@ export const useHasCode = (
} }
return data as boolean | undefined; return data as boolean | undefined;
}; };
const tokenMetadataFetcher =
(provider: JsonRpcProvider | undefined) =>
async (
_: "tokenmeta",
address: ChecksummedAddress
): Promise<TokenMeta | null> => {
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;
};