First working version of token/usd oracle support; add support to token transfers section on tx details
This commit is contained in:
parent
f1ac5a6b2b
commit
f39cc4184f
@ -1,17 +1,21 @@
|
|||||||
import React from "react";
|
import React, { useContext } from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faCaretRight } from "@fortawesome/free-solid-svg-icons/faCaretRight";
|
import { faCaretRight } from "@fortawesome/free-solid-svg-icons/faCaretRight";
|
||||||
import { faSackDollar } from "@fortawesome/free-solid-svg-icons/faSackDollar";
|
import { faSackDollar } from "@fortawesome/free-solid-svg-icons/faSackDollar";
|
||||||
import TransactionAddress from "./components/TransactionAddress";
|
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 {
|
import {
|
||||||
AddressContext,
|
AddressContext,
|
||||||
ChecksummedAddress,
|
ChecksummedAddress,
|
||||||
TokenMeta,
|
TokenMeta,
|
||||||
TokenTransfer,
|
TokenTransfer,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { RuntimeContext } from "./useRuntime";
|
||||||
|
import { useBlockNumberContext } from "./useBlockTagContext";
|
||||||
import { Metadata } from "./sourcify/useSourcify";
|
import { Metadata } from "./sourcify/useSourcify";
|
||||||
|
import { useTokenUSDOracle } from "./usePriceOracle";
|
||||||
|
|
||||||
type TokenTransferItemProps = {
|
type TokenTransferItemProps = {
|
||||||
t: TokenTransfer;
|
t: TokenTransfer;
|
||||||
@ -25,6 +29,10 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
|||||||
tokenMeta,
|
tokenMeta,
|
||||||
metadatas,
|
metadatas,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { provider } = useContext(RuntimeContext);
|
||||||
|
const blockNumber = useBlockNumberContext();
|
||||||
|
const [quote, decimals] = useTokenUSDOracle(provider, blockNumber, 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">
|
||||||
<div className="grid grid-cols-4 gap-x-1 w-full items-baseline">
|
<div className="grid grid-cols-4 gap-x-1 w-full items-baseline">
|
||||||
@ -60,6 +68,16 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
|||||||
</ValueHighlighter>
|
</ValueHighlighter>
|
||||||
</span>
|
</span>
|
||||||
<TransactionAddress address={t.token} metadata={metadatas[t.token]} />
|
<TransactionAddress address={t.token} metadata={metadatas[t.token]} />
|
||||||
|
{tokenMeta && quote !== undefined && decimals !== undefined && (
|
||||||
|
<span className="px-2 border-gray-200 border rounded-lg bg-gray-100 text-gray-600">
|
||||||
|
<USDAmount
|
||||||
|
amount={t.value}
|
||||||
|
amountDecimals={tokenMeta.decimals}
|
||||||
|
quote={quote}
|
||||||
|
quoteDecimals={decimals}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
44
src/components/USDAmount.tsx
Normal file
44
src/components/USDAmount.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { BigNumber, FixedNumber } from "@ethersproject/bignumber";
|
||||||
|
import { commify } from "@ethersproject/units";
|
||||||
|
|
||||||
|
type USDAmountProps = {
|
||||||
|
amount: BigNumber;
|
||||||
|
amountDecimals: number;
|
||||||
|
quote: BigNumber;
|
||||||
|
quoteDecimals: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: fix the duplication mess with other currency display components
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic display of USD amount WITHOUT box decoration, only
|
||||||
|
* text formatting.
|
||||||
|
*
|
||||||
|
* USD amounts are displayed commified with 2 decimals places and $ prefix,
|
||||||
|
* i.e., "$1,000.00".
|
||||||
|
*/
|
||||||
|
const USDAmount: React.FC<USDAmountProps> = ({
|
||||||
|
amount,
|
||||||
|
amountDecimals,
|
||||||
|
quote,
|
||||||
|
quoteDecimals,
|
||||||
|
}) => {
|
||||||
|
const value = amount.mul(quote);
|
||||||
|
const decimals = amountDecimals + quoteDecimals;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="text-xs">
|
||||||
|
$
|
||||||
|
<span className="font-balance">
|
||||||
|
{commify(
|
||||||
|
FixedNumber.fromValue(value, decimals, `ufixed256x${decimals}`)
|
||||||
|
.round(2)
|
||||||
|
.toString()
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(USDAmount);
|
@ -3,6 +3,85 @@ import { JsonRpcProvider, BlockTag } from "@ethersproject/providers";
|
|||||||
import { Contract } from "@ethersproject/contracts";
|
import { Contract } from "@ethersproject/contracts";
|
||||||
import { BigNumber } from "@ethersproject/bignumber";
|
import { BigNumber } from "@ethersproject/bignumber";
|
||||||
import AggregatorV3Interface from "@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json";
|
import AggregatorV3Interface from "@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json";
|
||||||
|
import FeedRegistryInterface from "@chainlink/contracts/abi/v0.8/FeedRegistryInterface.json";
|
||||||
|
import { ChecksummedAddress } from "./types";
|
||||||
|
|
||||||
|
const FEED_REGISTRY_MAINNET: ChecksummedAddress =
|
||||||
|
"0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf";
|
||||||
|
|
||||||
|
// The USD "token" address for Chainlink feed registry's purposes
|
||||||
|
const USD = "0x0000000000000000000000000000000000000348";
|
||||||
|
|
||||||
|
export const useTokenUSDOracle = (
|
||||||
|
provider: JsonRpcProvider | undefined,
|
||||||
|
blockTag: BlockTag | undefined,
|
||||||
|
tokenAddress: ChecksummedAddress
|
||||||
|
): [BigNumber | undefined, number | undefined] => {
|
||||||
|
const feedRegistry = useMemo(() => {
|
||||||
|
// It work works on ethereum mainnet and kovan, see:
|
||||||
|
// https://docs.chain.link/docs/feed-registry/
|
||||||
|
if (!provider || provider.network.chainId !== 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Contract(
|
||||||
|
FEED_REGISTRY_MAINNET,
|
||||||
|
FeedRegistryInterface,
|
||||||
|
provider
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}, [provider]);
|
||||||
|
|
||||||
|
const [decimals, setDecimals] = useState<number | undefined>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (!feedRegistry || blockTag === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readData = async () => {
|
||||||
|
try {
|
||||||
|
const _decimals = await feedRegistry.decimals(tokenAddress, USD, {
|
||||||
|
blockTag,
|
||||||
|
});
|
||||||
|
setDecimals(_decimals);
|
||||||
|
} catch (err) {
|
||||||
|
// Silently ignore on purpose; it means the network or block number does
|
||||||
|
// not contain the chainlink feed contract
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
readData();
|
||||||
|
}, [feedRegistry, blockTag, tokenAddress]);
|
||||||
|
|
||||||
|
const [latestPriceData, setLatestPriceData] = useState<BigNumber>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (!feedRegistry || blockTag === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readData = async () => {
|
||||||
|
try {
|
||||||
|
const priceData = await feedRegistry.latestRoundData(
|
||||||
|
tokenAddress,
|
||||||
|
USD,
|
||||||
|
{ blockTag }
|
||||||
|
);
|
||||||
|
setLatestPriceData(BigNumber.from(priceData.answer));
|
||||||
|
} catch (err) {
|
||||||
|
// Silently ignore on purpose; it means the network or block number does
|
||||||
|
// not contain the chainlink feed contract
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
readData();
|
||||||
|
}, [feedRegistry, blockTag, tokenAddress]);
|
||||||
|
|
||||||
|
return [latestPriceData, decimals];
|
||||||
|
};
|
||||||
|
|
||||||
export const useETHUSDOracle = (
|
export const useETHUSDOracle = (
|
||||||
provider: JsonRpcProvider | undefined,
|
provider: JsonRpcProvider | undefined,
|
||||||
@ -22,6 +101,7 @@ export const useMultipleETHUSDOracle = (
|
|||||||
blockTags: (BlockTag | undefined)[]
|
blockTags: (BlockTag | undefined)[]
|
||||||
) => {
|
) => {
|
||||||
const ethFeed = useMemo(() => {
|
const ethFeed = useMemo(() => {
|
||||||
|
// TODO: it currently is hardcoded to support only mainnet
|
||||||
if (!provider || provider.network.chainId !== 1) {
|
if (!provider || provider.network.chainId !== 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user