parent
6ed602bb7c
commit
91ebeca78d
@ -1,25 +1,3 @@ |
||||
import React from 'react' |
||||
import {PluginClient} from '@remixproject/plugin' |
||||
|
||||
import {Receipt, ThemeType} from './types' |
||||
|
||||
export const AppContext = React.createContext({ |
||||
apiKey: '', |
||||
setAPIKey: (value: string) => { |
||||
console.log('Set API Key from Context') |
||||
}, |
||||
clientInstance: {} as PluginClient, |
||||
receipts: [] as Receipt[], |
||||
setReceipts: (receipts: Receipt[]) => { |
||||
console.log('Calling Set Receipts') |
||||
}, |
||||
contracts: [] as string[], |
||||
setContracts: (contracts: string[]) => { |
||||
console.log('Calling Set Contract Names') |
||||
}, |
||||
themeType: 'dark' as ThemeType, |
||||
setThemeType: (themeType: ThemeType) => { |
||||
console.log('Calling Set Theme Type') |
||||
}, |
||||
networkName: '' |
||||
}) |
||||
export const AppContext = React.createContext({}) |
||||
|
@ -1,70 +0,0 @@ |
||||
import { PluginClient } from '@remixproject/plugin' |
||||
import { createClient } from '@remixproject/plugin-webview' |
||||
import { verify, EtherScanReturn } from './utils/verify' |
||||
import { getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus } from './utils' |
||||
import EventManager from 'events' |
||||
|
||||
export class EtherscanPluginClient extends PluginClient { |
||||
public internalEvents: EventManager |
||||
|
||||
constructor() { |
||||
super() |
||||
this.internalEvents = new EventManager() |
||||
createClient(this) |
||||
this.onload() |
||||
} |
||||
|
||||
onActivation(): void { |
||||
this.internalEvents.emit('etherscan_activated') |
||||
} |
||||
|
||||
async verify( |
||||
apiKey: string, |
||||
contractAddress: string, |
||||
contractArguments: string, |
||||
contractName: string, |
||||
compilationResultParam: any, |
||||
chainRef?: number | string, |
||||
isProxyContract?: boolean, |
||||
expectedImplAddress?: string |
||||
) { |
||||
const result = await verify( |
||||
apiKey, |
||||
contractAddress, |
||||
contractArguments, |
||||
contractName, |
||||
compilationResultParam, |
||||
chainRef, |
||||
isProxyContract, |
||||
expectedImplAddress, |
||||
this, |
||||
(value: EtherScanReturn) => {}, |
||||
(value: string) => {} |
||||
) |
||||
return result |
||||
} |
||||
|
||||
async receiptStatus(receiptGuid: string, apiKey: string, isProxyContract: boolean) { |
||||
try { |
||||
const { network, networkId } = await getNetworkName(this) |
||||
if (network === 'vm') { |
||||
throw new Error('Cannot check the receipt status in the selected network') |
||||
} |
||||
const etherscanApi = getEtherScanApi(networkId) |
||||
let receiptStatus |
||||
|
||||
if (isProxyContract) receiptStatus = await getProxyContractReceiptStatus(receiptGuid, apiKey, etherscanApi) |
||||
else receiptStatus = await getReceiptStatus(receiptGuid, apiKey, etherscanApi) |
||||
return { |
||||
message: receiptStatus.result, |
||||
succeed: receiptStatus.status === '0' ? false : true |
||||
} |
||||
} catch (e: any) { |
||||
return { |
||||
status: 'error', |
||||
message: e.message, |
||||
succeed: false |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,136 +1,12 @@ |
||||
import React, {useState, useEffect, useRef} from 'react' |
||||
|
||||
import {CompilationFileSources, CompilationResult} from '@remixproject/plugin-api' |
||||
|
||||
import { EtherscanPluginClient } from './EtherscanPluginClient' |
||||
|
||||
import {AppContext} from './AppContext' |
||||
import {DisplayRoutes} from './routes' |
||||
|
||||
import {useLocalStorage} from './hooks/useLocalStorage' |
||||
|
||||
import {getReceiptStatus, getEtherScanApi, getNetworkName, getProxyContractReceiptStatus} from './utils' |
||||
import {Receipt, ThemeType} from './types' |
||||
import DisplayRoutes from './routes' |
||||
|
||||
import './App.css' |
||||
|
||||
export const getNewContractNames = (compilationResult: CompilationResult) => { |
||||
const compiledContracts = compilationResult.contracts |
||||
let result: string[] = [] |
||||
|
||||
for (const file of Object.keys(compiledContracts)) { |
||||
const newContractNames = Object.keys(compiledContracts[file]) |
||||
|
||||
result = [...result, ...newContractNames] |
||||
} |
||||
|
||||
return result |
||||
} |
||||
|
||||
const plugin = new EtherscanPluginClient() |
||||
|
||||
const App = () => { |
||||
const [apiKey, setAPIKey] = useLocalStorage('apiKey', '') |
||||
const [receipts, setReceipts] = useLocalStorage('receipts', [])
|
||||
const [contracts, setContracts] = useState<string[]>([]) |
||||
const [themeType, setThemeType] = useState<ThemeType>('dark') |
||||
const [networkName, setNetworkName] = useState('Loading...') |
||||
const timer = useRef(null) |
||||
const contractsRef = useRef(contracts) |
||||
|
||||
contractsRef.current = contracts |
||||
|
||||
const setListeners = () => { |
||||
plugin.on('solidity', 'compilationFinished', (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => { |
||||
const newContractsNames = getNewContractNames(data) |
||||
|
||||
const newContractsToSave: string[] = [...contractsRef.current, ...newContractsNames] |
||||
|
||||
const uniqueContracts: string[] = [...new Set(newContractsToSave)] |
||||
|
||||
setContracts(uniqueContracts) |
||||
}) |
||||
plugin.on('blockchain' as any, 'networkStatus', (result) => { |
||||
setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`) |
||||
}) |
||||
// @ts-ignore
|
||||
plugin.call('blockchain', 'getCurrentNetworkStatus').then((result: any) => setNetworkName(`${result.network.name} ${result.network.id !== '-' ? `(Chain id: ${result.network.id})` : '(Not supported)'}`)) |
||||
|
||||
} |
||||
|
||||
useEffect(() => { |
||||
plugin.onload(() => { |
||||
setListeners() |
||||
}) |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
let receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => item.status === 'Pending in queue' || item.status === 'Max rate limit reached') |
||||
|
||||
if (receiptsNotVerified.length > 0) { |
||||
if (timer.current) { |
||||
clearInterval(timer.current) |
||||
timer.current = null |
||||
} |
||||
timer.current = setInterval(async () => { |
||||
const {network, networkId} = await getNetworkName(plugin) |
||||
|
||||
if (!plugin) return |
||||
if (network === 'vm') return |
||||
let newReceipts = receipts |
||||
|
||||
for (const item of receiptsNotVerified) { |
||||
await new Promise((r) => setTimeout(r, 500)) // avoid api rate limit exceed.
|
||||
let status |
||||
if (item.isProxyContract) { |
||||
status = await getProxyContractReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId)) |
||||
if (status.status === '1') { |
||||
status.message = status.result |
||||
status.result = 'Successfully Updated' |
||||
} |
||||
} else status = await getReceiptStatus(item.guid, apiKey, getEtherScanApi(networkId)) |
||||
if (status.result === 'Pass - Verified' || status.result === 'Already Verified' || status.result === 'Successfully Updated') { |
||||
newReceipts = newReceipts.map((currentReceipt: Receipt) => { |
||||
if (currentReceipt.guid === item.guid) { |
||||
const res = { |
||||
...currentReceipt, |
||||
status: status.result |
||||
} |
||||
if (currentReceipt.isProxyContract) res.message = status.message |
||||
return res |
||||
} |
||||
return currentReceipt |
||||
}) |
||||
} |
||||
} |
||||
receiptsNotVerified = newReceipts.filter((item: Receipt) => item.status === 'Pending in queue' || item.status === 'Max rate limit reached') |
||||
if (timer.current && receiptsNotVerified.length === 0) { |
||||
clearInterval(timer.current) |
||||
timer.current = null |
||||
} |
||||
setReceipts(newReceipts) |
||||
}, 10000) |
||||
} |
||||
}, [receipts]) |
||||
|
||||
return ( |
||||
<AppContext.Provider |
||||
value={{ |
||||
apiKey, |
||||
setAPIKey, |
||||
clientInstance: plugin, |
||||
receipts, |
||||
setReceipts, |
||||
contracts, |
||||
setContracts, |
||||
themeType, |
||||
setThemeType, |
||||
networkName |
||||
}} |
||||
> |
||||
{ plugin && <DisplayRoutes /> } |
||||
</AppContext.Provider> |
||||
) |
||||
return <DisplayRoutes /> |
||||
} |
||||
|
||||
export default App |
||||
|
@ -1,81 +0,0 @@ |
||||
import React from 'react' |
||||
|
||||
import {NavLink} from 'react-router-dom' |
||||
import {CustomTooltip} from '@remix-ui/helper' |
||||
import {AppContext} from '../AppContext' |
||||
|
||||
interface Props { |
||||
title?: string |
||||
from: string |
||||
} |
||||
|
||||
interface IconProps { |
||||
from: string |
||||
} |
||||
|
||||
const HomeIcon = ({from}: IconProps) => { |
||||
return ( |
||||
<NavLink |
||||
data-id="home" |
||||
to={{ |
||||
pathname: '/' |
||||
}} |
||||
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')} |
||||
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})} |
||||
state={from} |
||||
> |
||||
<CustomTooltip tooltipText="Home" tooltipId="etherscan-nav-home" placement="bottom"> |
||||
<i className="fas fa-home"></i> |
||||
</CustomTooltip> |
||||
</NavLink> |
||||
) |
||||
} |
||||
|
||||
const ReceiptsIcon = ({from}: IconProps) => { |
||||
return ( |
||||
<NavLink |
||||
data-id="receipts" |
||||
to={{ |
||||
pathname: '/receipts' |
||||
}} |
||||
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')} |
||||
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})} |
||||
state={from} |
||||
> |
||||
<CustomTooltip tooltipText="Receipts" tooltipId="etherscan-nav-receipts" placement="bottom"> |
||||
<i className="fas fa-receipt"></i> |
||||
</CustomTooltip> |
||||
</NavLink> |
||||
) |
||||
} |
||||
|
||||
const SettingsIcon = ({from}: IconProps) => { |
||||
return ( |
||||
<NavLink |
||||
data-id="settings" |
||||
to={{ |
||||
pathname: '/settings' |
||||
}} |
||||
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')} |
||||
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})} |
||||
state={from} |
||||
> |
||||
<CustomTooltip tooltipText="Settings" tooltipId="etherscan-nav-settings" placement="bottom"> |
||||
<i className="fas fa-cog"></i> |
||||
</CustomTooltip> |
||||
</NavLink> |
||||
) |
||||
} |
||||
|
||||
export const HeaderWithSettings = ({title = '', from}) => { |
||||
return ( |
||||
<div className="d-flex justify-content-between"> |
||||
<h6 className="d-inline">{title}</h6> |
||||
<div className="nav"> |
||||
<HomeIcon from={from} /> |
||||
<ReceiptsIcon from={from} /> |
||||
<SettingsIcon from={from} /> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,39 @@ |
||||
import React from 'react' |
||||
|
||||
import {NavLink} from 'react-router-dom' |
||||
|
||||
interface NavItemProps { |
||||
to: string |
||||
icon: JSX.Element |
||||
title: string |
||||
} |
||||
|
||||
const NavItem = ({to, icon, title}: NavItemProps) => { |
||||
return ( |
||||
<NavLink |
||||
// data-id="home"
|
||||
to={to} |
||||
className={({isActive}) => (isActive ? 'border border-secondary shadow-none btn p-1 m-0' : 'border-0 shadow-none btn p-1 m-0')} |
||||
style={({isActive}) => (!isActive ? {width: '1.8rem', filter: 'contrast(0.5)'} : {width: '1.8rem'})} |
||||
// state={from}
|
||||
> |
||||
<div> |
||||
<div>{icon}</div> |
||||
<div>{title}</div> |
||||
</div> |
||||
</NavLink> |
||||
) |
||||
} |
||||
|
||||
export const NavMenu = () => { |
||||
return ( |
||||
<div className="d-flex justify-content-between"> |
||||
<div className="nav"> |
||||
<NavItem to="/" icon={<i className="fas fa-home"></i>} title="Verify" /> |
||||
<NavItem to="/receipts" icon={<i className="fas fa-home"></i>} title="Receipts" /> |
||||
<NavItem to="/lookup" icon={<i className="fas fa-receipt"></i>} title="Lookup" /> |
||||
<NavItem to="/settings" icon={<i className="fas fa-cog"></i>} title="Settings" /> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
@ -1,34 +0,0 @@ |
||||
import React from 'react' |
||||
import {CustomTooltip} from '@remix-ui/helper' |
||||
|
||||
interface Props { |
||||
text: string |
||||
isSubmitting?: boolean |
||||
dataId?: string |
||||
disable?: boolean |
||||
} |
||||
|
||||
export const SubmitButton = ({text, dataId, isSubmitting = false, disable = true}) => { |
||||
return ( |
||||
<div> |
||||
<button data-id={dataId} type="submit" className="btn btn-primary btn-block p-1 text-decoration-none" disabled={disable}> |
||||
<CustomTooltip |
||||
tooltipText={disable ? 'Fill in the valid value(s) and select a supported network' : 'Click to proceed'} |
||||
tooltipId={'etherscan-submit-button-' + dataId} |
||||
tooltipTextClasses="border bg-light text-dark p-1 pr-3" |
||||
placement="bottom" |
||||
> |
||||
<div> |
||||
{!isSubmitting && text} |
||||
{isSubmitting && ( |
||||
<div> |
||||
<span className="spinner-border spinner-border-sm mr-1" role="status" aria-hidden="true" /> |
||||
Verifying... Please wait |
||||
</div> |
||||
)} |
||||
</div> |
||||
</CustomTooltip> |
||||
</button> |
||||
</div> |
||||
) |
||||
} |
@ -1,2 +1 @@ |
||||
export { HeaderWithSettings } from "./HeaderWithSettings" |
||||
export { SubmitButton } from "./SubmitButton" |
||||
export {NavMenu} from './NavMenu' |
||||
|
@ -1,9 +0,0 @@ |
||||
export type ReceiptStatus = "Pending in queue" | "Pass - Verified" | "Already Verified" | "Max rate limit reached" | "Successfully Updated" |
||||
|
||||
export interface Receipt { |
||||
guid: string |
||||
status: ReceiptStatus |
||||
isProxyContract: boolean |
||||
message?: string |
||||
succeed?: boolean |
||||
} |
@ -1,2 +1 @@ |
||||
export * from "./Receipt" |
||||
export * from "./ThemeType" |
||||
export * from './ThemeType' |
||||
|
@ -1 +0,0 @@ |
||||
export * from "./utilities" |
@ -1,42 +0,0 @@ |
||||
export const scanAPIurls = { |
||||
// all mainnet
|
||||
1: 'https://api.etherscan.io/api', |
||||
56: 'https://api.bscscan.com/api', |
||||
137: 'https://api.polygonscan.com/api', |
||||
250: 'https://api.ftmscan.com/api', |
||||
42161: 'https://api.arbiscan.io/api', |
||||
43114: 'https://api.snowtrace.io/api', |
||||
1285: 'https://api-moonriver.moonscan.io/api', |
||||
1284: 'https://api-moonbeam.moonscan.io/api', |
||||
25: 'https://api.cronoscan.com/api', |
||||
199: 'https://api.bttcscan.com/api', |
||||
10: 'https://api-optimistic.etherscan.io/api', |
||||
42220: 'https://api.celoscan.io/api', |
||||
288: 'https://api.bobascan.com/api', |
||||
100: 'https://api.gnosisscan.io/api', |
||||
1101: 'https://api-zkevm.polygonscan.com/api', |
||||
59144: 'https://api.lineascan.build/api', |
||||
8453: 'https://api.basescan.org/api', |
||||
534352: 'https://api.scrollscan.com/api', |
||||
|
||||
// all testnet
|
||||
17000: 'https://api-holesky.etherscan.io/api', |
||||
11155111: 'https://api-sepolia.etherscan.io/api', |
||||
97: 'https://api-testnet.bscscan.com/api', |
||||
80001: 'https://api-testnet.polygonscan.com/api', |
||||
4002: 'https://api-testnet.ftmscan.com/api', |
||||
421611: 'https://api-testnet.arbiscan.io/api', |
||||
42170: 'https://api-nova.arbiscan.io/api', |
||||
43113: 'https://api-testnet.snowtrace.io/api', |
||||
1287: 'https://api-moonbase.moonscan.io/api', |
||||
338: 'https://api-testnet.cronoscan.com/api', |
||||
1028: 'https://api-testnet.bttcscan.com/api', |
||||
420: 'https://api-goerli-optimistic.etherscan.io/api', |
||||
44787: 'https://api-alfajores.celoscan.io/api', |
||||
2888: 'https://api-testnet.bobascan.com/api', |
||||
84531: 'https://api-goerli.basescan.org/api', |
||||
84532: "https://api-sepolia.basescan.org/api", |
||||
1442: 'https://api-testnet-zkevm.polygonscan.com/api', |
||||
59140: 'https://api-testnet.lineascan.build/api', |
||||
534351: 'https://api-sepolia.scrollscan.com/api', |
||||
} |
@ -1,30 +0,0 @@ |
||||
export const verifyScript = ` |
||||
/** |
||||
* @param {string} apikey - etherscan api key |
||||
* @param {string} contractAddress - Address of the contract to verify |
||||
* @param {string} contractArguments - Parameters used in the contract constructor during the initial deployment. It should be the hex encoded value |
||||
* @param {string} contractName - Name of the contract |
||||
* @param {string} contractFile - File where the contract is located |
||||
* @param {number | string} chainRef - Network chain id or API URL (optional) |
||||
* @param {boolean} isProxyContract - true, if contract is a proxy contract (optional) |
||||
* @param {string} expectedImplAddress - Implementation contract address, in case of proxy contract verification (optional) |
||||
* @returns {{ guid, status, message, succeed }} verification result |
||||
*/ |
||||
export const verify = async (apikey: string, contractAddress: string, contractArguments: string, contractName: string, contractFile: string, chainRef?: number | string, isProxyContract?: boolean, expectedImplAddress?: string) => { |
||||
const compilationResultParam = await remix.call('compilerArtefacts' as any, 'getCompilerAbstract', contractFile) |
||||
console.log('verifying.. ' + contractName) |
||||
// update apiKey and chainRef to verify contract on multiple networks
|
||||
return await remix.call('etherscan' as any, 'verify', apikey, contractAddress, contractArguments, contractName, compilationResultParam, chainRef, isProxyContract, expectedImplAddress) |
||||
}` |
||||
|
||||
export const receiptGuidScript = ` |
||||
/** |
||||
* @param {string} apikey - etherscan api key |
||||
* @param {string} guid - receipt id |
||||
* @param {boolean} isProxyContract - true, if contract is a proxy contract (optional) |
||||
* @returns {{ status, message, succeed }} receiptStatus |
||||
*/ |
||||
export const receiptStatus = async (apikey: string, guid: string, isProxyContract?: boolean) => { |
||||
return await remix.call('etherscan' as any, 'receiptStatus', guid, apikey, isProxyContract) |
||||
} |
||||
` |
@ -1,69 +0,0 @@ |
||||
import { PluginClient } from "@remixproject/plugin" |
||||
import axios from 'axios' |
||||
import { scanAPIurls } from "./networks" |
||||
type RemixClient = PluginClient |
||||
|
||||
/* |
||||
status: 0=Error, 1=Pass |
||||
message: OK, NOTOK |
||||
result: explanation |
||||
*/ |
||||
export type receiptStatus = { |
||||
result: string |
||||
message: string |
||||
status: string |
||||
} |
||||
|
||||
export const getEtherScanApi = (networkId: any) => { |
||||
if (!(networkId in scanAPIurls)) { |
||||
throw new Error("no known network to verify against") |
||||
} |
||||
const apiUrl = (scanAPIurls as any)[networkId] |
||||
return apiUrl |
||||
} |
||||
|
||||
export const getNetworkName = async (client: RemixClient) => { |
||||
const network = await client.call("network", "detectNetwork") |
||||
if (!network) { |
||||
throw new Error("no known network to verify against") |
||||
} |
||||
return { network: network.name!.toLowerCase(), networkId: network.id } |
||||
} |
||||
|
||||
export const getReceiptStatus = async ( |
||||
receiptGuid: string, |
||||
apiKey: string, |
||||
etherscanApi: string |
||||
): Promise<receiptStatus> => { |
||||
const params = `guid=${receiptGuid}&module=contract&action=checkverifystatus&apiKey=${apiKey}` |
||||
try { |
||||
const response = await axios.get(`${etherscanApi}?${params}`) |
||||
const { result, message, status } = response.data |
||||
return { |
||||
result, |
||||
message, |
||||
status, |
||||
} |
||||
} catch (error) { |
||||
console.error(error) |
||||
} |
||||
} |
||||
|
||||
export const getProxyContractReceiptStatus = async ( |
||||
receiptGuid: string, |
||||
apiKey: string, |
||||
etherscanApi: string |
||||
): Promise<receiptStatus> => { |
||||
const params = `guid=${receiptGuid}&module=contract&action=checkproxyverification&apiKey=${apiKey}` |
||||
try { |
||||
const response = await axios.get(`${etherscanApi}?${params}`) |
||||
const { result, message, status } = response.data |
||||
return { |
||||
result, |
||||
message, |
||||
status, |
||||
} |
||||
} catch (error) { |
||||
console.error(error) |
||||
} |
||||
} |
@ -1,206 +0,0 @@ |
||||
import { getNetworkName, getEtherScanApi, getReceiptStatus, getProxyContractReceiptStatus } from "." |
||||
import { CompilationResult } from "@remixproject/plugin-api" |
||||
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||
import axios from 'axios' |
||||
import { PluginClient } from "@remixproject/plugin" |
||||
|
||||
const resetAfter10Seconds = (client: PluginClient, setResults: (value: string) => void) => { |
||||
setTimeout(() => { |
||||
client.emit("statusChanged", { key: "none" }) |
||||
setResults("") |
||||
}, 10000) |
||||
} |
||||
|
||||
export type EtherScanReturn = { |
||||
guid: any, |
||||
status: any, |
||||
} |
||||
export const verify = async ( |
||||
apiKeyParam: string, |
||||
contractAddress: string, |
||||
contractArgumentsParam: string, |
||||
contractName: string, |
||||
compilationResultParam: CompilerAbstract, |
||||
chainRef: number | string, |
||||
isProxyContract: boolean, |
||||
expectedImplAddress: string, |
||||
client: PluginClient, |
||||
onVerifiedContract: (value: EtherScanReturn) => void, |
||||
setResults: (value: string) => void |
||||
) => { |
||||
let networkChainId |
||||
let etherscanApi |
||||
if (chainRef) { |
||||
if (typeof chainRef === 'number') { |
||||
networkChainId = chainRef |
||||
etherscanApi = getEtherScanApi(networkChainId) |
||||
} else if (typeof chainRef === 'string') etherscanApi = chainRef |
||||
} else { |
||||
const { network, networkId } = await getNetworkName(client) |
||||
if (network === "vm") { |
||||
return { |
||||
succeed: false, |
||||
message: "Cannot verify in the selected network" |
||||
} |
||||
} else { |
||||
networkChainId = networkId |
||||
etherscanApi = getEtherScanApi(networkChainId) |
||||
} |
||||
} |
||||
|
||||
try { |
||||
const contractMetadata = getContractMetadata( |
||||
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
|
||||
compilationResultParam.data as unknown as CompilationResult, |
||||
contractName |
||||
) |
||||
|
||||
if (!contractMetadata) { |
||||
return { |
||||
succeed: false, |
||||
message: "Please recompile contract" |
||||
} |
||||
} |
||||
|
||||
const contractMetadataParsed = JSON.parse(contractMetadata) |
||||
|
||||
const fileName = getContractFileName( |
||||
// cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository
|
||||
compilationResultParam.data as unknown as CompilationResult, |
||||
contractName |
||||
) |
||||
|
||||
const jsonInput = { |
||||
language: 'Solidity', |
||||
sources: compilationResultParam.source.sources, |
||||
settings: { |
||||
optimizer: { |
||||
enabled: contractMetadataParsed.settings.optimizer.enabled, |
||||
runs: contractMetadataParsed.settings.optimizer.runs |
||||
} |
||||
} |
||||
} |
||||
|
||||
const data: { [key: string]: string | any } = { |
||||
apikey: apiKeyParam, // A valid API-Key is required
|
||||
module: "contract", // Do not change
|
||||
action: "verifysourcecode", // Do not change
|
||||
codeformat: "solidity-standard-json-input", |
||||
sourceCode: JSON.stringify(jsonInput), |
||||
contractname: fileName + ':' + contractName, |
||||
compilerversion: `v${contractMetadataParsed.compiler.version}`, // see http://etherscan.io/solcversions for list of support versions
|
||||
constructorArguements: contractArgumentsParam ? contractArgumentsParam.replace('0x', '') : '', // if applicable
|
||||
} |
||||
|
||||
if (isProxyContract) { |
||||
data.action = "verifyproxycontract" |
||||
data.expectedimplementation = expectedImplAddress |
||||
data.address = contractAddress |
||||
} else { |
||||
data.contractaddress = contractAddress |
||||
} |
||||
|
||||
const body = new FormData() |
||||
Object.keys(data).forEach((key) => body.append(key, data[key])) |
||||
|
||||
client.emit("statusChanged", { |
||||
key: "loading", |
||||
type: "info", |
||||
title: "Verifying ...", |
||||
}) |
||||
const response = await axios.post(etherscanApi, body) |
||||
const { message, result, status } = await response.data |
||||
|
||||
if (message === "OK" && status === "1") { |
||||
resetAfter10Seconds(client, setResults) |
||||
let receiptStatus |
||||
if (isProxyContract) { |
||||
receiptStatus = await getProxyContractReceiptStatus( |
||||
result, |
||||
apiKeyParam, |
||||
etherscanApi |
||||
) |
||||
if (receiptStatus.status === '1') { |
||||
receiptStatus.message = receiptStatus.result |
||||
receiptStatus.result = 'Successfully Updated' |
||||
} |
||||
} else receiptStatus = await getReceiptStatus( |
||||
result, |
||||
apiKeyParam, |
||||
etherscanApi |
||||
) |
||||
|
||||
const returnValue = { |
||||
guid: result, |
||||
status: receiptStatus.result, |
||||
message: `Verification request submitted successfully. Use this receipt GUID ${result} to track the status of your submission`, |
||||
succeed: true, |
||||
isProxyContract |
||||
} |
||||
onVerifiedContract(returnValue) |
||||
return returnValue |
||||
} else if (message === "NOTOK") { |
||||
client.emit("statusChanged", { |
||||
key: "failed", |
||||
type: "error", |
||||
title: result, |
||||
}) |
||||
const returnValue = { |
||||
message: result, |
||||
succeed: false, |
||||
isProxyContract |
||||
} |
||||
resetAfter10Seconds(client, setResults) |
||||
return returnValue |
||||
} |
||||
return { |
||||
message: 'unknown reason ' + result, |
||||
succeed: false |
||||
} |
||||
} catch (error: any) { |
||||
console.error(error) |
||||
setResults("Something wrong happened, try again") |
||||
return { |
||||
message: error.message, |
||||
succeed: false |
||||
} |
||||
} |
||||
} |
||||
|
||||
export const getContractFileName = ( |
||||
compilationResult: CompilationResult, |
||||
contractName: string |
||||
) => { |
||||
const compiledContracts = compilationResult.contracts |
||||
let fileName = "" |
||||
|
||||
for (const file of Object.keys(compiledContracts)) { |
||||
for (const contract of Object.keys(compiledContracts[file])) { |
||||
if (contract === contractName) { |
||||
fileName = file |
||||
break |
||||
} |
||||
} |
||||
} |
||||
return fileName |
||||
} |
||||
|
||||
export const getContractMetadata = ( |
||||
compilationResult: CompilationResult, |
||||
contractName: string |
||||
) => { |
||||
const compiledContracts = compilationResult.contracts |
||||
let contractMetadata = "" |
||||
|
||||
for (const file of Object.keys(compiledContracts)) { |
||||
for (const contract of Object.keys(compiledContracts[file])) { |
||||
if (contract === contractName) { |
||||
contractMetadata = compiledContracts[file][contract].metadata |
||||
if (contractMetadata) { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return contractMetadata |
||||
} |
@ -1,63 +0,0 @@ |
||||
import React, {useState, useEffect} from 'react' |
||||
|
||||
import {Formik, ErrorMessage, Field} from 'formik' |
||||
import {useNavigate, useLocation} from 'react-router-dom' |
||||
|
||||
import {AppContext} from '../AppContext' |
||||
import {SubmitButton} from '../components' |
||||
|
||||
export const CaptureKeyView = () => { |
||||
const location = useLocation() |
||||
const navigate = useNavigate() |
||||
const [msg, setMsg] = useState('') |
||||
const context = React.useContext(AppContext) |
||||
|
||||
useEffect(() => { |
||||
if (!context.apiKey) setMsg('Please provide a 34-character API key to continue') |
||||
}, [context.apiKey]) |
||||
|
||||
return ( |
||||
<div> |
||||
<Formik |
||||
initialValues={{apiKey: context.apiKey}} |
||||
validate={(values) => { |
||||
const errors = {} as any |
||||
if (!values.apiKey) { |
||||
errors.apiKey = 'Required' |
||||
} else if (values.apiKey.length !== 34) { |
||||
errors.apiKey = 'API key should be 34 characters long' |
||||
} |
||||
return errors |
||||
}} |
||||
onSubmit={(values) => { |
||||
const apiKey = values.apiKey |
||||
if (apiKey.length === 34) { |
||||
context.setAPIKey(values.apiKey) |
||||
navigate(location && location.state ? location.state : '/') |
||||
} |
||||
}} |
||||
> |
||||
{({errors, touched, handleSubmit}) => ( |
||||
<form onSubmit={handleSubmit}> |
||||
<div className="form-group mb-2"> |
||||
<label htmlFor="apikey">API Key</label> |
||||
<Field |
||||
className={errors.apiKey && touched.apiKey ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'} |
||||
type="password" |
||||
name="apiKey" |
||||
placeholder="e.g. GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ" |
||||
/> |
||||
<ErrorMessage className="invalid-feedback" name="apiKey" component="div" /> |
||||
</div> |
||||
|
||||
<div> |
||||
<SubmitButton text="Save" dataId="save-api-key" disable={errors && errors.apiKey ? true : false} /> |
||||
</div> |
||||
</form> |
||||
)} |
||||
</Formik> |
||||
|
||||
<div data-id="api-key-result" className="text-primary mt-4 text-center" style={{fontSize: '0.8em'}} dangerouslySetInnerHTML={{__html: msg}} /> |
||||
</div> |
||||
) |
||||
} |
@ -1,16 +0,0 @@ |
||||
import React from 'react' |
||||
|
||||
export const ErrorView = () => { |
||||
return ( |
||||
<div className="d-flex w-100 flex-column align-items-center"> |
||||
<img className="pb-4" width="250" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" alt="Error page" /> |
||||
<h5>Sorry, something unexpected happened.</h5> |
||||
<h5> |
||||
Please raise an issue:{' '} |
||||
<a className="text-danger" href="https://github.com/ethereum/remix-project/issues"> |
||||
Here |
||||
</a> |
||||
</h5> |
||||
</div> |
||||
) |
||||
} |
@ -1,31 +1,9 @@ |
||||
import React from 'react' |
||||
|
||||
import {Navigate} from 'react-router-dom' |
||||
|
||||
import {AppContext} from '../AppContext' |
||||
import {Receipt} from '../types' |
||||
|
||||
import {VerifyView} from './VerifyView' |
||||
|
||||
export const HomeView = () => { |
||||
const context = React.useContext(AppContext) |
||||
|
||||
return !context.apiKey ? ( |
||||
<Navigate |
||||
to={{ |
||||
pathname: '/settings' |
||||
}} |
||||
/> |
||||
) : ( |
||||
<VerifyView |
||||
contracts={context.contracts} |
||||
client={context.clientInstance} |
||||
apiKey={context.apiKey} |
||||
onVerifiedContract={(receipt: Receipt) => { |
||||
const newReceipts = [...context.receipts, receipt] |
||||
context.setReceipts(newReceipts) |
||||
}} |
||||
networkName={context.networkName} |
||||
/> |
||||
) |
||||
|
||||
return <div>HOME</div> |
||||
} |
||||
|
@ -1,170 +0,0 @@ |
||||
import React, {useState} from 'react' |
||||
|
||||
import {Formik, ErrorMessage, Field} from 'formik' |
||||
import {getEtherScanApi, getNetworkName, getReceiptStatus, getProxyContractReceiptStatus} from '../utils' |
||||
import {Receipt} from '../types' |
||||
import {AppContext} from '../AppContext' |
||||
import {SubmitButton} from '../components' |
||||
import {Navigate} from 'react-router-dom' |
||||
import {Button} from 'react-bootstrap' |
||||
import {CustomTooltip} from '@remix-ui/helper' |
||||
|
||||
interface FormValues { |
||||
receiptGuid: string |
||||
} |
||||
|
||||
export const ReceiptsView = () => { |
||||
const [results, setResults] = useState({succeed: false, message: ''}) |
||||
const [isProxyContractReceipt, setIsProxyContractReceipt] = useState(false) |
||||
const context = React.useContext(AppContext) |
||||
|
||||
const onGetReceiptStatus = async (values: FormValues, clientInstance: any, apiKey: string) => { |
||||
try { |
||||
const {network, networkId} = await getNetworkName(clientInstance) |
||||
if (network === 'vm') { |
||||
setResults({ |
||||
succeed: false, |
||||
message: 'Cannot verify in the selected network' |
||||
}) |
||||
return |
||||
} |
||||
const etherscanApi = getEtherScanApi(networkId) |
||||
let result |
||||
if (isProxyContractReceipt) { |
||||
result = await getProxyContractReceiptStatus(values.receiptGuid, apiKey, etherscanApi) |
||||
if (result.status === '1') { |
||||
result.message = result.result |
||||
result.result = 'Successfully Updated' |
||||
} |
||||
} else result = await getReceiptStatus(values.receiptGuid, apiKey, etherscanApi) |
||||
setResults({ |
||||
succeed: result.status === '1' ? true : false, |
||||
message: result.result || (result.status === '0' ? 'Verification failed' : result.message) |
||||
}) |
||||
} catch (error: any) { |
||||
setResults({ |
||||
succeed: false, |
||||
message: error.message |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return !context.apiKey ? ( |
||||
<Navigate |
||||
to={{ |
||||
pathname: '/settings' |
||||
}} |
||||
/> |
||||
) : ( |
||||
<div> |
||||
<Formik |
||||
initialValues={{receiptGuid: ''}} |
||||
validate={(values) => { |
||||
const errors = {} as any |
||||
if (!values.receiptGuid) { |
||||
errors.receiptGuid = 'Required' |
||||
} |
||||
return errors |
||||
}} |
||||
onSubmit={(values) => onGetReceiptStatus(values, context.clientInstance, context.apiKey)} |
||||
> |
||||
{({errors, touched, handleSubmit, handleChange}) => ( |
||||
<form onSubmit={handleSubmit}> |
||||
<div className="form-group mb-2"> |
||||
<label htmlFor="receiptGuid">Receipt GUID</label> |
||||
<Field |
||||
className={errors.receiptGuid && touched.receiptGuid ? 'form-control form-control-sm is-invalid' : 'form-control form-control-sm'} |
||||
type="text" |
||||
name="receiptGuid" |
||||
/> |
||||
<ErrorMessage className="invalid-feedback" name="receiptGuid" component="div" /> |
||||
</div> |
||||
|
||||
<div className="d-flex mb-2 custom-control custom-checkbox"> |
||||
<Field |
||||
className="custom-control-input" |
||||
type="checkbox" |
||||
name="isProxyReceipt" |
||||
id="isProxyReceipt" |
||||
onChange={async (e) => { |
||||
handleChange(e) |
||||
if (e.target.checked) setIsProxyContractReceipt(true) |
||||
else setIsProxyContractReceipt(false) |
||||
}} |
||||
/> |
||||
<label className="form-check-label custom-control-label" htmlFor="isProxyReceipt"> |
||||
It's a proxy contract GUID |
||||
</label> |
||||
</div> |
||||
<SubmitButton dataId={null} text="Check" disable={!touched.receiptGuid || (touched.receiptGuid && errors.receiptGuid) ? true : false} /> |
||||
</form> |
||||
)} |
||||
</Formik> |
||||
|
||||
<div |
||||
className={results['succeed'] ? 'text-success mt-3 text-center' : 'text-danger mt-3 text-center'} |
||||
dangerouslySetInnerHTML={{ |
||||
__html: results.message ? results.message : '' |
||||
}} |
||||
/> |
||||
|
||||
<ReceiptsTable receipts={context.receipts} /> |
||||
<br /> |
||||
<CustomTooltip tooltipText="Clear the list of receipts" tooltipId="etherscan-clear-receipts" placement="bottom"> |
||||
<Button |
||||
className="btn-sm" |
||||
onClick={() => { |
||||
context.setReceipts([]) |
||||
}} |
||||
> |
||||
Clear |
||||
</Button> |
||||
</CustomTooltip> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
const ReceiptsTable = ({receipts}) => { |
||||
return ( |
||||
<div className="table-responsive"> |
||||
<h6>Receipts</h6> |
||||
<table className="table h6 table-sm"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">Status</th> |
||||
<th scope="col">GUID</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{receipts && |
||||
receipts.length > 0 && |
||||
receipts.map((item: Receipt, index) => { |
||||
return ( |
||||
<tr key={item.guid}> |
||||
<td |
||||
className={ |
||||
item.status === 'Pass - Verified' || item.status === 'Successfully Updated' |
||||
? 'text-success' |
||||
: item.status === 'Pending in queue' |
||||
? 'text-warning' |
||||
: item.status === 'Already Verified' |
||||
? 'text-info' |
||||
: 'text-secondary' |
||||
} |
||||
> |
||||
{item.status} |
||||
{item.status === 'Successfully Updated' && ( |
||||
<CustomTooltip placement={'bottom'} tooltipClasses="text-wrap" tooltipId="etherscan-receipt-proxy-status" tooltipText={item.message}> |
||||
<i style={{fontSize: 'small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i> |
||||
</CustomTooltip> |
||||
)} |
||||
</td> |
||||
<td>{item.guid}</td> |
||||
</tr> |
||||
) |
||||
})} |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
) |
||||
} |
@ -1,235 +0,0 @@ |
||||
import React, {useEffect, useRef, useState} from 'react' |
||||
import Web3 from 'web3' |
||||
|
||||
import {PluginClient} from '@remixproject/plugin' |
||||
import {CustomTooltip} from '@remix-ui/helper' |
||||
import {Formik, ErrorMessage, Field} from 'formik' |
||||
|
||||
import {SubmitButton} from '../components' |
||||
import {Receipt} from '../types' |
||||
import {verify} from '../utils/verify' |
||||
import {etherscanScripts} from '@remix-project/remix-ws-templates' |
||||
|
||||
interface Props { |
||||
client: PluginClient |
||||
apiKey: string |
||||
onVerifiedContract: (receipt: Receipt) => void |
||||
contracts: string[], |
||||
networkName: string |
||||
} |
||||
|
||||
interface FormValues { |
||||
contractName: string |
||||
contractAddress: string |
||||
expectedImplAddress?: string |
||||
} |
||||
|
||||
export const VerifyView = ({apiKey, client, contracts, onVerifiedContract, networkName}) => { |
||||
const [results, setResults] = useState('') |
||||
const [selectedContract, setSelectedContract] = useState('') |
||||
const [showConstructorArgs, setShowConstructorArgs] = useState(false) |
||||
const [isProxyContract, setIsProxyContract] = useState(false) |
||||
const [constructorInputs, setConstructorInputs] = useState([]) |
||||
const verificationResult = useRef({}) |
||||
|
||||
useEffect(() => { |
||||
if (contracts.includes(selectedContract)) updateConsFields(selectedContract) |
||||
}, [contracts]) |
||||
|
||||
const updateConsFields = (contractName) => { |
||||
client.call('compilerArtefacts' as any, 'getArtefactsByContractName', contractName).then((result) => { |
||||
const {artefact} = result |
||||
if (artefact && artefact.abi && artefact.abi[0] && artefact.abi[0].type && artefact.abi[0].type === 'constructor' && artefact.abi[0].inputs.length > 0) { |
||||
setConstructorInputs(artefact.abi[0].inputs) |
||||
setShowConstructorArgs(true) |
||||
} else { |
||||
setConstructorInputs([]) |
||||
setShowConstructorArgs(false) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
const onVerifyContract = async (values: FormValues) => { |
||||
const compilationResult = (await client.call('solidity', 'getCompilationResult')) as any |
||||
|
||||
if (!compilationResult) { |
||||
throw new Error('no compilation result available') |
||||
} |
||||
|
||||
const constructorValues = [] |
||||
for (const key in values) { |
||||
if (key.startsWith('contractArgValue')) constructorValues.push(values[key]) |
||||
} |
||||
const web3 = new Web3() |
||||
const constructorTypes = constructorInputs.map((e) => e.type) |
||||
let contractArguments = web3.eth.abi.encodeParameters(constructorTypes, constructorValues) |
||||
contractArguments = contractArguments.replace('0x', '') |
||||
|
||||
verificationResult.current = await verify( |
||||
apiKey, |
||||
values.contractAddress, |
||||
contractArguments, |
||||
values.contractName, |
||||
compilationResult, |
||||
null, |
||||
isProxyContract, |
||||
values.expectedImplAddress, |
||||
client, |
||||
onVerifiedContract, |
||||
setResults |
||||
) |
||||
setResults(verificationResult.current['message']) |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
<Formik |
||||
initialValues={{ |
||||
contractName: '', |
||||
contractAddress: '' |
||||
}} |
||||
validate={(values) => { |
||||
const errors = {} as any |
||||
if (!values.contractName) { |
||||
errors.contractName = 'Required' |
||||
} |
||||
if (!values.contractAddress) { |
||||
errors.contractAddress = 'Required' |
||||
} |
||||
if (values.contractAddress.trim() === '' || !values.contractAddress.startsWith('0x') || values.contractAddress.length !== 42) { |
||||
errors.contractAddress = 'Please enter a valid contract address' |
||||
} |
||||
return errors |
||||
}} |
||||
onSubmit={(values) => onVerifyContract(values)} |
||||
> |
||||
{({errors, touched, handleSubmit, handleChange, isSubmitting}) => { |
||||
return ( |
||||
<form onSubmit={handleSubmit}> |
||||
<div className="form-group"> |
||||
<label htmlFor="network">Selected Network</label> |
||||
<CustomTooltip |
||||
tooltipText="Network is fetched from 'Deploy and Run Transactions' plugin's ENVIRONMENT field" |
||||
tooltipId="etherscan-impl-address2" |
||||
placement="bottom" |
||||
> |
||||
<Field className="form-control" type="text" name="network" value={networkName} disabled={true} /> |
||||
</CustomTooltip> |
||||
</div> |
||||
<div className="form-group"> |
||||
<label htmlFor="contractName">Contract Name</label> |
||||
<Field |
||||
as="select" |
||||
className={errors.contractName && touched.contractName && contracts.length ? 'form-control is-invalid' : 'form-control'} |
||||
name="contractName" |
||||
onChange={async (e) => { |
||||
handleChange(e) |
||||
setSelectedContract(e.target.value) |
||||
updateConsFields(e.target.value) |
||||
}} |
||||
> |
||||
<option disabled={true} value=""> |
||||
{contracts.length ? 'Select a contract' : `--- No compiled contracts ---`} |
||||
</option> |
||||
{contracts.map((item) => ( |
||||
<option key={item} value={item}> |
||||
{item} |
||||
</option> |
||||
))} |
||||
</Field> |
||||
<ErrorMessage className="invalid-feedback" name="contractName" component="div" /> |
||||
</div> |
||||
<div className={showConstructorArgs ? 'form-group d-block' : 'form-group d-none'}> |
||||
<label>Constructor Arguments</label> |
||||
{constructorInputs.map((item, index) => { |
||||
return ( |
||||
<div className="d-flex"> |
||||
<Field className="form-control m-1" type="text" key={`contractArgName${index}`} name={`contractArgName${index}`} value={item.name} disabled={true} /> |
||||
<CustomTooltip tooltipText={`value of ${item.name}`} tooltipId={`etherscan-constructor-value${index}`} placement="top"> |
||||
<Field className="form-control m-1" type="text" key={`contractArgValue${index}`} name={`contractArgValue${index}`} placeholder={item.type} /> |
||||
</CustomTooltip> |
||||
</div> |
||||
) |
||||
})} |
||||
</div> |
||||
<div className="form-group"> |
||||
<label htmlFor="contractAddress">Contract Address</label> |
||||
<Field |
||||
className={errors.contractAddress && touched.contractAddress ? 'form-control is-invalid' : 'form-control'} |
||||
type="text" |
||||
name="contractAddress" |
||||
placeholder="e.g. 0x11b79afc03baf25c631dd70169bb6a3160b2706e" |
||||
/> |
||||
<ErrorMessage className="invalid-feedback" name="contractAddress" component="div" /> |
||||
<div className="d-flex mb-2 custom-control custom-checkbox"> |
||||
<Field |
||||
className="custom-control-input" |
||||
type="checkbox" |
||||
name="isProxy" |
||||
id="isProxy" |
||||
onChange={async (e) => { |
||||
handleChange(e) |
||||
if (e.target.checked) setIsProxyContract(true) |
||||
else setIsProxyContract(false) |
||||
}} |
||||
/> |
||||
<label className="form-check-label custom-control-label" htmlFor="isProxy"> |
||||
It's a proxy contract address |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div className={isProxyContract ? 'form-group d-block' : 'form-group d-none'}> |
||||
<label htmlFor="expectedImplAddress">Expected Implementation Address</label> |
||||
<CustomTooltip |
||||
tooltipText="Providing expected implementation address enforces a check to ensure the returned implementation contract address is same as address picked up by the verifier" |
||||
tooltipId="etherscan-impl-address" |
||||
placement="bottom" |
||||
> |
||||
<Field className="form-control" type="text" name="expectedImplAddress" placeholder="verified implementation contract address" /> |
||||
</CustomTooltip> |
||||
<i style={{fontSize: 'x-small'}} className={'ml-1 fal fa-info-circle align-self-center'} aria-hidden="true"></i> |
||||
<label> Make sure contract is already verified on Etherscan</label> |
||||
</div> |
||||
<SubmitButton |
||||
dataId="verify-contract" |
||||
text="Verify" |
||||
isSubmitting={isSubmitting} |
||||
disable={ |
||||
!contracts.length || |
||||
!touched.contractName || |
||||
!touched.contractAddress || |
||||
(touched.contractName && errors.contractName) || |
||||
(touched.contractAddress && errors.contractAddress) || |
||||
networkName === 'VM (Not supported)' |
||||
? true |
||||
: false |
||||
} |
||||
/> |
||||
<br /> |
||||
<CustomTooltip tooltipText="Generate the required TS scripts to verify a contract on Etherscan" tooltipId="etherscan-generate-scripts" placement="bottom"> |
||||
<button |
||||
type="button" |
||||
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block" |
||||
onClick={async () => { |
||||
etherscanScripts(client) |
||||
}} |
||||
> |
||||
Generate Verification Scripts |
||||
</button> |
||||
</CustomTooltip> |
||||
</form> |
||||
) |
||||
}} |
||||
</Formik> |
||||
<div |
||||
data-id="verify-result" |
||||
className={verificationResult.current['succeed'] ? 'text-success mt-4 text-center' : 'text-danger mt-4 text-center'} |
||||
style={{fontSize: '0.8em'}} |
||||
dangerouslySetInnerHTML={{__html: results}} |
||||
/> |
||||
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}> |
||||
<Link to="/receipts">View Receipts</Link> |
||||
</div> */} |
||||
</div> |
||||
) |
||||
} |
@ -1,4 +1 @@ |
||||
export { HomeView } from "./HomeView" |
||||
export { ErrorView } from "./ErrorView" |
||||
export { ReceiptsView } from "./ReceiptsView" |
||||
export { CaptureKeyView } from "./CaptureKeyView" |
||||
export {HomeView} from './HomeView' |
||||
|
Loading…
Reference in new issue