Merge branch 'feature/usd-oracle' into develop

This commit is contained in:
Willian Mitsuda 2022-04-10 02:00:44 -03:00
commit d29b3fdfb3
3 changed files with 130 additions and 1 deletions

View File

@ -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>

View 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);

View File

@ -3,6 +3,72 @@ 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 { Fetcher } from "swr";
import useSWRImmutable from "swr/immutable";
import { ChecksummedAddress } from "./types";
const FEED_REGISTRY_MAINNET: ChecksummedAddress =
"0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf";
// The USD "token" address for Chainlink feed registry's purposes
const USD = "0x0000000000000000000000000000000000000348";
type FeedRegistryFetcherKey = [ChecksummedAddress, BlockTag];
type FeedRegistryFetcherData = [BigNumber | undefined, number | undefined];
const feedRegistryFetcherKey = (
tokenAddress: ChecksummedAddress,
blockTag: BlockTag | undefined
): FeedRegistryFetcherKey | null => {
if (blockTag === undefined) {
return null;
}
return [tokenAddress, blockTag];
};
const feedRegistryFetcher =
(
provider: JsonRpcProvider | undefined
): Fetcher<FeedRegistryFetcherData, FeedRegistryFetcherKey> =>
async (tokenAddress, blockTag) => {
// It work works on ethereum mainnet and kovan, see:
// https://docs.chain.link/docs/feed-registry/
if (provider!.network.chainId !== 1) {
throw new Error("FeedRegistry is supported only on mainnet");
}
// Let SWR handle error
const feedRegistry = new Contract(
FEED_REGISTRY_MAINNET,
FeedRegistryInterface,
provider
);
const priceData = await feedRegistry.latestRoundData(tokenAddress, USD, {
blockTag,
});
const quote = BigNumber.from(priceData.answer);
const decimals = await feedRegistry.decimals(tokenAddress, USD, {
blockTag,
});
return [quote, decimals];
};
export const useTokenUSDOracle = (
provider: JsonRpcProvider | undefined,
blockTag: BlockTag | undefined,
tokenAddress: ChecksummedAddress
): [BigNumber | undefined, number | undefined] => {
const fetcher = feedRegistryFetcher(provider);
const { data, error } = useSWRImmutable(
feedRegistryFetcherKey(tokenAddress, blockTag),
fetcher
);
if (error) {
return [undefined, undefined];
}
return data ?? [undefined, undefined];
};
export const useETHUSDOracle = ( export const useETHUSDOracle = (
provider: JsonRpcProvider | undefined, provider: JsonRpcProvider | undefined,
@ -22,6 +88,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;
} }