commit
f45d66a0db
@ -1,9 +0,0 @@ |
|||||||
{ |
|
||||||
"presets": ["@babel/preset-env", ["@babel/preset-react", |
|
||||||
{"runtime": "automatic"} |
|
||||||
]], |
|
||||||
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", "@babel/plugin-proposal-nullish-coalescing-operator"], |
|
||||||
"ignore": [ |
|
||||||
"**/node_modules/**" |
|
||||||
] |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
# This file is used by: |
|
||||||
# 1. autoprefixer to adjust CSS to support the below specified browsers |
|
||||||
# 2. babel preset-env to adjust included polyfills |
|
||||||
# |
|
||||||
# For additional information regarding the format and rule options, please see: |
|
||||||
# https://github.com/browserslist/browserslist#queries |
|
||||||
# |
|
||||||
# If you need to support different browsers in production, you may tweak the list below. |
|
||||||
|
|
||||||
last 1 Chrome version |
|
||||||
last 1 Firefox version |
|
||||||
last 2 Edge major versions |
|
||||||
last 2 Safari major version |
|
||||||
last 2 iOS major versions |
|
||||||
Firefox ESR |
|
||||||
not IE 9-11 # For IE 9-11 support, remove 'not'. |
|
@ -1,3 +0,0 @@ |
|||||||
{ |
|
||||||
"extends": "../../.eslintrc.json", |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
{ |
|
||||||
"extends": [ |
|
||||||
"plugin:@nrwl/nx/react", |
|
||||||
"../../.eslintrc.json" |
|
||||||
], |
|
||||||
"ignorePatterns": [ |
|
||||||
"!**/*" |
|
||||||
], |
|
||||||
"overrides": [ |
|
||||||
{ |
|
||||||
"files": [ |
|
||||||
"*.ts", |
|
||||||
"*.tsx", |
|
||||||
"*.js", |
|
||||||
"*.jsx" |
|
||||||
], |
|
||||||
"rules": {} |
|
||||||
}, |
|
||||||
{ |
|
||||||
"files": [ |
|
||||||
"*.ts", |
|
||||||
"*.tsx" |
|
||||||
], |
|
||||||
"rules": {} |
|
||||||
}, |
|
||||||
{ |
|
||||||
"files": [ |
|
||||||
"*.js", |
|
||||||
"*.jsx" |
|
||||||
], |
|
||||||
"rules": {} |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
@ -1,69 +0,0 @@ |
|||||||
{ |
|
||||||
"name": "etherscan", |
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json", |
|
||||||
"sourceRoot": "apps/etherscan/src", |
|
||||||
"projectType": "application", |
|
||||||
"targets": { |
|
||||||
"build": { |
|
||||||
"executor": "@nrwl/webpack:webpack", |
|
||||||
"outputs": ["{options.outputPath}"], |
|
||||||
"defaultConfiguration": "development", |
|
||||||
"options": { |
|
||||||
"compiler": "babel", |
|
||||||
"outputPath": "dist/apps/etherscan", |
|
||||||
"index": "apps/etherscan/src/index.html", |
|
||||||
"baseHref": "./", |
|
||||||
"main": "apps/etherscan/src/main.tsx", |
|
||||||
"polyfills": "apps/etherscan/src/polyfills.ts", |
|
||||||
"tsConfig": "apps/etherscan/tsconfig.app.json", |
|
||||||
"assets": [ |
|
||||||
"apps/etherscan/src/favicon.ico", |
|
||||||
"apps/etherscan/src/assets", |
|
||||||
"apps/etherscan/src/profile.json" |
|
||||||
], |
|
||||||
"styles": ["apps/etherscan/src/styles.css"], |
|
||||||
"scripts": [], |
|
||||||
"webpackConfig": "apps/etherscan/webpack.config.js" |
|
||||||
}, |
|
||||||
"configurations": { |
|
||||||
"development": { |
|
||||||
}, |
|
||||||
"production": { |
|
||||||
"fileReplacements": [ |
|
||||||
{ |
|
||||||
"replace": "apps/etherscan/src/environments/environment.ts", |
|
||||||
"with": "apps/etherscan/src/environments/environment.prod.ts" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
"lint": { |
|
||||||
"executor": "@nrwl/linter:eslint", |
|
||||||
"outputs": ["{options.outputFile}"], |
|
||||||
"options": { |
|
||||||
"lintFilePatterns": ["apps/etherscan/**/*.ts"], |
|
||||||
"eslintConfig": "apps/etherscan/.eslintrc" |
|
||||||
} |
|
||||||
}, |
|
||||||
"serve": { |
|
||||||
"executor": "@nrwl/webpack:dev-server", |
|
||||||
"defaultConfiguration": "development", |
|
||||||
"options": { |
|
||||||
"buildTarget": "etherscan:build", |
|
||||||
"hmr": true, |
|
||||||
"baseHref": "/" |
|
||||||
}, |
|
||||||
"configurations": { |
|
||||||
"development": { |
|
||||||
"buildTarget": "etherscan:build:development", |
|
||||||
"port": 5003 |
|
||||||
}, |
|
||||||
"production": { |
|
||||||
"buildTarget": "etherscan:build:production" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
"tags": [] |
|
||||||
} |
|
@ -1,7 +0,0 @@ |
|||||||
body { |
|
||||||
margin: 0; |
|
||||||
} |
|
||||||
|
|
||||||
#root { |
|
||||||
padding: 8px 14px; |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
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: '' |
|
||||||
}) |
|
@ -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 +0,0 @@ |
|||||||
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 './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> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
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> |
|
||||||
) |
|
||||||
} |
|
@ -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 +0,0 @@ |
|||||||
export { HeaderWithSettings } from "./HeaderWithSettings" |
|
||||||
export { SubmitButton } from "./SubmitButton" |
|
@ -1,36 +0,0 @@ |
|||||||
import {useState} from 'react' |
|
||||||
|
|
||||||
export function useLocalStorage(key: string, initialValue: any) { |
|
||||||
// State to store our value
|
|
||||||
// Pass initial state function to useState so logic is only executed once
|
|
||||||
const [storedValue, setStoredValue] = useState(() => { |
|
||||||
try { |
|
||||||
// Get from local storage by key
|
|
||||||
const item = window.localStorage.getItem(key) |
|
||||||
// Parse stored json or if none return initialValue
|
|
||||||
return item ? JSON.parse(item) : initialValue |
|
||||||
} catch (error) { |
|
||||||
// If error also return initialValue
|
|
||||||
console.error(error) |
|
||||||
return initialValue |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
// Return a wrapped version of useState's setter function that ...
|
|
||||||
// ... persists the new value to localStorage.
|
|
||||||
const setValue = (value: any) => { |
|
||||||
try { |
|
||||||
// Allow value to be a function so we have same API as useState
|
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value |
|
||||||
// Save state
|
|
||||||
setStoredValue(valueToStore) |
|
||||||
// Save to local storage
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(valueToStore)) |
|
||||||
} catch (error) { |
|
||||||
// A more advanced implementation would handle the error case
|
|
||||||
console.error(error) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return [storedValue, setValue] |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
import React, {PropsWithChildren} from 'react' |
|
||||||
|
|
||||||
import {HeaderWithSettings} from '../components' |
|
||||||
|
|
||||||
interface Props { |
|
||||||
from: string |
|
||||||
title?: string |
|
||||||
} |
|
||||||
|
|
||||||
export const DefaultLayout = ({children, from, title}) => { |
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<HeaderWithSettings from={from} title={title} /> |
|
||||||
{children} |
|
||||||
</div> |
|
||||||
) |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
export { DefaultLayout } from "./Default" |
|
@ -1,37 +0,0 @@ |
|||||||
import React from 'react' |
|
||||||
import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom' |
|
||||||
|
|
||||||
import {ErrorView, HomeView, ReceiptsView, CaptureKeyView} from './views' |
|
||||||
import {DefaultLayout} from './layouts' |
|
||||||
|
|
||||||
export const DisplayRoutes = () => ( |
|
||||||
<Router> |
|
||||||
<Routes> |
|
||||||
<Route |
|
||||||
path="/" |
|
||||||
element={ |
|
||||||
<DefaultLayout from="/" title="Verify Smart Contracts"> |
|
||||||
<HomeView /> |
|
||||||
</DefaultLayout> |
|
||||||
} |
|
||||||
/> |
|
||||||
<Route path="/error" element={<ErrorView />} /> |
|
||||||
<Route |
|
||||||
path="/receipts" |
|
||||||
element={ |
|
||||||
<DefaultLayout from="/receipts" title="Check Receipt GUID Status"> |
|
||||||
<ReceiptsView /> |
|
||||||
</DefaultLayout> |
|
||||||
} |
|
||||||
/> |
|
||||||
<Route |
|
||||||
path="/settings" |
|
||||||
element={ |
|
||||||
<DefaultLayout from="/settings" title="Set Explorer API Key"> |
|
||||||
<CaptureKeyView /> |
|
||||||
</DefaultLayout> |
|
||||||
} |
|
||||||
/> |
|
||||||
</Routes> |
|
||||||
</Router> |
|
||||||
) |
|
@ -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 +0,0 @@ |
|||||||
export type ThemeType = "dark" | "light" |
|
@ -1,2 +0,0 @@ |
|||||||
export * from "./Receipt" |
|
||||||
export * from "./ThemeType" |
|
@ -1 +0,0 @@ |
|||||||
export * from "./utilities" |
|
@ -1,46 +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', |
|
||||||
1116: 'https://openapi.coredao.org/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', |
|
||||||
80002: 'https://api-amoy.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', |
|
||||||
2442: 'https://api-cardona-zkevm.polygonscan.com/api', |
|
||||||
59140: 'https://api-testnet.lineascan.build/api', |
|
||||||
534351: 'https://api-sepolia.scrollscan.com/api', |
|
||||||
1115: 'https://api.test.btcs.network/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 "../utils" |
|
||||||
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 or 32 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 && values.apiKey.length !== 32) { |
|
||||||
errors.apiKey = 'API key should be 34 or 32 characters long' |
|
||||||
} |
|
||||||
return errors |
|
||||||
}} |
|
||||||
onSubmit={(values) => { |
|
||||||
const apiKey = values.apiKey |
|
||||||
if (apiKey.length === 34 || apiKey.length === 32) { |
|
||||||
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 +0,0 @@ |
|||||||
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} |
|
||||||
/> |
|
||||||
) |
|
||||||
} |
|
@ -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 +0,0 @@ |
|||||||
export { HomeView } from "./HomeView" |
|
||||||
export { ErrorView } from "./ErrorView" |
|
||||||
export { ReceiptsView } from "./ReceiptsView" |
|
||||||
export { CaptureKeyView } from "./CaptureKeyView" |
|
@ -1,3 +0,0 @@ |
|||||||
export const environment = { |
|
||||||
production: true |
|
||||||
}; |
|
@ -1,6 +0,0 @@ |
|||||||
// This file can be replaced during build by using the `fileReplacements` array.
|
|
||||||
// When building for production, this file is replaced with `environment.prod.ts`.
|
|
||||||
|
|
||||||
export const environment = { |
|
||||||
production: false |
|
||||||
}; |
|
Before Width: | Height: | Size: 15 KiB |
@ -1,17 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
<head> |
|
||||||
<meta charset="utf-8" /> |
|
||||||
<title>Etherscan</title> |
|
||||||
<base href="./" /> |
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" /> |
|
||||||
<link rel="stylesheet" integrity="ha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" |
|
||||||
crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<div id="root"></div> |
|
||||||
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script> |
|
||||||
</body> |
|
||||||
</html> |
|
@ -1,14 +0,0 @@ |
|||||||
import React from 'react' |
|
||||||
import * as ReactDOM from 'react-dom' |
|
||||||
import { createRoot } from 'react-dom/client'; |
|
||||||
import App from './app/app' |
|
||||||
|
|
||||||
|
|
||||||
const container = document.getElementById('root'); |
|
||||||
|
|
||||||
if (container) { |
|
||||||
createRoot(container).render( |
|
||||||
<App /> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
@ -1,7 +0,0 @@ |
|||||||
/** |
|
||||||
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. |
|
||||||
* |
|
||||||
* See: https://github.com/zloirock/core-js#babel
|
|
||||||
*/ |
|
||||||
import 'core-js/stable'; |
|
||||||
import 'regenerator-runtime/runtime'; |
|
@ -1,16 +0,0 @@ |
|||||||
{ |
|
||||||
"name": "etherscan", |
|
||||||
"displayName": "Contract verification - Etherscan", |
|
||||||
"description": "Verify Solidity contract code using Etherscan, BscScan, PolygonScan etc. APIs", |
|
||||||
"version": "0.1.0", |
|
||||||
"events": [], |
|
||||||
"methods": ["verify", "receiptStatus"], |
|
||||||
"kind": "none", |
|
||||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHsAAAB7CAYAAABUx/9/AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAHBklEQVR42u2dy3WbTBTH/+ikgFEFwZ8K8HQAm6yjDkIHdiogHWBXgDqADgQVaLLJToZ1NtAB38JC0QNJIPGYx73nsIgcHVvz033OnblWVVXQQcqyhBACQgiUZYk0TfHnz5/q79+/rd7PGPsA8I1z/gEAjuOAMQbOOVzX1WKNLFVhCyGQJAnSNEUcx0N/CMu2bXDO4TgOXNcF51y9RauqSomnKApEUQTG2BZANeXDGNsyxuB5HqIoUmYNpf8DoyjC1HBbPEqAl/KPyrJMCg2+V+N930eWZQT72rNer6Ea4FvavtlsCLbGkM+gu66L9XptNuwsy3SG3KjpU5r3ySJrgyCfQfd93wzYmpvs1sBt2x7dtJM2S2DatYJN2nxby8eI2of/BQSzNfQgCNSETWb7PuDL5VIt2IalVL0D55wPwmXW98bKfD7fPj096bFvOtHeVJ7n2/l8DiGEvLteKtazZa+z9xm4EWiDgBNog4ATaIOAE2iDgFMebVBadnfDoWVZlF5N1CTKOcdms+n8xhmBVi8PF0Lg58+fw+fZZErlMeldGxwpIFM8YOvS+UJarUF/W++waWGbNSuKoof6ynpom269NUrme0SfOYQydTHnpNUPwO6zU3QMc06gH1hgyTp6blqam0UVyqkvL02fJ2B7WGfLtu26caR7UYVAK9f0gLe3t+7fzvl8vi3L8j9aQ2U0u75QYLHbt2hfQSOfrJzPvnni5OK3k0y4epp9y3fPCLSevnu1WrX7dhJspTX74jbojEDruw162oo8o3XRV97f3y+bIkq3tDHjzWmYhukWPM976Oxzy50oFQ5AIgzD5to4DG/I6whACdiHBwVnhyZcB9uq5M2DAwljbJskyXmApouv/vr1K1E+YFqWJeI4pmjcFEnTlGCbInmeAwC+UCFFa5/9AaC+//UTNom2PntxWDIlM65x0ScMw6PshGBrCtr3fXied/QiwdYQtOd5+PXr19kPZhSc6QXadV2EYdj4wykCNItzDs555wLI+/s7bdTcWNfdbZIXRam7Om9sUPR64y/UqY232hMY3Wf/+PHjofcXRbEgJT7X6DaH80eHrcuMLNVAUzRuEGiCbRBogm0Q6Le3N6qNmwB6V0uxSLMVK5jcCZrMuEqg6ybKLnLaakawFQDt+/7FEug10KfVRvLZkkrd9x0EwdnuVRtpKisTbIlBr9fru7plL21uEWwJzXY9+umuN1/ZxSSfLRno19fXQUCTZkskVVVZcRxjuVwOApo0WzIZEjTB1sHud+g0mlVVZdGSaQ/a4pyTZpug0cDncFeCbQBoAHh+fibYJoAGPjuECLYikuf5Q2fyOOefeXZVVRb1j8srD951Y3HOwRgjzdYc9N6EA1RB09I/n4rjOJ95dv1CfZaXRB/QjLEPxti+MreHTc33+ml0WZaLwz598tl3sPA8z7p2Y/9UEXeTfP/+/d8/Rp7yM/S5qCHPesHzvF6HwAy59vVUoKIo9r/jKEArimJBKdixzwOweHl5aTzvLJvZPjXhnueBMbZ/jaLxKzJGHDOkcp0eojzz2bQLNo4kSTIkaItzfnaIkjRb4Wj7mry8vJy91hiNk3YPI0NE203fJdu2G9uPSbNHkjHvcr904cGXa5Eo3V+ijtk+yB7w+vraDbZqaVj9QW3bNlKb63QrCIKjdOvoSzfyLM5eRi3UR1Bt2wbnHI7jwHVdae4az/McT09PYyvKzVmcSvrsLMss2bR4Km0+lCAIrv+HsUcHDlFqlOHZHaedbFxGL/Oze67bagd5ZzYnm4tS18Db1OxbmfGiKBZxHO/nRDHG8Pz8fC1YOvKftm1LaXJVibJvBWW+77da315nU1HOPP73zXXd1jcy0H52B1mtVrAsq5IBdN2F0ulGBl0Dpj6f3YJKN8PscEBbq0CbYCoHuaqrZF0/D/lsuX1yo5++5y408tknVa/5fL6VxSdfq5J1vSKLfLb8proxn95sNvf31JkIuOUUXa1AGwd7VztWanJwX6CNgK2KmR4atJawsyxTUoOHBq0N7CiK6u6MSodnCNDKwtYNLhom+Qxx8kT65oUkSSCEwO/fv7FarXSvAFnL5RJhGF5sLXpEpICd5znyPIcQAmVZIk1TJEliWmnPCoLgYrOgUrCTJEGSJAD+TWo3EOjFqlgYhsOPwRrT12rqYx+eYHh40nLQ9TesV0sayHWNe1RlmyqiNhm07/ujabM0qdfUzXpTmOyhDvMrk2fv9ma1hVz3iU29ztSWO7Am910F066ClmUZfN9XdofK9/1JzbWy5dIoilSAjuVyiSiKpF5L5WriMjQd1BrseR6iKJoksr7nUbbhUAiBJEmQpiniOB7lNgMZT4x2+hA6dZfWmyZ1fV0I0bpLtL4Gq4boOM7+GFN9q6/q8j8QmqQtM04gOgAAAABJRU5ErkJggg==", |
|
||||||
"location": "sidePanel", |
|
||||||
"url": "https://ipfs-cluster.ethdevops.io/ipfs/QmQsZbBSYCVBVpz2mVRbPRVTrcz59oJEpuuoxiT9otu3mh", |
|
||||||
"repo": "https://github.com/ethereum/remix-project/tree/master/apps/etherscan", |
|
||||||
"documentation": "https://remix-ide.readthedocs.io/en/latest/contract_verification.html#etherscan", |
|
||||||
"maintainedBy": "Remix", |
|
||||||
"authorContact": "remix@ethereum.org" |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
/* You can add global styles to this file, and also import other style files */ |
|
@ -1,22 +0,0 @@ |
|||||||
{ |
|
||||||
"extends": "./tsconfig.json", |
|
||||||
"compilerOptions": { |
|
||||||
"outDir": "../../dist/out-tsc", |
|
||||||
"types": ["node"] |
|
||||||
}, |
|
||||||
"files": [ |
|
||||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
|
||||||
"../../node_modules/@nrwl/react/typings/image.d.ts" |
|
||||||
], |
|
||||||
"exclude": [ |
|
||||||
"**/*.spec.ts", |
|
||||||
"**/*.test.ts", |
|
||||||
"**/*.spec.tsx", |
|
||||||
"**/*.test.tsx", |
|
||||||
"**/*.spec.js", |
|
||||||
"**/*.test.js", |
|
||||||
"**/*.spec.jsx", |
|
||||||
"**/*.test.jsx" |
|
||||||
], |
|
||||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
{ |
|
||||||
"extends": "../../tsconfig.base.json", |
|
||||||
"compilerOptions": { |
|
||||||
"jsx": "react-jsx", |
|
||||||
"allowJs": true, |
|
||||||
"esModuleInterop": true, |
|
||||||
"allowSyntheticDefaultImports": true |
|
||||||
}, |
|
||||||
"files": [], |
|
||||||
"include": [], |
|
||||||
"references": [ |
|
||||||
{ |
|
||||||
"path": "./tsconfig.app.json" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
const {composePlugins, withNx} = require('@nrwl/webpack') |
|
||||||
const webpack = require('webpack') |
|
||||||
const TerserPlugin = require('terser-webpack-plugin') |
|
||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') |
|
||||||
|
|
||||||
const versionData = { |
|
||||||
timestamp: Date.now(), |
|
||||||
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development' |
|
||||||
} |
|
||||||
// Nx plugins for webpack.
|
|
||||||
module.exports = composePlugins(withNx(), (config) => { |
|
||||||
// Update the webpack config as needed here.
|
|
||||||
// e.g. `config.plugins.push(new MyPlugin())`
|
|
||||||
|
|
||||||
// add fallback for node modules
|
|
||||||
config.resolve.fallback = { |
|
||||||
...config.resolve.fallback, |
|
||||||
crypto: require.resolve('crypto-browserify'), |
|
||||||
stream: require.resolve('stream-browserify'), |
|
||||||
path: require.resolve('path-browserify'), |
|
||||||
http: require.resolve('stream-http'), |
|
||||||
https: require.resolve('https-browserify'), |
|
||||||
constants: require.resolve('constants-browserify'), |
|
||||||
os: false, //require.resolve("os-browserify/browser"),
|
|
||||||
timers: false, // require.resolve("timers-browserify"),
|
|
||||||
zlib: require.resolve('browserify-zlib'), |
|
||||||
fs: false, |
|
||||||
module: false, |
|
||||||
tls: false, |
|
||||||
net: false, |
|
||||||
readline: false, |
|
||||||
child_process: false, |
|
||||||
buffer: require.resolve('buffer/'), |
|
||||||
vm: require.resolve('vm-browserify') |
|
||||||
} |
|
||||||
|
|
||||||
// add externals
|
|
||||||
config.externals = { |
|
||||||
...config.externals, |
|
||||||
solc: 'solc' |
|
||||||
} |
|
||||||
|
|
||||||
// add public path
|
|
||||||
config.output.publicPath = '/' |
|
||||||
|
|
||||||
// set filename
|
|
||||||
config.output.filename = `[name].plugin-etherscan.${versionData.timestamp}.js` |
|
||||||
config.output.chunkFilename = `[name].plugin-etherscan.${versionData.timestamp}.js` |
|
||||||
|
|
||||||
// add copy & provide plugin
|
|
||||||
config.plugins.push( |
|
||||||
new webpack.ProvidePlugin({ |
|
||||||
Buffer: ['buffer', 'Buffer'], |
|
||||||
url: ['url', 'URL'], |
|
||||||
process: 'process/browser' |
|
||||||
}) |
|
||||||
) |
|
||||||
|
|
||||||
// souce-map loader
|
|
||||||
config.module.rules.push({ |
|
||||||
test: /\.js$/, |
|
||||||
use: ['source-map-loader'], |
|
||||||
enforce: 'pre' |
|
||||||
}) |
|
||||||
|
|
||||||
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
|
|
||||||
|
|
||||||
// set minimizer
|
|
||||||
config.optimization.minimizer = [ |
|
||||||
new TerserPlugin({ |
|
||||||
parallel: true, |
|
||||||
terserOptions: { |
|
||||||
ecma: 2015, |
|
||||||
compress: false, |
|
||||||
mangle: false, |
|
||||||
format: { |
|
||||||
comments: false |
|
||||||
} |
|
||||||
}, |
|
||||||
extractComments: false |
|
||||||
}), |
|
||||||
new CssMinimizerPlugin() |
|
||||||
] |
|
||||||
|
|
||||||
config.watchOptions = { |
|
||||||
ignored: /node_modules/ |
|
||||||
} |
|
||||||
|
|
||||||
return config |
|
||||||
}) |
|
File diff suppressed because one or more lines are too long
@ -1,17 +0,0 @@ |
|||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { InjectedCustomProvider } from './injected-custom-provider' |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'injected-arbitrum-one-provider', |
|
||||||
displayName: 'Injected Arbitrum One Provider', |
|
||||||
kind: 'provider', |
|
||||||
description: 'injected Arbitrum One Provider', |
|
||||||
methods: ['sendAsync', 'init'], |
|
||||||
version: packageJson.version |
|
||||||
} |
|
||||||
|
|
||||||
export class InjectedArbitrumOneProvider extends InjectedCustomProvider { |
|
||||||
constructor() { |
|
||||||
super(profile, 'Arbitrum One', '0xa4b1', ['https://arb1.arbitrum.io/rpc']) |
|
||||||
} |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { InjectedCustomProvider } from './injected-custom-provider' |
|
||||||
import { Web3 } from 'web3' |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'injected-ephemery-testnet-provider', |
|
||||||
displayName: 'Injected Ephemery Testnet Provider', |
|
||||||
kind: 'provider', |
|
||||||
description: 'Injected Ephemery Testnet Provider', |
|
||||||
methods: ['sendAsync', 'init'], |
|
||||||
version: packageJson.version |
|
||||||
} |
|
||||||
|
|
||||||
export class InjectedEphemeryTestnetProvider extends InjectedCustomProvider { |
|
||||||
constructor() { |
|
||||||
super(profile, |
|
||||||
'Ephemery Testnet', |
|
||||||
'', |
|
||||||
['https://otter.bordel.wtf/erigon', 'https://eth.ephemeral.zeus.fyi'], |
|
||||||
{ |
|
||||||
"name": "Ephemery ETH", |
|
||||||
"symbol": "ETH", |
|
||||||
"decimals": 18 |
|
||||||
}, |
|
||||||
[ |
|
||||||
'https://otter.bordel.wtf/', |
|
||||||
'https://explorer.ephemery.dev/' |
|
||||||
] |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
async init() { |
|
||||||
const chainId = await new Web3(this.rpcUrls[0]).eth.getChainId() |
|
||||||
this.chainId = `0x${chainId.toString(16)}` |
|
||||||
this.chainName = `Ephemery Testnet ${chainId}` |
|
||||||
await super.init() |
|
||||||
return {} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { InjectedCustomProvider } from './injected-custom-provider' |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'injected-optimism-provider', |
|
||||||
displayName: 'Injected Optimism Provider', |
|
||||||
kind: 'provider', |
|
||||||
description: 'injected Optimism Provider', |
|
||||||
methods: ['sendAsync', 'init'], |
|
||||||
version: packageJson.version |
|
||||||
} |
|
||||||
|
|
||||||
export class Injected0ptimismProvider extends InjectedCustomProvider { |
|
||||||
constructor() { |
|
||||||
super(profile, 'Optimism', '0xa', ['https://mainnet.optimism.io']) |
|
||||||
} |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
/* global ethereum */ |
|
||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { InjectedProvider } from './injected-provider' |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'injected-trustwallet', |
|
||||||
displayName: 'Trust wallet', |
|
||||||
kind: 'provider', |
|
||||||
description: 'Trust wallet', |
|
||||||
methods: ['sendAsync', 'init'], |
|
||||||
version: packageJson.version |
|
||||||
} |
|
||||||
|
|
||||||
export class InjectedProviderTrustWallet extends InjectedProvider { |
|
||||||
constructor() { |
|
||||||
super(profile) |
|
||||||
} |
|
||||||
|
|
||||||
getInjectedProvider() { |
|
||||||
return (window as any).trustwallet |
|
||||||
} |
|
||||||
|
|
||||||
notFound() { |
|
||||||
return 'Could not find Trust Wallet provider. Please make sure the Trust Wallet extension is active. Download the latest version from https://trustwallet.com/browser-extension' |
|
||||||
} |
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { InjectedCustomProvider } from './injected-custom-provider' |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'injected-skale-chaos-testnet-provider', |
|
||||||
displayName: 'Injected SKALE Chaos Testnet', |
|
||||||
kind: 'provider', |
|
||||||
description: 'Injected SKALE Chaos Testnet Provider', |
|
||||||
methods: ['sendAsync', 'init'], |
|
||||||
version: packageJson.version |
|
||||||
} |
|
||||||
|
|
||||||
export class InjectedSKALEChaosTestnetProvider extends InjectedCustomProvider { |
|
||||||
|
|
||||||
constructor () { |
|
||||||
super(profile, |
|
||||||
'SKALE Chaos Testnet', |
|
||||||
'0x50877ed6', |
|
||||||
['https://staging-v3.skalenodes.com/v1/staging-fast-active-bellatrix'], |
|
||||||
{ |
|
||||||
"name": "sFUEL", |
|
||||||
"symbol": "sFUEL", |
|
||||||
"decimals": 18 |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -1,13 +1,15 @@ |
|||||||
{ |
{ |
||||||
"version": "v0.54.0", |
"version": "v0.56.0", |
||||||
"title": "RELEASE HIGHLIGHTS", |
"title": "RELEASE HIGHLIGHTS", |
||||||
"highlight1": "Login to GitHub from 'File Explorer'", |
"highlight1": "Added new 'Contract Verification' plugin to verify contract on multiple platforms", |
||||||
"highlight2": "Added Cookbook workspace templates", |
"highlight2": "Added new 'Remix Guide' plugin to learn using Remix IDE using videos", |
||||||
"highlight3": "Added 'sendRawTransaction' API to remix-simulator", |
"highlight3": "Added support for message signing using EIP712", |
||||||
"highlight4": "", |
"highlight4": "", |
||||||
"more": "Read More", |
"more": "Read More", |
||||||
"moreLink": "https://medium.com/remix-ide/remix-release-v0-54-0-021575d54849?source=friends_link&sk=e3960ba90994b63993987b007ee3ac63" |
"moreLink": "https://medium.com/remix-ide/remix-release-v0-56-0-574a5774f94c?source=friends_link&sk=af46111d5a9976306e5770ccc468ae28" |
||||||
} |
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue