├── .env ├── .env-template ├── .github └── workflows │ └── release.yaml ├── .gitignore ├── .nvmrc ├── .vscode ├── launch.json └── settings.json ├── README.md ├── craco.config.js ├── package-lock.json ├── package.json ├── public ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo.svg ├── logoText.svg ├── manifest.json ├── robots.txt ├── solidity.png ├── sourcify-eth-card.png ├── sourcify_blue_rounded.png └── sourcify_blue_rounded_transparent.png ├── src ├── App.css ├── App.js ├── App.test.js ├── Explainer.jsx ├── assets │ ├── IPFS_logo.png │ ├── blockscout.png │ ├── dice.png │ ├── etherscan.webp │ ├── fonts │ │ ├── Roboto-Black.ttf │ │ ├── Roboto-BlackItalic.ttf │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-LightItalic.ttf │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-MediumItalic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.ttf │ │ └── Roboto-ThinItalic.ttf │ ├── icons │ │ └── index.jsx │ ├── metadata.js │ ├── nonrandomContracts.js │ └── sourcify.png ├── components │ ├── BlockExplorerButton.jsx │ ├── CollapsibleCode.jsx │ ├── Connecting.jsx │ ├── CustomSelectSearch │ │ ├── CustomSelectSearch.jsx │ │ └── style.css │ ├── Dice │ │ ├── Dice.jsx │ │ └── dice.css │ ├── Header.jsx │ ├── IPFSButton.jsx │ ├── MetadataAndSources.jsx │ ├── Modal.jsx │ ├── RandomContract │ │ ├── defaultContracts.json │ │ └── index.jsx │ ├── RandomContractInfo.jsx │ ├── Sources.jsx │ └── SourcifyButton.jsx ├── index.css ├── index.js ├── logo.svg └── setupTests.js └── tailwind.config.js /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_INFURA_KEY=65850d7a85284d15b5e09e37060fb44f 2 | -------------------------------------------------------------------------------- /.env-template: -------------------------------------------------------------------------------- 1 | REACT_APP_INFURA_KEY= -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release on Google Bucket 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | packages: write 12 | id-token: write 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | - name: "auth" 17 | uses: "google-github-actions/auth@v2" 18 | with: 19 | workload_identity_provider: "projects/1019539084286/locations/global/workloadIdentityPools/github/providers/sourcify" 20 | service_account: "sourcify-playground-cd@sourcify-project.iam.gserviceaccount.com" 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: "16" 25 | - name: Install dependencies 26 | run: npm install 27 | env: 28 | CI: false 29 | - name: Build 30 | run: npm run build 31 | env: 32 | CI: false 33 | - name: "upload-files" 34 | uses: "google-github-actions/upload-cloud-storage@v2" 35 | with: 36 | path: "build" 37 | destination: "sourcify-playground-bucket" 38 | parent: false 39 | glob: "**/*" -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity Metadata Playground 2 | 3 | 🕹️ https://playground.sourcify.dev 4 | 5 | This tool allows you to see the [Solidity metadata](https://docs.soliditylang.org/en/latest/metadata.html) hash and other information [encoded at the end](https://docs.soliditylang.org/en/latest/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode) of the smart contract bytecode. Works with any EVM chain listed in [ethereum-lists/chains](https://github.com/ethereum-lists/chains) i.e. https://chainid.network/chains.json. 6 | 7 | ## Usage 8 | 9 | ### With Query Parameters 10 | 11 | Either with `chainId` and `address` 12 | 13 | [`https://playground.sourcify.dev/?address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&chainId=1`](https://playground.sourcify.dev/?address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&chainId=1) 14 | 15 | Or directly with `bytecode` 16 | 17 | [`https://playground.sourcify.dev/?bytecode=0x7f7050c9e0f4ca769c69bd3a8ef7...`](https://playground.sourcify.dev/?bytecode=0x7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35556fe43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e2066726f6d207468652070726f78792061646d696e43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f787920746f20746865207a65726f206164647265737343616e6e6f742073657420612070726f787920696d706c656d656e746174696f6e20746f2061206e6f6e2d636f6e74726163742061646472657373a2646970667358221220119e941d353783c92238fbc4e38a3a0327e471d10cff47c0a5066819d4a4195664736f6c634300060c0033) 18 | 19 | ### Through the UI 20 | 21 | - Choose network and input the contract address to see its deployed bytecode. Some example contracts provided. 22 | - In the popup you will see the contract's deployed bytecode. It will be automatically scrolled to the end where [CBOR encoded data](https://docs.soliditylang.org/en/latest/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode) is appended. 23 | - The CBOR decoding will be highlighted with the sections indicating its length and its contents. 24 | - The CBOR encoded data will be decoded. It will differ for each contract but usually this will contain the Solidity version and the IPFS or Swarm hash. 25 | - It will also try to fetch and display the metadata file from its IPFS hash if available. 26 | 27 | ## Run 28 | 29 | Clone the repository and install dependencies 30 | 31 | ``` 32 | npm install 33 | ``` 34 | 35 | Ethereum Mainnet and Ethereum testnets' connections are provided throught Infura. Copy the `.env-template` file to a file named `.env` and add the Infura key. If left empty, it will use the default [ethers.js Infura key](https://github.com/ethers-io/ethers.js/blob/0d40156fcba5be155aa5def71bcdb95b9c11d889/packages/providers/src.ts/infura-provider.ts#L17) 36 | 37 | ``` 38 | REACT_APP_INFURA_KEY= 39 | ``` 40 | 41 | Run the deployment server with 42 | 43 | ``` 44 | npm start 45 | ``` 46 | 47 | ## Build 48 | 49 | Build the project with 50 | 51 | ``` 52 | npm run build 53 | ``` 54 | 55 | ## Docker 56 | 57 | To serve the project in a Docker container simply use 58 | 59 | ``` 60 | docker-compose up 61 | ``` 62 | 63 | It will run at http://localhost:2346 as given in the `docker-compose.yaml` file. 64 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | postcss: { 4 | plugins: [require("tailwindcss"), require("autoprefixer")], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metadata.sourcify.dev", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^6.4.0", 7 | "@ethereum-sourcify/contract-call-decoder": "^0.1.1", 8 | "@headlessui/react": "^1.4.1", 9 | "@testing-library/jest-dom": "^5.11.4", 10 | "@testing-library/react": "^11.1.0", 11 | "@testing-library/user-event": "^12.1.10", 12 | "ethers": "^5.5.1", 13 | "fuse.js": "^3.6.1", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "react-select-search": "^3.0.8", 18 | "react-syntax-highlighter": "^15.4.5", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "craco start", 23 | "build": "craco build", 24 | "test": "craco test", 25 | "eject": "react-scripts eject", 26 | "publish": "git checkout main && git pull && docker-compose up --build -d" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "autoprefixer": "^9.8.8", 48 | "postcss": "^7.0.39", 49 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 19 | 20 | 24 | 25 | 34 | playground.sourcify.dev 35 | 36 | 54 | 55 | 56 | 57 | 58 |
59 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/logoText.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/solidity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/solidity.png -------------------------------------------------------------------------------- /public/sourcify-eth-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/sourcify-eth-card.png -------------------------------------------------------------------------------- /public/sourcify_blue_rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/sourcify_blue_rounded.png -------------------------------------------------------------------------------- /public/sourcify_blue_rounded_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcifyeth/metadata-playground/daa424f5d547e446623da4c466ca6cfe468988bb/public/sourcify_blue_rounded_transparent.png -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App-logo { 2 | height: 40vmin; 3 | pointer-events: none; 4 | } 5 | 6 | @media (prefers-reduced-motion: no-preference) { 7 | .App-logo { 8 | animation: App-logo-spin infinite 20s linear; 9 | } 10 | } 11 | 12 | .App-link { 13 | color: #1e3877; 14 | } 15 | 16 | @keyframes App-logo-spin { 17 | from { 18 | transform: rotate(0deg); 19 | } 20 | to { 21 | transform: rotate(360deg); 22 | } 23 | } 24 | 25 | @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=VT323&display=swap"); 26 | 27 | .App { 28 | font-family: "IBM Plex Sans", sans-serif; 29 | } 30 | 31 | .vt323 { 32 | font-family: "VT323", monospace; 33 | } 34 | 35 | .loader { 36 | border-top-color: #2b50aa !important; 37 | -webkit-animation: spinner 1s linear infinite; 38 | animation: spinner 1s linear infinite; 39 | } 40 | 41 | @-webkit-keyframes spinner { 42 | 0% { 43 | -webkit-transform: rotate(0deg); 44 | } 45 | 100% { 46 | -webkit-transform: rotate(360deg); 47 | } 48 | } 49 | 50 | @keyframes spinner { 51 | 0% { 52 | transform: rotate(0deg); 53 | } 54 | 100% { 55 | transform: rotate(360deg); 56 | } 57 | } 58 | 59 | .arrows { 60 | width: 60px; 61 | height: 72px; 62 | position: absolute; 63 | left: 50%; 64 | margin-left: -30px; 65 | bottom: 20px; 66 | } 67 | 68 | .arrows path { 69 | stroke: #1e3877; 70 | fill: transparent; 71 | stroke-width: 1px; 72 | animation: arrow 2s infinite; 73 | -webkit-animation: arrow 2s infinite; 74 | } 75 | 76 | @keyframes arrow { 77 | 0% { 78 | opacity: 0; 79 | } 80 | 40% { 81 | opacity: 1; 82 | } 83 | 80% { 84 | opacity: 0; 85 | } 86 | 100% { 87 | opacity: 0; 88 | } 89 | } 90 | 91 | @-webkit-keyframes arrow /*Safari and Chrome*/ { 92 | 0% { 93 | opacity: 0; 94 | } 95 | 40% { 96 | opacity: 1; 97 | } 98 | 80% { 99 | opacity: 0; 100 | } 101 | 100% { 102 | opacity: 0; 103 | } 104 | } 105 | 106 | .arrows path.a1 { 107 | animation-delay: -1s; 108 | -webkit-animation-delay: -1s; /* Safari 和 Chrome */ 109 | } 110 | 111 | .arrows path.a2 { 112 | animation-delay: -0.5s; 113 | -webkit-animation-delay: -0.5s; /* Safari 和 Chrome */ 114 | } 115 | 116 | .arrows path.a3 { 117 | animation-delay: 0s; 118 | -webkit-animation-delay: 0s; /* Safari 和 Chrome */ 119 | } 120 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import ContractCallDecoder from "@ethereum-sourcify/contract-call-decoder"; 2 | import { ethers } from "ethers"; 3 | import { useCallback, useEffect, useState } from "react"; 4 | import "./App.css"; 5 | import nonrandomContracts from "./assets/nonrandomContracts"; 6 | import Connecting from "./components/Connecting"; 7 | import CustomSelectSearch from "./components/CustomSelectSearch/CustomSelectSearch"; 8 | import Header from "./components/Header"; 9 | import Modal from "./components/Modal"; 10 | // import RandomContract from "./components/RandomContract"; 11 | // import RandomContractInfo from "./components/RandomContractInfo"; 12 | import Explainer from "./Explainer"; 13 | 14 | const ScrollArrow = () => { 15 | const [scrollHidden, setScrollHidden] = useState(false); 16 | const handleScroll = () => { 17 | if (window.pageYOffset > 200) { 18 | setScrollHidden(true); 19 | document.removeEventListener("scroll", handleScroll); 20 | } 21 | }; 22 | document.addEventListener("scroll", handleScroll); 23 | 24 | return ( 25 |
26 | 31 |
32 | ); 33 | }; 34 | function App() { 35 | const [byteCode, setByteCode] = useState(); 36 | const [decodedCbor, setDecodedCbor] = useState(); 37 | const [metadataHash, setMetadataHash] = useState(); 38 | const [provider, setProvider] = useState(); 39 | const [address, setAddress] = useState(""); 40 | const [errorMessage, setErrorMessage] = useState(); 41 | const [chainIndex, setChainIndex] = useState(0); 42 | const [chainObject, setChainObject] = useState(); 43 | const [sourcifyChains, setSourcifyChains] = useState(); 44 | const [isModalOpen, setModalOpen] = useState(false); 45 | const [customByteCode, setCustomByteCode] = useState(); 46 | const [chainArray, setChainArray] = useState(); // chainId.network/chains.json result 47 | const [connected, setConnected] = useState("not connected"); 48 | 49 | const INFURA_URLS = { 50 | 1: "https://mainnet.infura.io/v3/", 51 | 3: "https://ropsten.infura.io/v3/", 52 | 4: "https://rinkeby.infura.io/v3/", 53 | 5: "https://goerli.infura.io/v3/", 54 | 11155111: "https://sepolia.infura.io/v3/", 55 | 42: "https://kovan.infura.io/v3/", 56 | 42161: "https://arbitrum-mainnet.infura.io/v3/", 57 | 11297108099: "https://palm-testnet.infura.io/v3/", 58 | 11297108109: "https://palm-mainnet.infura.io/v3/", 59 | }; 60 | 61 | // On Mount 62 | useEffect(() => { 63 | const queryString = window.location.search; 64 | const urlParams = new URLSearchParams(queryString); 65 | const paramAddress = urlParams.get("address"); 66 | const paramChainId = parseInt(urlParams.get("chainId")); 67 | const paramBytecode = urlParams.get("bytecode"); 68 | fetch("https://chainid.network/chains.json") 69 | .then((res) => res.json()) 70 | .then((arr) => { 71 | const ethereumChainIds = [1, 5, 11155111]; 72 | // move ethereum networks to the top 73 | const sortedArr = arr.sort((a, b) => { 74 | if (ethereumChainIds.includes(a.chainId) && ethereumChainIds.includes(b.chainId)) { 75 | return a.chainId - b.chainId; 76 | } else if (ethereumChainIds.includes(a.chainId)) { 77 | return -1; 78 | } else if (ethereumChainIds.includes(b.chainId)) { 79 | return 1; 80 | } else { 81 | return a.chainId - b.chainId; 82 | } 83 | }); 84 | setChainArray(sortedArr); 85 | setChainObject(sortedArr[0]); 86 | setChainIndex(0); 87 | if (paramAddress) { 88 | setAddress(paramAddress); 89 | } 90 | if (paramChainId) { 91 | const chainIndex = sortedArr.findIndex((chainObj) => chainObj.chainId === paramChainId); 92 | setChainIndex(chainIndex); 93 | } 94 | if (paramBytecode) { 95 | setCustomByteCode(paramBytecode); 96 | } 97 | }) 98 | .catch(() => alert("Couldn't fetch networks")); 99 | 100 | fetch("https://sourcify.dev/server/chains") 101 | .then((res) => res.json()) 102 | .then((arr) => { 103 | setSourcifyChains(arr); 104 | }); 105 | }, []); 106 | 107 | // On chainIndex change 108 | useEffect(() => { 109 | if (!chainArray) return; 110 | setErrorMessage(); 111 | setConnected("connecting"); 112 | const chainlistObject = chainArray[chainIndex]; 113 | 114 | // Decide provider URL 115 | let provider; 116 | // INFURA with Ethereum networks, Arbitrum, Palm-testnet, Palm 117 | if ([1, 3, 4, 5, 11155111, 42, 42161, 11297108099, 11297108109].includes(chainlistObject.chainId)) { 118 | provider = new ethers.providers.JsonRpcProvider( 119 | INFURA_URLS[chainlistObject.chainId] + process.env.REACT_APP_INFURA_KEY, 120 | { 121 | name: chainlistObject.name, 122 | chainId: chainlistObject.chainId, 123 | ensAddress: chainlistObject?.ens?.registry, 124 | } 125 | ); 126 | } else { 127 | try { 128 | provider = new ethers.providers.JsonRpcProvider(chainlistObject.rpc[0]); 129 | } catch (err) { 130 | console.error(err); 131 | } 132 | } 133 | setProvider(provider); 134 | provider 135 | .getNetwork() 136 | .then((data) => { 137 | setConnected("connected"); 138 | }) 139 | .catch((err) => { 140 | chainlistObject.rpc[0] 141 | ? setErrorMessage("Can't connect to the network at " + chainlistObject.rpc[0]) 142 | : setErrorMessage("Can't connect to the network: No RPC found"); 143 | 144 | setConnected("not connected"); 145 | }); 146 | }, [chainIndex, chainArray]); 147 | 148 | const chainIdToIndex = (id) => { 149 | const chainIndex = chainArray.findIndex((chainObj) => chainObj.chainId === id); 150 | return chainIndex; 151 | }; 152 | 153 | const handleAddressChange = (e) => { 154 | e.preventDefault(); 155 | setErrorMessage(); 156 | setAddress(e.target.value); 157 | }; 158 | 159 | const handleCustomBytecodeChange = (e) => { 160 | e.preventDefault(); 161 | setErrorMessage(); 162 | const input = e.target.value; 163 | const formattedBytecode = input.startsWith("0x") ? input.trim() : "0x" + input.trim(); 164 | setCustomByteCode(formattedBytecode); 165 | }; 166 | 167 | const decodeBytecodeCbor = (byteCode) => { 168 | let decodedCbor; 169 | const formattedBytecode = byteCode.startsWith("0x") ? byteCode.trim() : "0x" + byteCode.trim(); 170 | try { 171 | decodedCbor = ContractCallDecoder.decodeCborAtTheEnd(formattedBytecode); 172 | } catch (err) { 173 | console.error(err); 174 | if (err.message.startsWith("Unexpected data")) { 175 | setErrorMessage("Unable to decode bytecode with CBOR"); 176 | } else { 177 | setErrorMessage("Unable to decode bytecode with CBOR\n" + err.message); 178 | } 179 | return null; 180 | } 181 | if (decodedCbor) { 182 | setModalOpen(true); 183 | const stringifyBuffers = {}; // show buffers in hex 184 | Object.keys(decodedCbor).forEach((key) => { 185 | stringifyBuffers[key] = 186 | typeof decodedCbor[key] === "boolean" ? decodedCbor[key] : "0x" + decodedCbor[key].toString("hex"); 187 | }); 188 | setDecodedCbor(stringifyBuffers); 189 | try { 190 | const metadataHash = ContractCallDecoder.getHashFromDecodedCbor(decodedCbor); 191 | setMetadataHash(metadataHash); 192 | } catch (err) { 193 | setMetadataHash(null); 194 | } 195 | } else { 196 | setDecodedCbor(null); 197 | setMetadataHash(null); 198 | } 199 | }; 200 | 201 | const handleSubmitAddress = useCallback( 202 | async (e) => { 203 | if (e) e.preventDefault(); 204 | setErrorMessage(); 205 | let code; 206 | try { 207 | code = await provider.getCode(address); 208 | } catch (err) { 209 | if (err) { 210 | return setErrorMessage(err.message); 211 | } 212 | } 213 | if (code === "0x") { 214 | return setErrorMessage("No contract found at the address"); 215 | } 216 | setByteCode(code); 217 | decodeBytecodeCbor(code); 218 | }, 219 | [address, provider] 220 | ); 221 | 222 | const handleDecodeCustomByteCode = useCallback( 223 | (e) => { 224 | if (e) e.preventDefault(); 225 | console.log("Decoding custom"); 226 | setAddress(""); 227 | setByteCode(customByteCode); 228 | decodeBytecodeCbor(customByteCode); 229 | }, 230 | [customByteCode] 231 | ); 232 | 233 | const handleModalClose = () => { 234 | setModalOpen(false); 235 | setMetadataHash(null); 236 | setDecodedCbor(null); 237 | setByteCode(null); 238 | }; 239 | 240 | // On provider change, if address and chainId query params are present, handleSubmitAddress 241 | useEffect(() => { 242 | if (!provider) return; 243 | const queryString = window.location.search; 244 | const urlParams = new URLSearchParams(queryString); 245 | const paramAddress = urlParams.get("address"); 246 | const paramChainId = parseInt(urlParams.get("chainId")); 247 | if (paramAddress && paramChainId) { 248 | handleSubmitAddress(); 249 | } 250 | }, [provider, handleSubmitAddress]); 251 | 252 | // Submit automatically if bytecode is present in query params 253 | useEffect(() => { 254 | if (!customByteCode) return; 255 | const queryString = window.location.search; 256 | const urlParams = new URLSearchParams(queryString); 257 | const paramBytecode = urlParams.get("bytecode"); 258 | if (paramBytecode) { 259 | handleDecodeCustomByteCode(); 260 | } 261 | }, [customByteCode, handleDecodeCustomByteCode]); 262 | 263 | if (!chainArray) { 264 | return "Loading"; 265 | } 266 | return ( 267 |
268 |
269 | 270 |
271 | 281 |
282 | Solidity logo 283 | 284 |

Solidity metadata.json playground

285 |
286 | {chainArray ? ( 287 |
288 | 289 | ({ 291 | name: `#${chain.chainId} ${chain.name}`, 292 | value: i, 293 | }))} 294 | value={chainIndex} 295 | onChange={(index) => setChainIndex(index)} 296 | /> 297 |
298 | Network list from{" "} 299 | 305 | ethereum-lists/chains 306 | 307 |
308 | {/*
309 | Random contracts from{" "} 310 | 315 | ethereum-lists/contracts 316 | 317 |
*/} 318 |
319 | ) : ( 320 |
Loading chain list
321 | )} 322 |
323 |
{errorMessage}
324 |
325 |
326 |
327 |
328 | {/* */} 332 |
333 |
Enter Contract Address or ENS
334 | {/* */} 337 |
338 | 339 |
340 | 349 |
350 |
351 | 358 |
359 |
360 | {" "} 361 |
Click to decode some example contracts:
362 |
363 | {/*
    */} 364 | {nonrandomContracts.map((contract, i) => ( 365 | 377 | ))} 378 | {/*
*/} 379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
or paste contract bytecode
388 | 389 |
390 |