Init contract-verification plugin and add it inside plugins

pull/5285/head
Kaan Uzdoğan 6 months ago committed by Aniket
parent 07a7a8ea39
commit 6ed602bb7c
  1. 9
      apps/contract-verification/.babelrc
  2. 16
      apps/contract-verification/.browserslistrc
  3. 3
      apps/contract-verification/.eslintrc
  4. 34
      apps/contract-verification/.eslintrc.json
  5. 69
      apps/contract-verification/project.json
  6. 7
      apps/contract-verification/src/app/App.css
  7. 25
      apps/contract-verification/src/app/AppContext.tsx
  8. 70
      apps/contract-verification/src/app/EtherscanPluginClient.ts
  9. 136
      apps/contract-verification/src/app/app.tsx
  10. 81
      apps/contract-verification/src/app/components/HeaderWithSettings.tsx
  11. 34
      apps/contract-verification/src/app/components/SubmitButton.tsx
  12. 2
      apps/contract-verification/src/app/components/index.ts
  13. 36
      apps/contract-verification/src/app/hooks/useLocalStorage.tsx
  14. 17
      apps/contract-verification/src/app/layouts/Default.tsx
  15. 1
      apps/contract-verification/src/app/layouts/index.ts
  16. 37
      apps/contract-verification/src/app/routes.tsx
  17. 9
      apps/contract-verification/src/app/types/Receipt.ts
  18. 1
      apps/contract-verification/src/app/types/ThemeType.ts
  19. 2
      apps/contract-verification/src/app/types/index.ts
  20. 1
      apps/contract-verification/src/app/utils/index.ts
  21. 42
      apps/contract-verification/src/app/utils/networks.ts
  22. 30
      apps/contract-verification/src/app/utils/scripts.ts
  23. 69
      apps/contract-verification/src/app/utils/utilities.ts
  24. 206
      apps/contract-verification/src/app/utils/verify.ts
  25. 63
      apps/contract-verification/src/app/views/CaptureKeyView.tsx
  26. 16
      apps/contract-verification/src/app/views/ErrorView.tsx
  27. 31
      apps/contract-verification/src/app/views/HomeView.tsx
  28. 170
      apps/contract-verification/src/app/views/ReceiptsView.tsx
  29. 235
      apps/contract-verification/src/app/views/VerifyView.tsx
  30. 4
      apps/contract-verification/src/app/views/index.ts
  31. 0
      apps/contract-verification/src/assets/.gitkeep
  32. 3
      apps/contract-verification/src/environments/environment.prod.ts
  33. 6
      apps/contract-verification/src/environments/environment.ts
  34. BIN
      apps/contract-verification/src/favicon.ico
  35. 17
      apps/contract-verification/src/index.html
  36. 14
      apps/contract-verification/src/main.tsx
  37. 7
      apps/contract-verification/src/polyfills.ts
  38. 16
      apps/contract-verification/src/profile.json
  39. 1
      apps/contract-verification/src/styles.css
  40. 22
      apps/contract-verification/tsconfig.app.json
  41. 16
      apps/contract-verification/tsconfig.json
  42. 90
      apps/contract-verification/webpack.config.js
  43. 2
      apps/remix-ide/project.json
  44. 1123
      apps/remix-ide/src/assets/list.json
  45. 25
      apps/remix-ide/src/remixAppManager.js

@ -0,0 +1,9 @@
{
"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/**"
]
}

@ -0,0 +1,16 @@
# 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'.

@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc.json",
}

@ -0,0 +1,34 @@
{
"extends": [
"plugin:@nrwl/nx/react",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

@ -0,0 +1,69 @@
{
"name": "contract-verification",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/contract-verification/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/contract-verification",
"index": "apps/contract-verification/src/index.html",
"baseHref": "./",
"main": "apps/contract-verification/src/main.tsx",
"polyfills": "apps/contract-verification/src/polyfills.ts",
"tsConfig": "apps/contract-verification/tsconfig.app.json",
"assets": [
"apps/contract-verification/src/favicon.ico",
"apps/contract-verification/src/assets",
"apps/contract-verification/src/profile.json"
],
"styles": ["apps/contract-verification/src/styles.css"],
"scripts": [],
"webpackConfig": "apps/contract-verification/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
"fileReplacements": [
{
"replace": "apps/contract-verification/src/environments/environment.ts",
"with": "apps/contract-verification/src/environments/environment.prod.ts"
}
]
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/contract-verification/**/*.ts"],
"eslintConfig": "apps/contract-verification/.eslintrc"
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "contract-verification:build",
"hmr": true,
"baseHref": "/"
},
"configurations": {
"development": {
"buildTarget": "contract-verification:build:development",
"port": 5003
},
"production": {
"buildTarget": "contract-verification:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,7 @@
body {
margin: 0;
}
#root {
padding: 8px 14px;
}

@ -0,0 +1,25 @@
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: ''
})

@ -0,0 +1,70 @@
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
}
}
}
}

@ -0,0 +1,136 @@
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

@ -0,0 +1,81 @@
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,34 @@
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>
)
}

@ -0,0 +1,2 @@
export { HeaderWithSettings } from "./HeaderWithSettings"
export { SubmitButton } from "./SubmitButton"

@ -0,0 +1,36 @@
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]
}

