Merge branch 'feature/address-page' into develop

This commit is contained in:
Willian Mitsuda 2022-02-22 14:10:56 -03:00
commit 5dd96f38b2
8 changed files with 193 additions and 62 deletions

View File

@ -10,6 +10,7 @@ import { useLatestBlock } from "./useLatestBlock";
const ETH_FEED_DECIMALS = 8;
// TODO: reduce duplication with useETHUSDOracle
const PriceBox: React.FC = () => {
const { provider } = useContext(RuntimeContext);
const latestBlock = useLatestBlock(provider);

View File

@ -1,6 +1,12 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import { BlockTag } from "@ethersproject/providers";
import ContentFrame from "../ContentFrame";
import InfoRow from "../components/InfoRow";
import TransactionValue from "../components/TransactionValue";
import ETH2USDValue from "../components/ETH2USDValue";
import TransactionAddress from "../components/TransactionAddress";
import Copy from "../components/Copy";
import TransactionLink from "../components/TransactionLink";
import PendingResults from "../search/PendingResults";
import ResultHeader from "../search/ResultHeader";
import { SearchController } from "../search/search";
@ -11,8 +17,9 @@ import { SelectionContext, useSelection } from "../useSelection";
import { useMultipleETHUSDOracle } from "../usePriceOracle";
import { RuntimeContext } from "../useRuntime";
import { useParams, useSearchParams } from "react-router-dom";
import { ChecksummedAddress } from "../types";
import { ChecksummedAddress, ProcessedTransaction } from "../types";
import { useContractsMetadata } from "../hooks";
import { useAddressBalance, useContractCreator } from "../useErigonHooks";
type AddressTransactionResultsProps = {
address: ChecksummedAddress;
@ -95,9 +102,12 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({
// TODO: dedup blockTags
const blockTags: BlockTag[] = useMemo(() => {
if (!page) {
return [];
return ["latest"];
}
return page.map((t) => t.blockNumber);
const blockTags: BlockTag[] = page.map((t) => t.blockNumber);
blockTags.push("latest");
return blockTags;
}, [page]);
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
@ -117,65 +127,91 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({
return _addresses;
}, [address, page]);
const metadatas = useContractsMetadata(addresses, provider);
const balance = useAddressBalance(provider, address);
const creator = useContractCreator(provider, address);
return (
<ContentFrame tabs>
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={address}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page?.[0]?.hash ?? ""}
nextHash={page?.[page.length - 1]?.hash ?? ""}
disabled={controller === undefined}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{page ? (
<SelectionContext.Provider value={selectionCtx}>
{page.map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
selectedAddress={address}
feeDisplay={feeDisplay}
priceMap={priceMap}
metadatas={metadatas}
/>
))}
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
<SelectionContext.Provider value={selectionCtx}>
{balance && (
<InfoRow title="Balance">
<div className="space-x-2">
<TransactionValue value={balance} />
{!balance.isZero() && priceMap["latest"] !== undefined && (
<span className="px-2 border-green-200 border rounded-lg bg-green-100 text-green-600">
<ETH2USDValue
ethAmount={balance}
eth2USDValue={priceMap["latest"]}
/>
</span>
)}
</div>
<UndefinedPageControl
address={address}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page?.[0]?.hash ?? ""}
nextHash={page?.[page.length - 1]?.hash ?? ""}
disabled={controller === undefined}
/>
</div>
</SelectionContext.Provider>
) : (
<PendingResults />
)}
</InfoRow>
)}
{creator && (
<InfoRow title="Contract creator">
<div className="flex divide-x-2 divide-dotted divide-gray-300">
<div className="flex items-baseline space-x-2 -ml-1 mr-3">
<TransactionAddress address={creator.creator} />
<Copy value={creator.creator} />
</div>
<div className="flex items-baseline pl-3">
<TransactionLink txHash={creator.hash} />
</div>
</div>
</InfoRow>
)}
<NavBar address={address} page={page} controller={controller} />
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{page ? (
<>
{page.map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
selectedAddress={address}
feeDisplay={feeDisplay}
priceMap={priceMap}
metadatas={metadatas}
/>
))}
<NavBar address={address} page={page} controller={controller} />
</>
) : (
<PendingResults />
)}
</SelectionContext.Provider>
</ContentFrame>
);
};
type NavBarProps = {
address: ChecksummedAddress;
page: ProcessedTransaction[] | undefined;
controller: SearchController | undefined;
};
const NavBar: React.FC<NavBarProps> = ({ address, page, controller }) => (
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={address}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page?.[0]?.hash ?? ""}
nextHash={page?.[page.length - 1]?.hash ?? ""}
disabled={controller === undefined}
/>
</div>
);
export default AddressTransactionResults;

