Merge pull request #234 from ArtemKolodko/proxy_erc20_tracking

Proxy erc20 tracking
pull/237/head
Artem 2 years ago committed by GitHub
commit 69b381e0dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      src/Routes.tsx
  2. 8
      src/api/client.ts
  3. 3
      src/components/ERC20_Pool.tsx
  4. 4
      src/hooks/ERC20_Pool.ts
  5. 3
      src/pages/AddressPage/ContractDetails/AbiMethodView.tsx
  6. 85
      src/pages/AddressPage/ContractDetails/index.tsx
  7. 2
      src/pages/AddressPage/tabs/transactions/Transactions.tsx
  8. 4
      src/pages/AddressPage/tabs/transactions/columns/erc20.tsx
  9. 31
      src/pages/ExportData/export-utils.ts
  10. 118
      src/pages/ExportData/index.tsx
  11. 196
      src/pages/VerifyProxyContract/VerifyProxyContract.tsx
  12. 1
      src/types/blockchain.ts

@ -15,7 +15,6 @@ import { ExportData } from "./pages/ExportData";
import { InventoryDetailsPage } from "./pages/InventoryDetailsPage/InventoryDetailsPage";
import { ApprovalPage } from "./pages/ApprovalPage";
import { CheckHRC } from "./pages/tools/CheckHRC";
import {VerifyProxyContract} from "./pages/VerifyProxyContract/VerifyProxyContract";
import { ChartsPage } from "./pages/ChartsPage";
import { TopStatsPage } from "./pages/TopStatsPage";
@ -97,10 +96,6 @@ export function Routes() {
<VerifyContract />
</Route>
<Route path="/proxyContractChecker">
<VerifyProxyContract />
</Route>
<Route path="/exportData">
<ExportData />
</Route>

@ -220,11 +220,3 @@ export async function getTokenERC1155AssetDetails(address: string, tokenID: stri
// todo fix on backend
return res && res[0]
}
export function getProxyImplementation(params: [ShardID, string]) {
return transport("getProxyImplementation", params) as Promise<any>;
}
export function assignProxyImplementation(params: [ShardID, string, string]) {
return transport("assignProxyImplementation", params) as Promise<any>;
}

@ -20,7 +20,8 @@ export function ERC20_Pool() {
erc20 = erc20.map((item) => {
erc20Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
isBridged: isTokenBridged(item.address),
name: item.implementationAddress ? `${item.name} (Proxy)` : item.name
};
return {
[IndexedDbKeyPath]: item.address,

@ -17,7 +17,8 @@ export const useERC20Pool = singletonHook(initValue, () => {
erc20.forEach(item => {
erc20Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
isBridged: isTokenBridged(item.address),
name: item.implementationAddress ? `${item.name} (Proxy)` : item.name
};
})
setMode(erc20Map)
@ -55,6 +56,7 @@ export interface Erc20 {
image?: string;
};
isBridged: boolean;
implementationAddress: string
}
export type ERC20_Pool = Record<string, Erc20>;

@ -184,7 +184,7 @@ export const AbiMethodsView = (props: {
const itemType = input.type.slice(0, input.type.indexOf('[]'));
return (
<Field gap='5px'>
<Field gap='5px' key={idx}>
<Text size='small'>
{name} <span>({input.type})</span>
</Text>
@ -296,6 +296,7 @@ export const AbiMethodsView = (props: {
{abiMethod.outputs
? abiMethod.outputs.map((input, idx) => {
return (<AbiParam
key={idx}
readonly={true}
type={input.type}
name={input.name}

@ -8,6 +8,7 @@ import { ISourceCode } from "src/api/explorerV1";
import { AbiMethodsView } from "./AbiMethodView";
import { AbiItem } from "web3-utils";
import { Wallet } from "./ConnectWallets";
import {Address} from "../../../components/ui";
const StyledTextArea = styled(TextArea)`
padding: 0.75rem;
@ -27,13 +28,13 @@ const LabelSuccess = styled(Box)`
export const ContractDetails = (props: {
address: string;
contracts?: AddressDetails | null;
sourceCode?: ISourceCode | null;
sourceCode: ISourceCode | null;
implementation?: AddressDetails | null;
implementationSourceCode?: ISourceCode | null;
shard?: ShardID;
}) => {
if (!!props.sourceCode) {
if (!!props.sourceCode || (props.implementation && props.implementationSourceCode)) {
return (
<VerifiedContractDetails
sourceCode={props.sourceCode}
@ -71,6 +72,7 @@ export const AbiMethods = (props: {
{props.abi.map((abiMethod, idx) =>
abiMethod.name ? (
<AbiMethodsView
key={idx}
abiMethod={abiMethod}
address={props.address}
index={idx}
@ -201,9 +203,7 @@ export const NoVerifiedContractDetails = (props: {
<Item
label="Bytecode"
value={
<StyledTextArea readOnly={true} rows={15} cols={100}>
{props.contracts.bytecode || ""}
</StyledTextArea>
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.contracts.bytecode || ""} />
}
/>
</Box>
@ -248,7 +248,7 @@ const TabButton = (props: {
};
export const VerifiedContractDetails = (props: {
sourceCode: ISourceCode;
sourceCode: ISourceCode | null;
address: string;
contracts?: AddressDetails | null;
shard: number;
@ -269,7 +269,7 @@ export const VerifiedContractDetails = (props: {
: (chainId === 1666700000 || chainId === 1666900000);
try {
abiString = JSON.stringify(props.sourceCode.abi, null, 4);
abiString = JSON.stringify(props.sourceCode?.abi, null, 4);
} catch { }
return (
@ -281,20 +281,18 @@ export const VerifiedContractDetails = (props: {
onClick={() => setTab(V_TABS.CODE)}
selected={tab === V_TABS.CODE}
/>
{props.sourceCode.abi ? (
<>
<TabButton
<>
<TabButton
text={V_TABS.READ}
onClick={() => setTab(V_TABS.READ)}
selected={tab === V_TABS.READ}
/>
<TabButton
/>
<TabButton
text={V_TABS.WRITE}
onClick={() => setTab(V_TABS.WRITE)}
selected={tab === V_TABS.WRITE}
/>
</>
) : null}
/>
</>
{/*{props.sourceCode.proxyAddress && props.sourceCode.proxy ? (*/}
{/* <>*/}
{/* <TabButton*/}
@ -331,7 +329,7 @@ export const VerifiedContractDetails = (props: {
) : null}
</Box>
{tab === V_TABS.CODE ? (
{tab === V_TABS.CODE && props.sourceCode ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<Box direction="column" gap="30px">
<Box direction="column">
@ -357,8 +355,7 @@ export const VerifiedContractDetails = (props: {
<Item
label="Contract Source Code Verified"
value={
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode.sourceCode || ""}>
</StyledTextArea>
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode.sourceCode || ""} />
}
/>}
{props.sourceCode.supporting?.sources
@ -368,9 +365,7 @@ export const VerifiedContractDetails = (props: {
key={i}
label={`Verified ${source.substring(source.lastIndexOf('/') + 1)}`}
value={
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode.supporting.sources[source].source || ""}>
</StyledTextArea>
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode?.supporting.sources[source].source || ""} />
}
/>
})}
@ -382,9 +377,7 @@ export const VerifiedContractDetails = (props: {
key={i}
label={`Verified ${source.substring(source.lastIndexOf('/') + 1)}`}
value={
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode.supporting[source].source || ""}>
</StyledTextArea>
<StyledTextArea readOnly={true} rows={15} cols={100} value={props.sourceCode?.supporting[source].source || ""} />
}
/>
})}
@ -421,20 +414,24 @@ export const VerifiedContractDetails = (props: {
</Box>
) : null}
{tab === V_TABS.READ && props.sourceCode.abi ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<AbiMethods
abi={props.sourceCode?.abi?.filter(
(a) => a.stateMutability === "view" && a.type === "function"
)}
address={props.address}
isRead={V_TABS.READ === tab}
/>
</Box>
{tab === V_TABS.CODE && !props.sourceCode && props.contracts && <NoVerifiedContractDetails contracts={props.contracts} address={props.address} shard={props.shard} />}
{tab === V_TABS.READ ? (
(props.sourceCode && props.sourceCode.abi) ? <Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<AbiMethods
abi={props.sourceCode?.abi?.filter(
(a) => a.stateMutability === "view" && a.type === "function"
)}
address={props.address}
isRead={V_TABS.READ === tab}
/>
</Box> : <Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
Sorry, there are no available Contract ABI methods to read. Unable to read contract info.
</Box>
) : null}
{tab === V_TABS.WRITE && props.sourceCode.abi ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
{tab === V_TABS.WRITE ? (
(props.sourceCode && props.sourceCode.abi) ? <Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<Wallet onSetMetamask={setMetamask} onSetChainId={setChainId} />
<AbiMethods
abi={props.sourceCode.abi.filter(
@ -447,11 +444,17 @@ export const VerifiedContractDetails = (props: {
metamaskAddress={metamaskAddress}
validChainId={validChainId}
/>
</Box>
</Box> : <Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
Sorry, there are no available Contract ABI methods to write. Unable to write contract info.
</Box>
) : null}
{tab === V_TABS.READ_PROXY && props.implementation?.address && props.implementationSourceCode?.abi ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<Box style={{ padding: "10px" }} margin={{ top: "xsmall" }} gap={'16px'}>
<Box direction={'row'} align={'center'} gap={'4px'}>
<Text size={'small'}>ABI for the implementation contract at</Text>
<Address address={props.implementation?.address} hideCopyBtn={true} />
</Box>
<AbiMethods
abi={props.implementationSourceCode?.abi.filter(
(a) => a.stateMutability === "view" && a.type === "function"
@ -463,7 +466,11 @@ export const VerifiedContractDetails = (props: {
) : null}
{tab === V_TABS.WRITE_PROXY && props.implementation?.address && props.implementationSourceCode?.abi ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<Box style={{ padding: "10px" }} margin={{ top: "xsmall" }} gap={'16px'}>
<Box direction={'row'} align={'center'} gap={'4px'}>
<Text size={'small'}>ABI for the implementation contract at</Text>
<Address address={props.implementation?.address} hideCopyBtn={true} />
</Box>
<Wallet onSetMetamask={setMetamask} onSetChainId={setChainId} />
<AbiMethods
abi={props.implementationSourceCode?.abi.filter(

@ -305,7 +305,7 @@ export function Transactions(props: {
rowDetails={props.rowDetails}
showPages={totalElements > 0}
/>
{['transaction', 'erc20', 'erc721', 'erc1155'].includes(props.type) &&
{['transaction', 'erc20', 'erc721', 'erc1155', 'internal_transaction'].includes(props.type) &&
<Box style={{ alignItems: 'flex-end' }}>
<ExportToCsvButton address={id} type={props.type} />
</Box>

@ -72,7 +72,7 @@ export function getERC20Columns(id: string): ColumnConfig<any>[] {
<Text
color="minorText"
size="small"
style={{ width: '320px' }}
style={{ width: '260px' }}
>
Value
</Text>
@ -103,7 +103,7 @@ export function getERC20Columns(id: string): ColumnConfig<any>[] {
<Text
color="minorText"
size="small"
style={{ width: '120px' }}
style={{ width: '150px' }}
>
Token
</Text>

@ -1,4 +1,4 @@
import { RelatedTransaction } from "../../types";
import {InternalTransaction, RelatedTransaction} from "../../types";
import dayjs from "dayjs";
import Big from "big.js";
import { calculateFee, calculateFeePriceUSD } from "../../utils/fee";
@ -62,6 +62,24 @@ const mapHrc20TxToExport = (ownerAddress: string, tx: any, erc20Map: Record<stri
}
}
const mapInternalTxToExport = (ownerAddress: string, tx: InternalTransaction, onePrice: number) => {
const txDate = dayjs(tx.timestamp)
const isSender = ownerAddress === tx.from
return {
Txhash: tx.transactionHash,
Blockno: tx.blockNumber,
UnixTimestamp: txDate.unix(),
DateTime: txDate.format('YYYY-MM-DD HH:MM:ss'),
From: tx.from,
To: tx.to,
['Value_IN(ONE)']: convertValue(isSender ? '0': tx.value),
['Value_OUT(ONE)']: convertValue(isSender ? tx.value : '0'),
[`CurrentValue @ $${onePrice}/ONE`]: convertValue(onePrice * +tx.value),
Method: tx.input.slice(0, 10),
Type: tx.type
}
}
export interface IDownloadCsvParams {
type: TRelatedTransaction
address: string
@ -76,12 +94,15 @@ export const downloadCSV = (params: IDownloadCsvParams, filename: string) => {
const { type, address, txs, onePrice, erc20Map, erc721Map, erc1155Map } = params
const mapTx = (tx: any) => {
return type === 'transaction'
? mapRelatedTxToExport(address, tx, onePrice)
: mapHrc20TxToExport(address, tx, erc20Map, erc721Map, erc1155Map)
if(type === 'erc20') {
return mapHrc20TxToExport(address, tx, erc20Map, erc721Map, erc1155Map)
} else if (type === 'internal_transaction') {
return mapInternalTxToExport(address, tx, onePrice)
}
return mapRelatedTxToExport(address, tx, onePrice)
}
const mappedTxs = txs.map((tx) => mapTx(tx))
const mappedTxs = txs.map(mapTx)
const header = mappedTxs.filter((_, index) => index === 0).map(item => Object.keys(item))
const body = mappedTxs
.map((item) => Object.values(item))

@ -4,7 +4,7 @@ import { Address, BaseContainer, BasePage, Button, TipContent } from "src/compon
import { Heading, DateInput, Box, Spinner, Tip, Text } from "grommet";
import styled from "styled-components";
import useQuery from "../../hooks/useQuery";
import { getRelatedTransactionsByType } from "../../api/client";
import {getBlocks, getRelatedTransactionsByType} from "../../api/client";
import { downloadCSV } from "./export-utils";
import dayjs from "dayjs";
import { toaster } from "../../App";
@ -35,9 +35,11 @@ const DownloadButton = styled(Button)`
height: 42px;
`
const DefaultLimit = 5000
export const ExportData = () => {
const query = useQuery();
const address = query.get('address') || '';
const address = (query.get('address') || '').toLowerCase()
const type = (query.get('type') || 'transaction') as TRelatedTransaction;
const dateFormat = 'YYYY-MM-DD'
@ -63,22 +65,6 @@ export const ExportData = () => {
inputProps: { width: '170px' }
}
const filter = {
offset: 0,
limit: 5000,
orderBy: 'block_number',
orderDirection: 'desc',
filters: [{
type: 'gt',
property: 'timestamp',
value: `'${dateFrom}'`
}, {
type: 'lt',
property: 'timestamp',
value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'`
}]
}
const showErrorNotification = () => {
toaster.show({
message: () => (
@ -93,11 +79,86 @@ export const ExportData = () => {
const onDownloadClicked = async () => {
try {
setIsDownloading(true)
const txsFilter: any = {
offset: 0,
limit: DefaultLimit,
orderBy: 'block_number',
orderDirection: 'desc',
filters: []
}
// if (type === 'transaction') {
// if (dateFrom) {
// txsFilter.filters.push({
// type: 'gt',
// property: 'timestamp',
// value: `'${dateFrom}'`
// })
// }
// if (dateTo) {
// txsFilter.filters.push({
// type: 'lt',
// property: 'timestamp',
// value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'`
// })
// }
// }
// Get block numbers first, then filter internal by block number (not timestamp)
if (dateFrom) {
const blockFromFilter = {
offset: 0,
limit: 1,
orderBy: 'number',
orderDirection: 'desc',
filters: [{
type: 'lte',
property: 'timestamp',
value: `'${dateFrom}'`
}]
}
const [blockFrom] = await getBlocks([0, blockFromFilter]);
if(blockFrom) {
txsFilter.filters.push({
type: 'gte',
property: 'block_number',
value: blockFrom.number
})
}
}
if (dateTo) {
const diff = dayjs().diff(dayjs(dateTo), 'days')
const isCurrentDateOrNext = diff <= 0
const blockToFilter = {
offset: 0,
limit: 1,
orderBy: 'number',
orderDirection: 'desc',
filters: [] as any
}
if(!isCurrentDateOrNext) {
blockToFilter.filters.push({
type: 'lte',
property: 'timestamp',
value: `'${dayjs(dateTo).add(1, 'day').format(dateFormat)}'`
})
}
const [blockTo] = await getBlocks([0, blockToFilter]);
if(blockTo) {
txsFilter.filters.push({
type: 'lte',
property: 'block_number',
value: blockTo.number
})
}
}
const txs = await getRelatedTransactionsByType([
0,
address,
type,
filter,
txsFilter,
]);
const downloadParams = {
type,
@ -126,20 +187,27 @@ export const ExportData = () => {
}
const getTxTextType = (type: TRelatedTransaction) => {
return type === 'transaction' ? 'transactions' : type + ' transactions'
if(type === 'transaction') {
return 'transactions'
} else if (type === 'internal_transaction') {
return 'internal transactions'
}
return type + ' transactions'
}
return <BaseContainer pad={{ horizontal: "0" }} style={{ maxWidth: '700px', alignSelf: 'center' }}>
return <BaseContainer pad={{ horizontal: "0" }} style={{ maxWidth: '740px', alignSelf: 'center' }}>
<Heading size="xsmall" margin={{ bottom: "medium", top: "0" }}>
Export transactions
<Box gap={'4px'} direction={'row'} align={'baseline'}>
Download Data
<Text size={'medium'} weight={'normal'} color={'minorText'}>({getTxTextType(type)})</Text>
</Box>
</Heading>
<BasePage pad={"medium"} style={{ overflow: "inherit" }}>
<Box pad={{ top: 'medium', bottom: 'medium' }} style={{ display: 'inline-block' }}>
Export the last {filter.limit} {getTxTextType(type)} for <Address address={address} />
Export the last {DefaultLimit} {getTxTextType(type)} for <Address address={address} />
{type === 'transaction' && 'starting from'}
</Box>
{/* TODO: support timestamp filter on backend side */}
{type === 'transaction' &&
{!['erc20', 'internal_transaction'].includes(type) &&
<FlexWrapper>
<InputContainer>
<Tip dropProps={{ align: { bottom: "top" }}} content={<TipContent showArrow={true} message={'Select start date'} />}>

@ -1,196 +0,0 @@
import React, { useEffect, useState } from "react";
import {Box, Heading, Select, Spinner, Text, TextArea, TextInput, Tip} from "grommet";
import { Alert, StatusGood } from "grommet-icons";
import { useHistory } from "react-router-dom";
import useQuery from "../../hooks/useQuery";
import {
assignProxyImplementation,
getProxyImplementation
} from "../../api/client";
import {Address, BaseContainer, BasePage, Button} from "../../components/ui";
import styled from "styled-components";
import {getAddress} from "../../utils";
import {ISourceCode, loadSourceCode} from "../../api/explorerV1";
export const ActionButton = styled(Button)`
font-size: 14px;
padding: 8px 8px 5px 8px;
font-weight: 500;
`;
export function VerifyProxyContract() {
const query = useQuery();
const history = useHistory();
const queryAddress = query.get('a') || '';
const [isLoading, setIsLoading] = useState(false)
const [contractAddress, setContractAddress] = useState(queryAddress)
const [implementationAddress, setImplementationAddress] = useState('')
const [implementationCode, setImplementationCode] = useState<ISourceCode>()
const [contractCode, setContractCode] = useState<ISourceCode>()
const [verificationError, setVerificationError] = useState('')
const [isVerified, setIsVerified] = useState(false)
const enrichAddress = (address: string) => {
let a = address.trim().toLowerCase()
if(address.startsWith('one1')) { // convert one1 to 0x before send request to backend
try {
a = getAddress(address).basicHex
} catch (e) {}
}
return a
}
const setDefaultState = () => {
setImplementationAddress('')
setVerificationError('')
setIsVerified(false)
}
const validateAddress = (address: string) => {
if(address.length === 0) {
return ''
} else {
if(!address.startsWith('0x') && !address.startsWith('one1')) {
return 'Address should start with 0x or one1'
}
if(address.length != 42) {
return 'Address must be 42 characters long'
}
}
return ''
}
const verifyContract = async () => {
setImplementationAddress('')
const err = validateAddress(contractAddress)
if(err) {
setVerificationError(err)
} else {
try {
setIsLoading(true)
setVerificationError('')
const formattedContractAddress = enrichAddress(contractAddress)
const implContract = await getProxyImplementation([0, formattedContractAddress]);
if(implContract) {
try {
const code = await loadSourceCode(formattedContractAddress, 0)
const implCode = await loadSourceCode(implContract.address, 0)
setContractCode(code)
setImplementationCode(implCode)
} catch (e) {
setVerificationError('Proxy or implementation contract source code is not verified. Please verify and publish the contract source before proceeding with this proxy verification.')
}
setImplementationAddress(implContract.address)
} else {
setImplementationAddress('')
setVerificationError('This contract does not look like it contains any delegatecall opcode sequence.')
}
} catch (e) {
console.error('Unable to verify contract', e)
setVerificationError('Cannot verify contract address, try again later')
} finally {
setIsLoading(false)
}
}
}
const applyImplementationAddress = async () => {
const contract = await assignProxyImplementation([0, enrichAddress(contractAddress), implementationAddress])
setDefaultState()
setIsVerified(true)
}
const onChangeInput = (value: string) => {
setContractAddress(value)
setDefaultState()
}
return (
<BaseContainer pad={{ horizontal: "0" }}>
<Heading size="small" margin={{ bottom: "medium", top: "0" }}>
Proxy Contract Verification
</Heading>
<BasePage pad={"small"} style={{ overflow: "inherit" }}>
<Box pad={'16px'} gap={'16px'} width={'600px'}>
<Box direction="row" justify={'start'} wrap>
<Box margin={'0'}>
<Text size={'small'}>Please enter the Proxy Contract Address you would like to verify</Text>
<Box direction="row">
<Box width={'600px'}>
<TextInput
placeholder={"Contract address"}
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => onChangeInput(evt.currentTarget.value)}
value={contractAddress}
disabled={false}
/>
</Box>
</Box>
</Box>
</Box>
{!implementationAddress &&
<Box direction={'column'} gap={'16px'}>
{verificationError &&
<Box round={'8px'} background={'backgroundError'} pad={'16px'}>
<Text color={'text'} size={'small'}>
{verificationError}
</Text>
</Box>
}
{isVerified &&
<Box round={'8px'} background={'backgroundSuccess'} pad={'16px'}>
<Text color={'successText'} size={'small'}>
Successfully saved. Feel free to return to the address <Address address={contractAddress} hideCopyBtn={true} /> to view updates.
</Text>
</Box>
}
<Box direction={'row'} gap={'16px'}>
<Box width={'small'}>
<ActionButton
disabled={!contractAddress || isLoading}
onClick={verifyContract}>
Verify
</ActionButton>
</Box>
<Box width={'small'}>
<ActionButton
disabled={!contractAddress}
onClick={() => {
setDefaultState()
setContractAddress('')
}
}>Clear</ActionButton>
</Box>
</Box>
</Box>
}
{implementationAddress &&
<Box direction={'column'} gap={'16px'}>
<Box>
<Text size={'small'}>The proxy's implementation contract is found at:</Text>
<Text size={'small'} weight={'bold'} color={'successText'}>
<Address address={implementationAddress} />
</Text>
</Box>
{verificationError &&
<Box round={'8px'} background={'backgroundError'} pad={'16px'}>
<Text color={'text'} size={'small'}>
{verificationError}
</Text>
</Box>
}
<Box direction={'row'} gap={'16px'}>
<Box width={'small'}>
<ActionButton
disabled={isLoading || !implementationCode || !contractCode}
onClick={applyImplementationAddress}>Save</ActionButton>
</Box>
<Box width={'small'}><ActionButton onClick={setDefaultState}>Clear</ActionButton></Box>
</Box>
</Box>
}
</Box>
</BasePage>
</BaseContainer>
)
}

@ -231,6 +231,7 @@ export type InternalTransaction = {
blockNumber: BlockNumber;
transactionHash: TransactionHash;
signatures?: any[];
timestamp?: string
};
export type Transaction = {

Loading…
Cancel
Save