├── .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 | [![CircleCI](https://circleci.com/gh/Machinalabs/remix-etherscan-plugin.svg?style=svg)](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 | 41 | 42 | 46 | 47 | 48 | ) 49 | } 50 | 51 | const SettingsIcon: React.FC = ({ from, themeType }: IconProps) => { 52 | return ( 53 | 63 | 72 | 76 | 77 | 81 | 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 | 116 | 120 | 121 | 125 | 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 |
31 |
32 | 33 | 43 | 48 |
49 | 50 |
51 | 52 |
53 |
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 | Error page 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 |
66 |
70 |
Get your Receipt GUID status
71 | 72 | 81 | 86 |
87 | 88 | 89 | 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 | 118 | 119 | 120 | 121 | 122 | {receipts && 123 | receipts.length > 0 && 124 | receipts.map((item: Receipt, index) => { 125 | return ( 126 | 127 | 128 | 129 | 130 | ) 131 | })} 132 | 133 |
GuidStatus
{item.guid}{item.status}
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 |
221 |
222 |
Verify your smart contracts
223 | 224 | 233 | 236 | {contracts.map((item) => ( 237 | 240 | ))} 241 | 242 | 247 |
248 | 249 |
250 | 251 | 261 | 266 |
267 | 268 |
269 | 270 | 280 | 285 |
286 | 287 | 288 | 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 | } --------------------------------------------------------------------------------