@ -0,0 +1,17 @@
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>
)
}

@ -0,0 +1 @@
export { DefaultLayout } from "./Default"

@ -0,0 +1,37 @@
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>
)

@ -0,0 +1,9 @@
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
}

@ -0,0 +1 @@
export type ThemeType = "dark" | "light"

@ -0,0 +1,2 @@
export * from "./Receipt"
export * from "./ThemeType"

@ -0,0 +1,42 @@
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',
}

@ -0,0 +1,30 @@
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)
}
`

@ -0,0 +1,69 @@
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)
}
}

@ -0,0 +1,206 @@
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
}

@ -0,0 +1,63 @@
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>
)
}

@ -0,0 +1,16 @@
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>
)
}

@ -0,0 +1,31 @@
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}
/>
)
}

@ -0,0 +1,170 @@
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>
)
}

@ -0,0 +1,235 @@
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> &nbsp;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>
)
}

@ -0,0 +1,4 @@
export { HomeView } from "./HomeView"
export { ErrorView } from "./ErrorView"
export { ReceiptsView } from "./ReceiptsView"
export { CaptureKeyView } from "./CaptureKeyView"

@ -0,0 +1,3 @@
export const environment = {
production: true
};

@ -0,0 +1,6 @@
// 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
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,17 @@
<!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>

@ -0,0 +1,14 @@
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 />
);
}

@ -0,0 +1,7 @@
/**
* 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';

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

@ -0,0 +1,22 @@
{
"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"]
}

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -0,0 +1,90 @@
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
})

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src", "sourceRoot": "apps/remix-ide/src",
"projectType": "application", "projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth", "quick-dapp", "remix-dapp"], "implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "contract-verification", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth", "quick-dapp", "remix-dapp"],
"targets": { "targets": {
"build": { "build": {
"executor": "@nrwl/webpack:webpack", "executor": "@nrwl/webpack:webpack",

File diff suppressed because it is too large Load Diff

@ -7,7 +7,8 @@ import {Registry} from '@remix-project/remix-lib'
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
// requiredModule removes the plugin from the plugin manager list on UI // requiredModule removes the plugin from the plugin manager list on UI
let requiredModules = [ // services + layout views + system views let requiredModules = [
// services + layout views + system views
'manager', 'manager',
'config', 'config',
'compilerArtefacts', 'compilerArtefacts',
@ -92,14 +93,14 @@ let requiredModules = [ // services + layout views + system views
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither'] const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
const loadLocalPlugins = ['doc-gen', 'doc-viewer', 'etherscan', 'vyper', 'solhint', 'walletconnect', 'circuit-compiler', 'learneth', 'quick-dapp'] const loadLocalPlugins = ['doc-gen', 'doc-viewer', 'etherscan', 'contract-verification', 'vyper', 'solhint', 'walletconnect', 'circuit-compiler', 'learneth', 'quick-dapp']
const partnerPlugins = ['cookbookdev'] const partnerPlugins = ['cookbookdev']
const sensitiveCalls = { const sensitiveCalls = {
fileManager: ['writeFile', 'copyFile', 'rename', 'copyDir'], fileManager: ['writeFile', 'copyFile', 'rename', 'copyDir'],
contentImport: ['resolveAndSave'], contentImport: ['resolveAndSave'],
web3Provider: ['sendAsync'] web3Provider: ['sendAsync'],
} }
const isInjectedProvider = (name) => { const isInjectedProvider = (name) => {
@ -316,7 +317,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 5 group: 5,
}) })
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
id: 'nahmii-compiler', id: 'nahmii-compiler',
@ -327,7 +328,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 6 group: 6,
}) })
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityumlgen', id: 'solidityumlgen',
@ -338,7 +339,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 7 group: 7,
}) })
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
id: 'doc-gen', id: 'doc-gen',
@ -349,7 +350,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 7 group: 7,
}) })
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
id: 'vyper', id: 'vyper',
@ -360,7 +361,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 7 group: 7,
}) })
if (Registry.getInstance().get('platform').api.isDesktop()) { if (Registry.getInstance().get('platform').api.isDesktop()) {
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
@ -372,7 +373,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 8 group: 8,
}) })
await this.call('filePanel', 'registerContextMenuItem', { await this.call('filePanel', 'registerContextMenuItem', {
id: 'fs', id: 'fs',
@ -383,7 +384,7 @@ export class RemixAppManager extends PluginManager {
path: [], path: [],
pattern: [], pattern: [],
sticky: true, sticky: true,
group: 8 group: 8,
}) })
} }
} }
@ -418,7 +419,7 @@ class PluginLoader {
}, },
get: () => { get: () => {
return JSON.parse(localStorage.getItem('workspace')) return JSON.parse(localStorage.getItem('workspace'))
} },
} }
this.loaders.queryParams = { this.loaders.queryParams = {
@ -429,7 +430,7 @@ class PluginLoader {
const {activate} = queryParams.get() const {activate} = queryParams.get()
if (!activate) return [] if (!activate) return []
return activate.split(',') return activate.split(',')
} },
} }
this.current = queryParams.get().activate ? 'queryParams' : 'localStorage' this.current = queryParams.get().activate ? 'queryParams' : 'localStorage'

Loading…
Cancel
Save