├── .circleci
└── config.yml
├── .coveralls.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── README.md
├── docs
└── index.md
├── mkdocs.yml
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── AppContext.tsx
├── components
│ ├── HeaderWithSettings.tsx
│ ├── SubmitButton.tsx
│ └── index.ts
├── hooks
│ └── useLocalStorage.tsx
├── index.tsx
├── layouts
│ ├── Default.tsx
│ └── index.ts
├── react-app-env.d.ts
├── routes.tsx
├── setupTests.ts
├── types
│ ├── Receipt.ts
│ ├── ThemeType.ts
│ └── index.ts
├── utils
│ ├── index.ts
│ └── utilities.ts
└── views
│ ├── CaptureKeyView.tsx
│ ├── ErrorView.tsx
│ ├── HomeView.tsx
│ ├── ReceiptsView.tsx
│ ├── VerifyView.tsx
│ └── index.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | defaults: &defaults
2 | working_directory: ~/remix-etherscan
3 | docker:
4 | - image: circleci/node:12.16
5 |
6 | aliases:
7 | - &restore_cache
8 | restore_cache:
9 | name: Restore Yarn Package Cache
10 | keys:
11 | - yarn-packages-v1-{{ checksum "yarn.lock" }}
12 | - yarn-packages-v1-
13 | - yarn-packages-v1-
14 |
15 | version: 2.0
16 |
17 | jobs:
18 | setup:
19 | <<: *defaults
20 | steps:
21 | - checkout
22 | - *restore_cache
23 | - run:
24 | name: Install Dependencies
25 | command: |
26 | echo 'Installing dependencies'
27 | yarn --frozen-lockfile
28 | - save_cache:
29 | name: Save Yarn Package Cache
30 | paths:
31 | - node_modules
32 | key: yarn-packages-v1-{{ checksum "yarn.lock" }}
33 |
34 | tslint-and-format:
35 | <<: *defaults
36 | steps:
37 | - checkout
38 | - *restore_cache
39 | - run:
40 | name: TSLint
41 | command: yarn tslint
42 | - run:
43 | name: Check formatting (Prettier)
44 | command: yarn check-formatting
45 |
46 | unit-tests:
47 | <<: *defaults
48 | steps:
49 | - checkout
50 | - *restore_cache
51 | - run:
52 | name: Run unit tests with JUnit as reporter
53 | command: yarn test:ci
54 | environment:
55 | JEST_JUNIT_OUTPUT: "reports/junit/js-test-results.xml"
56 | - store_test_results:
57 | path: reports/junit
58 | - store_artifacts:
59 | path: reports/junit
60 |
61 | build:
62 | <<: *defaults
63 | steps:
64 | - checkout
65 | - *restore_cache
66 | - run:
67 | name: Build
68 | command: yarn build
69 |
70 | deploy:
71 | <<: *defaults
72 | steps:
73 | - checkout
74 | - *restore_cache
75 | - run:
76 | name: Deploy
77 | command: yarn deploy
78 |
79 | workflows:
80 | version: 2
81 | build-deploy:
82 | jobs:
83 | - setup
84 | - tslint-and-format:
85 | requires:
86 | - setup
87 | - unit-tests:
88 | requires:
89 | - setup
90 | - build:
91 | requires:
92 | - tslint-and-format
93 | - unit-tests
94 | - deploy:
95 | context: SolidStudio
96 | requires:
97 | - build
98 | filters:
99 | branches:
100 | only: master
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: circleci
2 | repo_token: $COVERALLS_REPO_TOKEN
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | .poc
25 | TODO.md
26 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .cache
2 | package.json
3 | package-lock.json
4 | public
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": false,
4 | "singleQuote": false,
5 | "tabWidth": 2,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 🚨🚨🚨🚨🚨 **WARNING** 🚨🚨🚨🚨🚨
2 |
3 | This repository is not maintained anymore.
4 |
5 | The Remix Team has included this into the core Remix project.
6 |
7 | You can check it here. https://github.com/ethereum/remix-project/tree/master/apps/etherscan
8 |
9 | -----
10 |
11 | # Remix Etherscan Plugin
12 |
13 | A plugin to verify contracts in Etherscan
14 |
15 | [](https://circleci.com/gh/Machinalabs/remix-etherscan-plugin)
16 |
17 | Full documentation https://remix-etherscan-plugin.readthedocs.io/en/latest/
18 |
19 | ### Install
20 |
21 | ```
22 | $ git clone git@github.com:Machinalabs/remix-etherscan-plugin.git
23 |
24 | $ yarn
25 |
26 | ```
27 |
28 | ### Commands
29 |
30 | ```
31 | $ yarn start # start development project
32 |
33 | $ yarn deploy # deploy to surge
34 |
35 | $ yarn build # build production project
36 |
37 | ```
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Remix Etherscan plugin
2 |
3 | The Remix Etherscan plugin allow you to verify smart contracts in Etherscan.
4 |
5 | ## Activate
6 |
7 | To be able to work with the plugin go to the plugins section and find Etherscan, then activate it.
8 |
9 | ## How to verify smart contracts
10 |
11 | To verify smart contracts, the first thing you need to do is to compile smart contracts.
12 |
13 | Once the contracts are compiled, go to the Etherscan plugin and you can select the smart contract you wish to verify. Then, include parameters if required and just click verify.
14 |
15 | ## Issues
16 |
17 | If you have any issues, please feel free to create an issue in our [Github repository](https://github.com/Machinalabs/remix-etherscan-plugin/issues).
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Remix Etherscan
2 | nav:
3 | - Home: index.md
4 | theme: readthedocs
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "remix-etherscan",
3 | "version": "0.1.0",
4 | "description": "A plugin to verify contracts in Etherscan",
5 | "author": "MachinaLabs",
6 | "dependencies": {
7 | "@remixproject/plugin": "^0.3.19",
8 | "@remixproject/plugin-api": "^0.3.19",
9 | "@remixproject/plugin-utils": "^0.3.19",
10 | "@remixproject/plugin-webview": "^0.3.19",
11 | "@testing-library/jest-dom": "^5.14.1",
12 | "@testing-library/react": "^12.0.0",
13 | "@testing-library/user-event": "^13.1.9",
14 | "@types/jest": "^26.0.24",
15 | "@types/node": "^16.3.1",
16 | "@types/react": "^17.0.14",
17 | "@types/react-dom": "^17.0.9",
18 | "axios": "^0.21.1",
19 | "formik": "^2.2.9",
20 | "react": "^17.0.2",
21 | "react-dom": "^17.0.2",
22 | "react-router-dom": "^5.2.0",
23 | "react-scripts": "^4.0.3",
24 | "typescript": "^4.3.5"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "CI=true react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject",
31 | "test:ci": "CI=true react-scripts test",
32 | "tslint": "tslint -c tslint.json src/**/*.{ts,tsx} --fix --format verbose",
33 | "check-formatting": "prettier --write --list-different './src/**/*.tsx'",
34 | "deploy": "yarn build && surge --project ./build --domain machinalabs-remix-etherscan-plugin-v2.surge.sh"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "devDependencies": {
52 | "@types/react-router-dom": "^5.1.8",
53 | "coveralls": "^3.1.1",
54 | "husky": "^7.0.1",
55 | "prettier": "^2.3.2",
56 | "surge": "^0.23.0",
57 | "tslint": "^6.1.3",
58 | "tslint-config-prettier": "^1.18.0",
59 | "tslint-plugin-prettier": "^2.3.0",
60 | "tslint-react": "^5.0.0"
61 | },
62 | "husky": {
63 | "hooks": {
64 | "pre-push": "yarn tslint && yarn check-formatting && yarn build"
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Machinalabs/remix-etherscan-plugin/9745ac2bb6fe8e47fb95946a0f3e19c272d3d42f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Etherscan Verify Plugin
28 |
29 |
30 |
37 |
38 |
59 |
60 |
61 |
62 |
63 |
73 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Machinalabs/remix-etherscan-plugin/9745ac2bb6fe8e47fb95946a0f3e19c272d3d42f/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Machinalabs/remix-etherscan-plugin/9745ac2bb6fe8e47fb95946a0f3e19c272d3d42f/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { render } from "@testing-library/react"
3 | import App, { getNewContractNames } from "./App"
4 | import {
5 | CompilationResult,
6 | UserMethodList,
7 | BytecodeObject,
8 | UserDocumentation,
9 | DeveloperDocumentation,
10 | } from "@remixproject/plugin"
11 |
12 | test("getNewContracts", () => {
13 | const fakeCompilationResult: CompilationResult = {
14 | sources: {} as any,
15 | contracts: {
16 | "browser/SafeMath.sol": {
17 | SafeMath: {
18 | abi: [],
19 | metadata: "",
20 | devdoc: {} as DeveloperDocumentation,
21 | userdoc: {} as UserDocumentation,
22 | ir: "",
23 | evm: {} as any,
24 | ewasm: {} as any,
25 | },
26 | },
27 | },
28 | }
29 |
30 | const result = getNewContractNames(fakeCompilationResult)
31 |
32 | expect(result).toHaveLength(1)
33 | expect(result[0]).toEqual("SafeMath")
34 | })
35 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react"
2 |
3 | import {
4 | CompilationFileSources,
5 | CompilationResult,
6 | } from "@remixproject/plugin-api"
7 |
8 | import { PluginClient } from "@remixproject/plugin";
9 | import { createClient } from "@remixproject/plugin-webview";
10 |
11 | import { AppContext } from "./AppContext"
12 | import { Routes } from "./routes"
13 |
14 | import { useLocalStorage } from "./hooks/useLocalStorage"
15 |
16 | import { getReceiptStatus, getEtherScanApi, getNetworkName } from "./utils"
17 | import { Receipt, ThemeType } from "./types"
18 |
19 | import "./App.css"
20 |
21 | export const getNewContractNames = (compilationResult: CompilationResult) => {
22 | const compiledContracts = compilationResult.contracts
23 | let result: string[] = []
24 |
25 | for (const file of Object.keys(compiledContracts)) {
26 | const newContractNames = Object.keys(compiledContracts[file])
27 | result = [...result, ...newContractNames]
28 | }
29 |
30 | return result
31 | }
32 |
33 | const App = () => {
34 | const [apiKey, setAPIKey] = useLocalStorage("apiKey", "")
35 | const [clientInstance, setClientInstance] = useState(undefined as any)
36 | const [receipts, setReceipts] = useLocalStorage("receipts", [])
37 | const [contracts, setContracts] = useState([] as string[])
38 | const [themeType, setThemeType] = useState("dark" as ThemeType)
39 |
40 | const clientInstanceRef = useRef(clientInstance)
41 | clientInstanceRef.current = clientInstance
42 | const contractsRef = useRef(contracts)
43 | contractsRef.current = contracts
44 |
45 | useEffect(() => {
46 | console.log("Remix Etherscan loading...")
47 | const client = new PluginClient()
48 | createClient(client)
49 | const loadClient = async () => {
50 | await client.onload()
51 | setClientInstance(client)
52 | console.log("Remix Etherscan Plugin has been loaded")
53 |
54 | client.on("solidity",
55 | "compilationFinished",
56 | (
57 | fileName: string,
58 | source: CompilationFileSources,
59 | languageVersion: string,
60 | data: CompilationResult
61 | ) => {
62 | console.log("New compilation received")
63 | const newContractsNames = getNewContractNames(data)
64 |
65 | const newContractsToSave: string[] = [
66 | ...contractsRef.current,
67 | ...newContractsNames,
68 | ]
69 |
70 | const uniqueContracts: string[] = [...new Set(newContractsToSave)]
71 |
72 | setContracts(uniqueContracts)
73 | }
74 | )
75 |
76 | //const currentTheme = await client.call("theme", "currentTheme")
77 | //setThemeType(currentTheme.quality)
78 | //client.on("theme", "themeChanged", (theme) => {
79 | // setThemeType(theme.quality)
80 | //})
81 | }
82 |
83 | loadClient()
84 | }, [])
85 |
86 | useEffect(() => {
87 | if (!clientInstance) {
88 | return
89 | }
90 |
91 | const receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
92 | return item.status !== "Verified"
93 | })
94 |
95 | if (receiptsNotVerified.length > 0) {
96 | let timer1 = setInterval(() => {
97 | receiptsNotVerified.forEach(async (item) => {
98 | if (!clientInstanceRef.current) {
99 | return
100 | }
101 | const network = await getNetworkName(clientInstanceRef.current)
102 | if (network === "vm") {
103 | return
104 | }
105 | const status = await getReceiptStatus(
106 | item.guid,
107 | apiKey,
108 | getEtherScanApi(network)
109 | )
110 | if (status === "Pass - Verified") {
111 | const newReceipts = receipts.map((currentReceipt: Receipt) => {
112 | if (currentReceipt.guid === item.guid) {
113 | return {
114 | ...currentReceipt,
115 | status: "Verified",
116 | }
117 | }
118 | return currentReceipt
119 | })
120 |
121 | clearInterval(timer1)
122 |
123 | setReceipts(newReceipts)
124 |
125 | return () => {
126 | clearInterval(timer1)
127 | }
128 | }
129 | })
130 | }, 5000)
131 | }
132 | }, [receipts, clientInstance, apiKey, setReceipts])
133 |
134 | return (
135 |
148 |
149 |
150 | )
151 | }
152 |
153 | export default App
154 |
--------------------------------------------------------------------------------
/src/AppContext.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { PluginClient } from "@remixproject/plugin"
3 |
4 | import { Receipt, ThemeType } from "./types"
5 |
6 | export const AppContext = React.createContext({
7 | apiKey: "",
8 | setAPIKey: (value: string) => {
9 | console.log("Set API Key from Context")
10 | },
11 | clientInstance: {} as PluginClient,
12 | receipts: [] as Receipt[],
13 | setReceipts: (receipts: Receipt[]) => {
14 | console.log("Calling Set Receipts")
15 | },
16 | contracts: [] as string[],
17 | setContracts: (contracts: string[]) => {
18 | console.log("Calling Set Contract Names")
19 | },
20 | themeType: "dark" as ThemeType,
21 | setThemeType: (themeType: ThemeType) => {
22 | console.log("Calling Set Theme Type")
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/src/components/HeaderWithSettings.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import { NavLink } from "react-router-dom"
4 | import { AppContext } from "../AppContext"
5 | import { ThemeType } from "../types"
6 |
7 | interface Props {
8 | title?: string
9 | showBackButton?: boolean
10 | from: string
11 | }
12 |
13 | interface IconProps {
14 | from: string
15 | themeType: ThemeType
16 | }
17 |
18 | const HomeIcon: React.FC = ({ from, themeType }: IconProps) => {
19 | return (
20 |
32 |
47 |
48 | )
49 | }
50 |
51 | const SettingsIcon: React.FC = ({ from, themeType }: IconProps) => {
52 | return (
53 |
63 |
82 |
83 | )
84 | }
85 |
86 | const getStyleFilterIcon = (themeType: ThemeType) => {
87 | const invert = themeType === "dark" ? 1 : 0
88 | const brightness = themeType === "dark" ? "150" : "0" // should be >100 for icons with color
89 | return {
90 | filter: `invert(${invert}) grayscale(1) brightness(${brightness}%)`,
91 | }
92 | }
93 |
94 | const ReceiptsIcon: React.FC = ({ from, themeType }: IconProps) => {
95 | return (
96 |
107 |
126 |
127 | )
128 | }
129 | export const HeaderWithSettings: React.FC = ({
130 | title = "",
131 | showBackButton = false,
132 | from,
133 | }) => {
134 | return (
135 |
136 | {({ themeType }) => (
137 |
138 |
{title}
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | )}
148 |
149 | )
150 | }
151 |
--------------------------------------------------------------------------------
/src/components/SubmitButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | interface Props {
4 | text: string
5 | isSubmitting?: boolean
6 | }
7 |
8 | export const SubmitButton: React.FC = ({
9 | text,
10 | isSubmitting = false,
11 | }) => {
12 | return (
13 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { HeaderWithSettings } from "./HeaderWithSettings"
2 | export { SubmitButton } from "./SubmitButton"
3 |
--------------------------------------------------------------------------------
/src/hooks/useLocalStorage.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | export function useLocalStorage(key: string, initialValue: any) {
4 | // State to store our value
5 | // Pass initial state function to useState so logic is only executed once
6 | const [storedValue, setStoredValue] = useState(() => {
7 | try {
8 | // Get from local storage by key
9 | const item = window.localStorage.getItem(key)
10 | // Parse stored json or if none return initialValue
11 | return item ? JSON.parse(item) : initialValue
12 | } catch (error) {
13 | // If error also return initialValue
14 | console.log(error)
15 | return initialValue
16 | }
17 | })
18 |
19 | // Return a wrapped version of useState's setter function that ...
20 | // ... persists the new value to localStorage.
21 | const setValue = (value: any) => {
22 | try {
23 | // Allow value to be a function so we have same API as useState
24 | const valueToStore =
25 | value instanceof Function ? value(storedValue) : value
26 | // Save state
27 | setStoredValue(valueToStore)
28 | // Save to local storage
29 | window.localStorage.setItem(key, JSON.stringify(valueToStore))
30 | } catch (error) {
31 | // A more advanced implementation would handle the error case
32 | console.log(error)
33 | }
34 | }
35 |
36 | return [storedValue, setValue]
37 | }
38 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import App from "./App"
4 |
5 | ReactDOM.render(, document.getElementById("root"))
6 |
--------------------------------------------------------------------------------
/src/layouts/Default.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren } from "react"
2 |
3 | import { HeaderWithSettings } from "../components"
4 |
5 | interface Props {
6 | from: string
7 | }
8 |
9 | export const DefaultLayout: React.FC> = ({
10 | children,
11 | from,
12 | }) => {
13 | return (
14 |
15 |
16 | {children}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/layouts/index.ts:
--------------------------------------------------------------------------------
1 | export { DefaultLayout } from "./Default"
2 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {
3 | BrowserRouter as Router,
4 | Switch,
5 | Route,
6 | RouteProps,
7 | } from "react-router-dom"
8 |
9 | import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views"
10 | import { DefaultLayout } from "./layouts"
11 |
12 | interface Props extends RouteProps {
13 | component: any // TODO: new (props: any) => React.Component
14 | from: string
15 | }
16 |
17 | const RouteWithHeader = ({ component: Component, ...rest }: Props) => {
18 | return (
19 | (
22 |
23 |
24 |
25 | )}
26 | />
27 | )
28 | }
29 |
30 | export const Routes = () => (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
43 |
49 |
50 |
51 | )
52 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/types/Receipt.ts:
--------------------------------------------------------------------------------
1 | export type ReceiptStatus = "Verified" | "Queue"
2 |
3 | export interface Receipt {
4 | guid: string
5 | status: ReceiptStatus
6 | }
7 |
--------------------------------------------------------------------------------
/src/types/ThemeType.ts:
--------------------------------------------------------------------------------
1 | export type ThemeType = "dark" | "light"
2 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Receipt"
2 | export * from "./ThemeType"
3 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./utilities"
2 |
--------------------------------------------------------------------------------
/src/utils/utilities.ts:
--------------------------------------------------------------------------------
1 | import { PluginClient } from "@remixproject/plugin"
2 | import axios from 'axios'
3 | type RemixClient = PluginClient
4 |
5 | export const getEtherScanApi = (network: string) => {
6 | return network === "main"
7 | ? `https://api.etherscan.io/api`
8 | : `https://api-${network}.etherscan.io/api`
9 | }
10 |
11 | export const getNetworkName = async (client: RemixClient) => {
12 | const network = await client.call("network", "detectNetwork")
13 | if (!network) {
14 | throw new Error("no known network to verify against")
15 | }
16 | const name = network.name!.toLowerCase()
17 | // TODO : remove that when https://github.com/ethereum/remix-ide/issues/2017 is fixe
18 | return name === "görli" ? "goerli" : name
19 | }
20 |
21 | export const getReceiptStatus = async (
22 | receiptGuid: string,
23 | apiKey: string,
24 | etherscanApi: string
25 | ) => {
26 | const params = `guid=${receiptGuid}&module=contract&action=checkverifystatus&apiKey=${apiKey}`
27 | try {
28 | const response = await axios.get(`${etherscanApi}?${params}`)
29 | const { result } = response.data
30 | return result
31 | } catch (error) {
32 | console.log("Error", error)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/views/CaptureKeyView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import { Formik, ErrorMessage, Field } from "formik"
4 | import { useHistory, useLocation } from "react-router-dom"
5 |
6 | import { AppContext } from "../AppContext"
7 | import { SubmitButton } from "../components"
8 |
9 | export const CaptureKeyView: React.FC = () => {
10 | const location = useLocation()
11 | const history = useHistory()
12 | return (
13 |
14 | {({ apiKey, setAPIKey }) => (
15 | {
18 | const errors = {} as any
19 | if (!values.apiKey) {
20 | errors.apiKey = "Required"
21 | }
22 | return errors
23 | }}
24 | onSubmit={(values) => {
25 | setAPIKey(values.apiKey)
26 | history.push((location.state as any).from)
27 | }}
28 | >
29 | {({ errors, touched, handleSubmit }) => (
30 |
54 | )}
55 |
56 | )}
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/src/views/ErrorView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export const ErrorView: React.FC = () => {
4 | return (
5 |
13 |

19 |
Sorry, something unexpected happened.
20 |
21 | Please raise an issue:{" "}
22 |
26 | Here
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/views/HomeView.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import { Redirect } from "react-router-dom"
4 |
5 | import { AppContext } from "../AppContext"
6 | import { Receipt } from "../types"
7 |
8 | import { VerifyView } from "./VerifyView"
9 |
10 | export const HomeView: React.FC = () => {
11 | // const [hasError, setHasError] = useState(false)
12 | return (
13 |
14 | {({ apiKey, clientInstance, setReceipts, receipts, contracts }) =>
15 | !apiKey ? (
16 |
22 | ) : (
23 | {
28 | const newReceipts = [...receipts, receipt]
29 |
30 | setReceipts(newReceipts)
31 | }}
32 | />
33 | )
34 | }
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/views/ReceiptsView.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 |
3 | import { Formik, ErrorMessage, Field } from "formik"
4 | import { getEtherScanApi, getNetworkName, getReceiptStatus } from "../utils"
5 | import { Receipt } from "../types"
6 | import { AppContext } from "../AppContext"
7 | import { SubmitButton } from "../components"
8 | import { Redirect } from "react-router-dom"
9 |
10 | interface FormValues {
11 | receiptGuid: string
12 | }
13 |
14 | export const ReceiptsView: React.FC = () => {
15 | const [results, setResults] = useState("")
16 | const onGetReceiptStatus = async (
17 | values: FormValues,
18 | clientInstance: any,
19 | apiKey: string
20 | ) => {
21 | try {
22 | const network = await getNetworkName(clientInstance)
23 | if (network === "vm") {
24 | setResults("Cannot verify in the selected network")
25 | return
26 | }
27 | const etherscanApi = getEtherScanApi(network)
28 | const result = await getReceiptStatus(
29 | values.receiptGuid,
30 | apiKey,
31 | etherscanApi
32 | )
33 | setResults(result)
34 | } catch (error) {
35 | setResults(error.message)
36 | }
37 | }
38 |
39 | return (
40 |
41 | {({ apiKey, clientInstance, receipts }) =>
42 | !apiKey ? (
43 |
49 | ) : (
50 |
51 |
{
54 | const errors = {} as any
55 | if (!values.receiptGuid) {
56 | errors.receiptGuid = "Required"
57 | }
58 | return errors
59 | }}
60 | onSubmit={(values) =>
61 | onGetReceiptStatus(values, clientInstance, apiKey)
62 | }
63 | >
64 | {({ errors, touched, handleSubmit }) => (
65 |
90 | )}
91 |
92 |
93 |
101 |
102 |
103 |
104 | )
105 | }
106 |
107 | )
108 | }
109 |
110 | const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
111 | return (
112 |
113 |
Receipts
114 |
115 |
116 |
117 | Guid |
118 | Status |
119 |
120 |
121 |
122 | {receipts &&
123 | receipts.length > 0 &&
124 | receipts.map((item: Receipt, index) => {
125 | return (
126 |
127 | {item.guid} |
128 | {item.status} |
129 |
130 | )
131 | })}
132 |
133 |
134 |
135 | )
136 | }
137 |
--------------------------------------------------------------------------------
/src/views/VerifyView.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 |
3 | import {
4 | PluginClient,
5 | } from "@remixproject/plugin"
6 | import { Formik, ErrorMessage, Field } from "formik"
7 |
8 | import { getNetworkName, getEtherScanApi, getReceiptStatus } from "../utils"
9 | import { SubmitButton } from "../components"
10 | import { Receipt } from "../types"
11 | import { CompilationResult } from "@remixproject/plugin-api"
12 | import axios from 'axios'
13 |
14 | interface Props {
15 | client: PluginClient
16 | apiKey: string
17 | onVerifiedContract: (receipt: Receipt) => void
18 | contracts: string[]
19 | }
20 |
21 | interface FormValues {
22 | contractName: string
23 | contractArguments: string
24 | contractAddress: string
25 | }
26 |
27 | export const getContractFileName = (
28 | compilationResult: CompilationResult,
29 | contractName: string
30 | ) => {
31 | const compiledContracts = compilationResult.contracts
32 | let fileName = ""
33 |
34 | for (const file of Object.keys(compiledContracts)) {
35 | for (const contract of Object.keys(compiledContracts[file])) {
36 | if (contract === contractName) {
37 | fileName = file
38 | break
39 | }
40 | }
41 | }
42 | return fileName
43 | }
44 |
45 | export const getContractMetadata = (
46 | compilationResult: CompilationResult,
47 | contractName: string
48 | ) => {
49 | const compiledContracts = compilationResult.contracts
50 | let contractMetadata = ""
51 |
52 | for (const file of Object.keys(compiledContracts)) {
53 | for (const contract of Object.keys(compiledContracts[file])) {
54 | if (contract === contractName) {
55 | contractMetadata = compiledContracts[file][contract].metadata
56 | if (contractMetadata) {
57 | break
58 | }
59 | }
60 | }
61 | }
62 | return contractMetadata
63 | }
64 |
65 | export const VerifyView: React.FC = ({
66 | apiKey,
67 | client,
68 | contracts,
69 | onVerifiedContract,
70 | }) => {
71 | const [results, setResults] = useState("")
72 |
73 | const onVerifyContract = async (values: FormValues) => {
74 | const compilationResult = (await client.call(
75 | "solidity",
76 | "getCompilationResult"
77 | )) as any
78 |
79 | if (!compilationResult) {
80 | throw new Error("no compilation result available")
81 | }
82 |
83 | const contractArguments = values.contractArguments.replace("0x", "")
84 |
85 | const verify = async (
86 | apiKeyParam: string,
87 | contractAddress: string,
88 | contractArgumentsParam: string,
89 | contractName: string,
90 | compilationResultParam: any
91 | ) => {
92 | const network = await getNetworkName(client)
93 | if (network === "vm") {
94 | return "Cannot verify in the selected network"
95 | }
96 | const etherscanApi = getEtherScanApi(network)
97 |
98 | try {
99 | const contractMetadata = getContractMetadata(
100 | compilationResultParam.data,
101 | contractName
102 | )
103 |
104 | if (!contractMetadata) {
105 | return "Please recompile contract"
106 | }
107 |
108 | const contractMetadataParsed = JSON.parse(contractMetadata)
109 |
110 | const fileName = getContractFileName(
111 | compilationResultParam.data,
112 | contractName
113 | )
114 |
115 | const jsonInput = {
116 | language: 'Solidity',
117 | sources: compilationResultParam.source.sources,
118 | settings: {
119 | optimizer: {
120 | enabled: contractMetadataParsed.settings.optimizer.enabled,
121 | runs: contractMetadataParsed.settings.optimizer.runs
122 | }
123 | }
124 | }
125 |
126 | const data: { [key: string]: string | any } = {
127 | apikey: apiKeyParam, // A valid API-Key is required
128 | module: "contract", // Do not change
129 | action: "verifysourcecode", // Do not change
130 | codeformat: "solidity-standard-json-input",
131 | contractaddress: contractAddress, // Contract Address starts with 0x...
132 | sourceCode: JSON.stringify(jsonInput),
133 | contractname: fileName + ':' + contractName,
134 | compilerversion: `v${contractMetadataParsed.compiler.version}`, // see http://etherscan.io/solcversions for list of support versions
135 | constructorArguements: contractArgumentsParam, // if applicable
136 | }
137 |
138 | const body = new FormData()
139 | Object.keys(data).forEach((key) => body.append(key, data[key]))
140 |
141 | client.emit("statusChanged", {
142 | key: "loading",
143 | type: "info",
144 | title: "Verifying ...",
145 | })
146 | const response = await axios.post(etherscanApi, body)
147 | const { message, result, status } = await response.data
148 |
149 | if (message === "OK" && status === "1") {
150 | resetAfter10Seconds()
151 | const receiptStatus = await getReceiptStatus(
152 | result,
153 | apiKey,
154 | etherscanApi
155 | )
156 |
157 | onVerifiedContract({
158 | guid: result,
159 | status: receiptStatus,
160 | })
161 | return `Contract verified correctly
Receipt GUID ${result}`
162 | }
163 | if (message === "NOTOK") {
164 | client.emit("statusChanged", {
165 | key: "failed",
166 | type: "error",
167 | title: result,
168 | })
169 | resetAfter10Seconds()
170 | }
171 | return result
172 | } catch (error) {
173 | console.log("Error, something wrong happened", error)
174 | setResults("Something wrong happened, try again")
175 | }
176 | }
177 |
178 | const resetAfter10Seconds = () => {
179 | setTimeout(() => {
180 | client.emit("statusChanged", { key: "none" })
181 | setResults("")
182 | }, 10000)
183 | }
184 |
185 | const verificationResult = await verify(
186 | apiKey,
187 | values.contractAddress,
188 | contractArguments,
189 | values.contractName,
190 | compilationResult
191 | )
192 |
193 | setResults(verificationResult)
194 | }
195 |
196 | return (
197 |
198 |
{
205 | const errors = {} as any
206 | if (!values.contractName) {
207 | errors.contractName = "Required"
208 | }
209 | if (!values.contractAddress) {
210 | errors.contractAddress = "Required"
211 | }
212 | if (values.contractAddress.trim() === "") {
213 | errors.contractAddress = "Please enter a valid contract address"
214 | }
215 | return errors
216 | }}
217 | onSubmit={(values) => onVerifyContract(values)}
218 | >
219 | {({ errors, touched, handleSubmit, isSubmitting }) => (
220 |
289 | )}
290 |
291 |
292 |
296 |
297 | {/*
298 | View Receipts
299 |
*/}
300 |
301 | )
302 | }
303 |
--------------------------------------------------------------------------------
/src/views/index.ts:
--------------------------------------------------------------------------------
1 | export { HomeView } from "./HomeView"
2 | export { ErrorView } from "./ErrorView"
3 | export { ReceiptsView } from "./ReceiptsView"
4 | export { CaptureKeyView } from "./CaptureKeyView"
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react-jsx",
21 | "downlevelIteration": true,
22 | "noFallthroughCasesInSwitch": true
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:recommended",
4 | "tslint-react",
5 | "tslint-config-prettier"
6 | ],
7 | "rulesDirectory": [
8 | "tslint-plugin-prettier"
9 | ],
10 | "rules": {
11 | "prettier": true,
12 | "interface-name": false,
13 | "no-console": false,
14 | "jsx-no-lambda": false
15 | }
16 | }
--------------------------------------------------------------------------------