View File

@ -22,7 +22,7 @@ const Copy: React.FC<CopyProps> = ({ value, rounded }) => {
return (
<button
className={`text-gray-500 focus:outline-none ${
className={`self-center flex flex-no-wrap justify-center items-center space-x-1 text-gray-500 focus:outline-none ${
rounded
? "transition-colors transition-shadows bg-gray-200 hover:bg-gray-500 hover:text-gray-200 hover:shadow w-7 h-7 rounded-full text-xs"
: "text-sm"
@ -34,10 +34,10 @@ const Copy: React.FC<CopyProps> = ({ value, rounded }) => {
rounded ? (
<FontAwesomeIcon icon={faCheck} size="1x" />
) : (
<div className="space-x-1">
<>
<FontAwesomeIcon icon={faCheckCircle} size="1x" />
{!rounded && <span>Copied</span>}
</div>
<span className="self-baseline">Copied</span>
</>
)
) : (
<FontAwesomeIcon icon={faCopy} size="1x" />

View File

@ -7,6 +7,13 @@ type ETH2USDValueProps = {
eth2USDValue: BigNumber;
};
/**
* Basic display of ETH -> USD values WITHOUT box decoration, only
* text formatting.
*
* USD amounts are displayed commified with 2 decimals places and $ prefix,
* i.e., "$1,000.00".
*/
const ETH2USDValue: React.FC<ETH2USDValueProps> = ({
ethAmount,
eth2USDValue,

View File

@ -7,6 +7,7 @@ type FormatterBalanceProps = {
decimals?: number;
};
// TODO: remove duplication with TransactionValue component
const FormattedBalance: React.FC<FormatterBalanceProps> = ({
value,
decimals = 18,

View File

@ -6,22 +6,34 @@ type TransactionValueProps = {
value: BigNumber;
decimals?: number;
hideUnit?: boolean;
unitName?: string;
};
/**
* Standard component for displaying balances. It:
*
* - Commify non-decimal parts, i.e., 1,000,000.00
* - Light gray absolute zero values
* - Cut out decimal part is it is 0 to reduce UI clutter, i.e., show
* 123 instead of 123.00
*
* TODO: remove duplication with FormattedBalance
*/
const TransactionValue: React.FC<TransactionValueProps> = ({
value,
decimals = 18,
hideUnit,
unitName = "ETH",
}) => {
const formattedValue = formatValue(value, decimals);
return (
<span
className={`text-sm ${value.isZero() ? "text-gray-400" : ""}`}
title={`${formattedValue} Ether`}
title={`${formattedValue} ${unitName}`}
>
<span className={`font-balance`}>{formattedValue}</span>
{!hideUnit && " Ether"}
{!hideUnit && ` ${unitName}`}
</span>
);
};

View File

@ -1,3 +1,3 @@
export const MIN_API_LEVEL = 7;
export const MIN_API_LEVEL = 8;
export const PAGE_SIZE = 25;

View File

@ -605,3 +605,77 @@ export const useTransactionBySenderAndNonce = (
}
return data;
};
type ContractCreatorKey = {
type: "cc";
network: number;
address: ChecksummedAddress;
};
type ContractCreator = {
hash: string;
creator: ChecksummedAddress;
};
export const useContractCreator = (
provider: JsonRpcProvider | undefined,
address: ChecksummedAddress | undefined
): ContractCreator | null | undefined => {
const { data, error } = useSWR<
ContractCreator | null | undefined,
any,
ContractCreatorKey | null
>(
provider && address
? {
type: "cc",
network: provider.network.chainId,
address,
}
: null,
getContractCreatorFetcher(provider!)
);
if (error) {
return undefined;
}
return data as ContractCreator;
};
const getContractCreatorFetcher =
(provider: JsonRpcProvider) =>
async ({
network,
address,
}: ContractCreatorKey): Promise<ContractCreator | null | undefined> => {
const result = (await provider.send("ots_getContractCreator", [
address,
])) as ContractCreator;
// Empty or success
if (result) {
result.creator = provider.formatter.address(result.creator);
}
return result;
};
export const useAddressBalance = (
provider: JsonRpcProvider | undefined,
address: ChecksummedAddress | undefined
): BigNumber | null | undefined => {
const [balance, setBalance] = useState<BigNumber | undefined>();
useEffect(() => {
if (!provider || !address) {
return undefined;
}
const readBalance = async () => {
const _balance = await provider.getBalance(address);
setBalance(_balance);
};
readBalance();
}, [provider, address]);
return balance;
};