-
- TRANSFER
-
-
- {formatEther(internalOp.value)} {nativeSymbol}
-
-
-
From
-
-
-
+
+
+
-
+
+
+
+
+
+
+ {formatEther(internalOp.value)} {symbol}
+
+ {ethUSDPrice && (
+
+
+
+ )}
+
);
diff --git a/src/components/TransactionValue.tsx b/src/components/TransactionValue.tsx
index b4a9d54..53f9300 100644
--- a/src/components/TransactionValue.tsx
+++ b/src/components/TransactionValue.tsx
@@ -22,16 +22,18 @@ const TransactionValue: React.FC
= ({
value,
hideUnit,
}) => {
- const { nativeSymbol, nativeDecimals } = useChainInfo();
- const formattedValue = formatValue(value, nativeDecimals);
+ const {
+ nativeCurrency: { symbol, decimals },
+ } = useChainInfo();
+ const formattedValue = formatValue(value, decimals);
return (
{formattedValue}
- {!hideUnit && ` ${nativeSymbol}`}
+ {!hideUnit && ` ${symbol}`}
);
};
diff --git a/src/components/USDAmount.tsx b/src/components/USDAmount.tsx
new file mode 100644
index 0000000..9ba7221
--- /dev/null
+++ b/src/components/USDAmount.tsx
@@ -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 = ({
+ amount,
+ amountDecimals,
+ quote,
+ quoteDecimals,
+}) => {
+ const value = amount.mul(quote);
+ const decimals = amountDecimals + quoteDecimals;
+
+ return (
+
+ $
+
+ {commify(
+ FixedNumber.fromValue(value, decimals, `ufixed256x${decimals}`)
+ .round(2)
+ .toString()
+ )}
+
+
+ );
+};
+
+export default React.memo(USDAmount);
diff --git a/src/components/USDValue.tsx b/src/components/USDValue.tsx
index 9554e41..90da1c2 100644
--- a/src/components/USDValue.tsx
+++ b/src/components/USDValue.tsx
@@ -10,7 +10,9 @@ type USDValueProps = {
};
const USDValue: React.FC = ({ value }) => {
- const { nativeSymbol } = useChainInfo();
+ const {
+ nativeCurrency: { symbol },
+ } = useChainInfo();
return (
@@ -24,7 +26,7 @@ const USDValue: React.FC = ({ value }) => {
.toString()
)}
{" "}
- / {nativeSymbol}
+ / {symbol}
>
) : (
"N/A"
diff --git a/src/special/london/BlockRow.tsx b/src/special/london/BlockRow.tsx
index 3d1cf8f..3bec637 100644
--- a/src/special/london/BlockRow.tsx
+++ b/src/special/london/BlockRow.tsx
@@ -16,7 +16,9 @@ type BlockRowProps = {
};
const BlockRow: React.FC = ({ now, block, baseFeeDelta }) => {
- const { nativeSymbol } = useChainInfo();
+ const {
+ nativeCurrency: { symbol },
+ } = useChainInfo();
const gasTarget = block.gasLimit.div(ELASTICITY_MULTIPLIER);
const burntFees =
block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed);
@@ -55,11 +57,10 @@ const BlockRow: React.FC = ({ now, block, baseFeeDelta }) => {
- {commify(formatEther(totalReward))} {nativeSymbol}
+ {commify(formatEther(totalReward))} {symbol}
- {commify(formatEther(block.gasUsed.mul(block.baseFeePerGas!)))}{" "}
- {nativeSymbol}
+ {commify(formatEther(block.gasUsed.mul(block.baseFeePerGas!)))} {symbol}
diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx
index e845d5e..e39346b 100644
--- a/src/transaction/Details.tsx
+++ b/src/transaction/Details.tsx
@@ -1,7 +1,6 @@
import React, { useContext, useMemo, useState } from "react";
import { Tab } from "@headlessui/react";
import { TransactionDescription } from "@ethersproject/abi";
-import { BigNumber } from "@ethersproject/bignumber";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
import { faCube } from "@fortawesome/free-solid-svg-icons/faCube";
@@ -47,6 +46,7 @@ import { RuntimeContext } from "../useRuntime";
import { useContractsMetadata } from "../hooks";
import { useTransactionError } from "../useErigonHooks";
import { useChainInfo } from "../useChainInfo";
+import { useETHUSDOracle } from "../usePriceOracle";
type DetailsProps = {
txData: TransactionData;
@@ -56,7 +56,6 @@ type DetailsProps = {
devDoc?: DevDoc | undefined;
internalOps?: InternalOperation[];
sendsEthToMiner: boolean;
- ethUSDPrice: BigNumber | undefined;
};
const Details: React.FC = ({
@@ -67,7 +66,6 @@ const Details: React.FC = ({
devDoc,
internalOps,
sendsEthToMiner,
- ethUSDPrice,
}) => {
const hasEIP1559 =
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
@@ -87,7 +85,15 @@ const Details: React.FC = ({
const devMethod = txDesc ? devDoc?.methods[txDesc.signature] : undefined;
const { provider } = useContext(RuntimeContext);
- const { nativeName, nativeSymbol } = useChainInfo();
+ const {
+ nativeCurrency: { name, symbol },
+ } = useChainInfo();
+
+ const blockETHUSDPrice = useETHUSDOracle(
+ provider,
+ txData?.confirmedData?.blockNumber
+ );
+
const addresses = useMemo(() => {
const _addresses: ChecksummedAddress[] = [];
if (txData.to) {
@@ -292,6 +298,7 @@ const Details: React.FC = ({
key={i}
txData={txData}
internalOp={op}
+ ethUSDPrice={blockETHUSDPrice}
/>
))}
@@ -315,10 +322,13 @@ const Details: React.FC
= ({
)}
- {nativeSymbol}{" "}
- {!txData.value.isZero() && ethUSDPrice && (
+ {symbol}{" "}
+ {!txData.value.isZero() && blockETHUSDPrice && (
-
+
)}
@@ -338,8 +348,7 @@ const Details: React.FC = ({
{txData.type === 2 && (
<>
- {" "}
- {nativeSymbol} (
+ {symbol} (
= ({
Gwei)
- {nativeSymbol} (
+ {symbol} (
Gwei)
>
@@ -356,7 +365,7 @@ const Details: React.FC = ({
- {nativeSymbol} (
+ {symbol} (
Gwei)
{sendsEthToMiner && (
@@ -407,13 +416,12 @@ const Details: React.FC
= ({
- {" "}
- {nativeSymbol}{" "}
- {ethUSDPrice && (
+ {symbol}{" "}
+ {blockETHUSDPrice && (
)}
@@ -421,8 +429,8 @@ const Details: React.FC = ({
{hasEIP1559 && }
-
-
+
+
>
)}
diff --git a/src/transaction/RewardSplit.tsx b/src/transaction/RewardSplit.tsx
index d6e47b3..b9268c4 100644
--- a/src/transaction/RewardSplit.tsx
+++ b/src/transaction/RewardSplit.tsx
@@ -12,7 +12,9 @@ type RewardSplitProps = {
};
const RewardSplit: React.FC = ({ txData }) => {
- const { nativeSymbol } = useChainInfo();
+ const {
+ nativeCurrency: { symbol },
+ } = useChainInfo();
const paidFees = txData.gasPrice.mul(txData.confirmedData!.gasUsed);
const burntFees = txData.confirmedData!.blockBaseFeePerGas!.mul(
txData.confirmedData!.gasUsed
@@ -41,7 +43,7 @@ const RewardSplit: React.FC = ({ txData }) => {
{" "}
- {nativeSymbol}
+ {symbol}
@@ -57,7 +59,7 @@ const RewardSplit: React.FC = ({ txData }) => {
- {nativeSymbol}
+ {symbol}
diff --git a/src/transaction/TraceInput.tsx b/src/transaction/TraceInput.tsx
index c21adfc..f8f469a 100644
--- a/src/transaction/TraceInput.tsx
+++ b/src/transaction/TraceInput.tsx
@@ -19,7 +19,9 @@ type TraceInputProps = {
};
const TraceInput: React.FC = ({ t }) => {
- const { nativeSymbol } = useChainInfo();
+ const {
+ nativeCurrency: { symbol },
+ } = useChainInfo();
const raw4Bytes = extract4Bytes(t.input);
const fourBytes = use4Bytes(raw4Bytes);
const sigText =
@@ -57,8 +59,7 @@ const TraceInput: React.FC = ({ t }) => {
{t.value && !t.value.isZero() && (
- {"{"}value: {" "}
- {nativeSymbol}
+ {"{"}value: {symbol}
{"}"}
)}
diff --git a/src/useChainInfo.ts b/src/useChainInfo.ts
index 0d494b9..dec3798 100644
--- a/src/useChainInfo.ts
+++ b/src/useChainInfo.ts
@@ -3,15 +3,23 @@ import { chainInfoURL } from "./url";
import { OtterscanRuntime } from "./useRuntime";
export type ChainInfo = {
- nativeName: string;
- nativeSymbol: string;
- nativeDecimals: number;
+ network: string | undefined;
+ faucets: string[];
+ nativeCurrency: {
+ name: string;
+ symbol: string;
+ decimals: number;
+ };
};
export const defaultChainInfo: ChainInfo = {
- nativeName: "Ether",
- nativeSymbol: "ETH",
- nativeDecimals: 18,
+ network: undefined,
+ faucets: [],
+ nativeCurrency: {
+ name: "Ether",
+ symbol: "ETH",
+ decimals: 18,
+ },
};
export const ChainInfoContext = createContext(undefined);
@@ -25,24 +33,26 @@ export const useChainInfoFromMetadataFile = (
const [chainInfo, setChainInfo] = useState(undefined);
useEffect(() => {
- if (chainId === undefined) {
+ if (assetsURLPrefix === undefined || chainId === undefined) {
setChainInfo(undefined);
return;
}
const readChainInfo = async () => {
- const res = await fetch(chainInfoURL(assetsURLPrefix!, chainId));
- if (!res.ok) {
+ try {
+ const res = await fetch(chainInfoURL(assetsURLPrefix, chainId));
+ if (!res.ok) {
+ setChainInfo(defaultChainInfo);
+ return;
+ }
+
+ const info: ChainInfo = await res.json();
+ setChainInfo(info);
+ } catch (err) {
+ // ignore
setChainInfo(defaultChainInfo);
return;
}
- const info = await res.json();
-
- setChainInfo({
- nativeName: info.nativeCurrency.name,
- nativeDecimals: info.nativeCurrency.decimals,
- nativeSymbol: info.nativeCurrency.symbol,
- });
};
readChainInfo();
}, [assetsURLPrefix, chainId]);
diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts
index 2e49afd..f39230f 100644
--- a/src/usePriceOracle.ts
+++ b/src/usePriceOracle.ts
@@ -3,6 +3,72 @@ import { JsonRpcProvider, BlockTag } from "@ethersproject/providers";
import { Contract } from "@ethersproject/contracts";
import { BigNumber } from "@ethersproject/bignumber";
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 =>
+ 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 = (
provider: JsonRpcProvider | undefined,
@@ -22,6 +88,7 @@ export const useMultipleETHUSDOracle = (
blockTags: (BlockTag | undefined)[]
) => {
const ethFeed = useMemo(() => {
+ // TODO: it currently is hardcoded to support only mainnet
if (!provider || provider.network.chainId !== 1) {
return undefined;
}
diff --git a/topic0 b/topic0
index cb6abe8..e7be61f 160000
--- a/topic0
+++ b/topic0
@@ -1 +1 @@
-Subproject commit cb6abe87055d2e2d54ba6a985903031420c4cbb1
+Subproject commit e7be61f955f53fa2131cd14155d6891993772ff7
diff --git a/trustwallet b/trustwallet
index b8d1c58..dd4ba75 160000
--- a/trustwallet
+++ b/trustwallet
@@ -1 +1 @@
-Subproject commit b8d1c58acfd93b1b698fe5854f2c09bff70e9fdf
+Subproject commit dd4ba75fb6da47c3ec1130109c4411c006267d56