├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .gitpod.yml ├── .husky └── post-checkout ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── docker ├── README.md └── setup.sh ├── package.json ├── packages ├── hardhat │ ├── .eslintrc.js │ ├── contracts │ │ └── YourContract.sol │ ├── deploy │ │ └── 00_deploy_your_contract.js │ ├── example.env │ ├── hardhat.config.js │ ├── package.json │ ├── scripts │ │ ├── deploy.js │ │ ├── publish.js │ │ └── watch.js │ └── test │ │ └── myTest.js ├── react-app │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .prettierrc │ ├── .sample.env │ ├── gulpfile.js │ ├── package.json │ ├── public │ │ ├── dark-theme.css │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── light-theme.css │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── robots.txt │ │ └── scaffold-eth.png │ ├── scripts │ │ ├── create_contracts.js │ │ ├── ipfs.js │ │ ├── s3.js │ │ └── watch.js │ └── src │ │ ├── App.css │ │ ├── App.jsx │ │ ├── App.test.js │ │ ├── components │ │ ├── Account.jsx │ │ ├── Address.jsx │ │ ├── AddressInput.jsx │ │ ├── Balance.jsx │ │ ├── Blockie.jsx │ │ ├── BytesStringInput.jsx │ │ ├── Contract │ │ │ ├── DisplayVariable.jsx │ │ │ ├── FunctionForm.jsx │ │ │ ├── index.jsx │ │ │ └── utils.js │ │ ├── EtherInput.jsx │ │ ├── Events.jsx │ │ ├── Faucet.jsx │ │ ├── GasGauge.jsx │ │ ├── Header.jsx │ │ ├── L2Bridge.jsx │ │ ├── Provider.jsx │ │ ├── Ramp.jsx │ │ ├── Swap.jsx │ │ ├── ThemeSwitch.jsx │ │ ├── Timeline.jsx │ │ ├── TokenBalance.jsx │ │ ├── Wallet.jsx │ │ └── index.js │ │ ├── constants.js │ │ ├── contracts │ │ └── external_contracts.js │ │ ├── ethereumLogo.png │ │ ├── helpers │ │ ├── Transactor.js │ │ ├── index.js │ │ └── loadAppContracts.js │ │ ├── hooks │ │ ├── Debounce.js │ │ ├── GasPrice.js │ │ ├── LocalStorage.js │ │ ├── TokenList.js │ │ ├── index.js │ │ └── useContractConfig.js │ │ ├── index.css │ │ ├── index.jsx │ │ ├── setupTests.js │ │ ├── themes │ │ ├── dark-theme.less │ │ └── light-theme.less │ │ └── views │ │ ├── ExampleUI.jsx │ │ ├── Hints.jsx │ │ ├── Subgraph.jsx │ │ └── index.js ├── services │ ├── graph-node │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── bin │ │ │ ├── create │ │ │ ├── debug │ │ │ ├── deploy │ │ │ ├── reassign │ │ │ └── remove │ │ ├── build.sh │ │ ├── cloudbuild.yaml │ │ ├── docker-compose.yml │ │ ├── hooks │ │ │ └── post_checkout │ │ ├── setup.sh │ │ ├── start │ │ ├── tag.sh │ │ └── wait_for │ └── package.json └── subgraph │ ├── package.json │ └── src │ ├── mapping.ts │ ├── schema.graphql │ └── subgraph.template.yaml └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [packages/**.js{,x}] 4 | indent_style = space 5 | indent_size = 2 6 | 7 | [*.{sol,yul}] 8 | indent_style = space 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | packages/subgraph/subgraph.yaml 2 | packages/subgraph/generated 3 | packages/subgraph/abis/* 4 | packages/hardhat/*.txt 5 | **/aws.json 6 | 7 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 8 | **/node_modules 9 | 10 | packages/hardhat/artifacts* 11 | packages/hardhat/deployments 12 | packages/react-app/src/contracts/* 13 | !packages/react-app/src/contracts/external_contracts.js 14 | packages/hardhat/cache* 15 | packages/**/data 16 | !packages/react-app/src/contracts/contracts.js 17 | 18 | 19 | # ts 20 | packages/hardhat-ts/cache 21 | # secrets 22 | .secret 23 | 24 | packages/subgraph/config/config.json 25 | tenderly.yaml 26 | 27 | # dependencies 28 | /node_modules 29 | /.pnp 30 | .pnp.js 31 | 32 | # testing 33 | coverage 34 | 35 | # production 36 | build 37 | # yarn / eslint 38 | .yarn/cache 39 | .yarn/install-state.gz 40 | .yarn/build-state.yml 41 | .eslintcache 42 | # testing 43 | coverage 44 | 45 | # production 46 | build 47 | 48 | # Hardhat files 49 | cache 50 | artifacts 51 | 52 | # misc 53 | .DS_Store 54 | .env* 55 | 56 | # debug 57 | npm-debug.log* 58 | yarn-debug.log* 59 | yarn-error.log* 60 | 61 | .idea 62 | 63 | # Local Netlify folder 64 | .netlify 65 | *.tsbuildinfo 66 | *.stackdump 67 | 68 | # doc directory 69 | /doc -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/services/arbitrum"] 2 | path = packages/services/arbitrum 3 | url = https://github.com/OffchainLabs/arbitrum 4 | branch = master 5 | [submodule "packages/services/optimism"] 6 | path = packages/services/optimism 7 | url = https://github.com/ethereum-optimism/optimism 8 | branch = regenesis/0.4.0 9 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: App 3 | init: > 4 | yarn && 5 | gp sync-done install 6 | command: REACT_APP_PROVIDER=$(gp url 8545) yarn start 7 | - name: Chain 8 | init: gp sync-await install 9 | command: yarn chain 10 | openMode: split-right 11 | - name: Deployment 12 | init: gp sync-await install 13 | command: yarn deploy 14 | openMode: split-right 15 | ports: 16 | - port: 3000 17 | onOpen: open-preview 18 | - port: 8545 19 | onOpen: ignore 20 | github: 21 | prebuilds: 22 | pullRequestsFromForks: true 23 | addComment: true 24 | vscode: 25 | extensions: 26 | - dbaeumer.vscode-eslint 27 | - esbenp.prettier-vscode 28 | - juanblanco.solidity -------------------------------------------------------------------------------- /.husky/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn install 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesDirectory": "node_modules/@scaffold-eth/hardhat/node_modules/", 3 | "solidity-va.test.defaultUnittestTemplate": "hardhat", 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.svn": true, 7 | "**/.hg": true, 8 | "**/CVS": true, 9 | "**/.DS_Store": true, 10 | "**/.cache": true, 11 | "**/.vs/": true, 12 | "**/*.cs": true, 13 | "**/*.orig": true, 14 | "**/bin/": true, 15 | "**/build/": true, 16 | "**/debug/": true, 17 | "**/dist/": true, 18 | "**/node_modules/": true, 19 | "**/obj": true, 20 | "yarn-error.log": true, 21 | "**/yarn-error.log": true, 22 | "packages\\eth-hooks/lib": true 23 | }, 24 | "explorerExclude.backup": null, 25 | "eslint.workingDirectories": [ 26 | 27 | { "directory": "packages/eth-hooks", "changeProcessCWD": true }, 28 | { "directory": "packages/hardhat-ts", "changeProcessCWD": true }, 29 | { "directory": "packages/vite-app-ts", "changeProcessCWD": true }, 30 | ], 31 | "search.exclude": { 32 | "**/yarn-error.log": true, 33 | "**/yarn.lock": true 34 | } 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Austin Griffith 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🏗 Scaffold-ETH 2 | 3 | > everything you need to build on Ethereum! 🚀 4 | 5 | 🧪 Quickly experiment with Solidity using a frontend that adapts to your smart contract: 6 | 7 | ![image](https://user-images.githubusercontent.com/2653167/124158108-c14ca380-da56-11eb-967e-69cde37ca8eb.png) 8 | 9 | 10 | # 🏄‍♂️ Quick Start 11 | 12 | Prerequisites: [Node](https://nodejs.org/en/download/) plus [Yarn](https://classic.yarnpkg.com/en/docs/install/) and [Git](https://git-scm.com/downloads) 13 | 14 | > clone/fork 🏗 scaffold-eth: 15 | 16 | ```bash 17 | git clone https://github.com/scaffold-eth/scaffold-eth-examples.git 18 | ``` 19 | 20 | > install and start your 👷‍ Hardhat chain: 21 | 22 | ```bash 23 | cd scaffold-eth-examples 24 | yarn install 25 | yarn chain 26 | ``` 27 | 28 | > in a second terminal window, start your 📱 frontend: 29 | 30 | ```bash 31 | cd scaffold-eth-examples 32 | yarn start 33 | ``` 34 | 35 | > in a third terminal window, 🛰 deploy your contract: 36 | 37 | ```bash 38 | cd scaffold-eth-examples 39 | yarn deploy 40 | ``` 41 | 42 | 🔏 Edit your smart contract `YourContract.sol` in `packages/hardhat/contracts` 43 | 44 | 📝 Edit your frontend `App.jsx` in `packages/react-app/src` 45 | 46 | 💼 Edit your deployment scripts in `packages/hardhat/deploy` 47 | 48 | 📱 Open http://localhost:3000 to see the app 49 | 50 | # 📚 Documentation 51 | 52 | Documentation, tutorials, challenges, and many more resources, visit: [docs.scaffoldeth.io](https://docs.scaffoldeth.io) 53 | 54 | # 🔭 Learning Solidity 55 | 56 | 📕 Read the docs: https://docs.soliditylang.org 57 | 58 | 📚 Go through each topic from [solidity by example](https://solidity-by-example.org) editing `YourContract.sol` in **🏗 scaffold-eth** 59 | 60 | - [Primitive Data Types](https://solidity-by-example.org/primitives/) 61 | - [Mappings](https://solidity-by-example.org/mapping/) 62 | - [Structs](https://solidity-by-example.org/structs/) 63 | - [Modifiers](https://solidity-by-example.org/function-modifier/) 64 | - [Events](https://solidity-by-example.org/events/) 65 | - [Inheritance](https://solidity-by-example.org/inheritance/) 66 | - [Payable](https://solidity-by-example.org/payable/) 67 | - [Fallback](https://solidity-by-example.org/fallback/) 68 | 69 | 📧 Learn the [Solidity globals and units](https://solidity.readthedocs.io/en/v0.6.6/units-and-global-variables.html) 70 | 71 | # 🛠 Buidl 72 | 73 | Check out all the [active branches](https://github.com/austintgriffith/scaffold-eth/branches/active), [open issues](https://github.com/austintgriffith/scaffold-eth/issues), and join/fund the 🏰 [BuidlGuidl](https://BuidlGuidl.com)! 74 | 75 | 76 | - 🚤 [Follow the full Ethereum Speed Run](https://medium.com/@austin_48503/%EF%B8%8Fethereum-dev-speed-run-bd72bcba6a4c) 77 | 78 | 79 | - 🎟 [Create your first NFT](https://github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-0-simple-nft) 80 | - 🥩 [Build a staking smart contract](https://github.com/austintgriffith/scaffold-eth/tree/challenge-1-decentralized-staking) 81 | - 🏵 [Deploy a token and vendor](https://github.com/austintgriffith/scaffold-eth/tree/challenge-2-token-vendor) 82 | - 🎫 [Extend the NFT example to make a "buyer mints" marketplace](https://github.com/austintgriffith/scaffold-eth/tree/buyer-mints-nft) 83 | - 🎲 [Learn about commit/reveal](https://github.com/austintgriffith/scaffold-eth/tree/commit-reveal-with-frontend) 84 | - ✍️ [Learn how ecrecover works](https://github.com/austintgriffith/scaffold-eth/tree/signature-recover) 85 | - 👩‍👩‍👧‍👧 [Build a multi-sig that uses off-chain signatures](https://github.com/austintgriffith/scaffold-eth/tree/meta-multi-sig) 86 | - ⏳ [Extend the multi-sig to stream ETH](https://github.com/austintgriffith/scaffold-eth/tree/streaming-meta-multi-sig) 87 | - ⚖️ [Learn how a simple DEX works](https://medium.com/@austin_48503/%EF%B8%8F-minimum-viable-exchange-d84f30bd0c90) 88 | - 🦍 [Ape into learning!](https://github.com/austintgriffith/scaffold-eth/tree/aave-ape) 89 | 90 | # 💬 Support Chat 91 | 92 | Join the telegram [support chat 💬](https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA) to ask questions and find others building with 🏗 scaffold-eth! 93 | 94 | --- 95 | 96 | 🙏 Please check out our [Gitcoin grant](https://gitcoin.co/grants/2851/scaffold-eth) too! 97 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # 🏄‍♂️ Using Docker 2 | 3 | Prerequisite: [Docker](https://docs.docker.com/engine/install/)/) 4 | 5 | > clone/fork 🏗 scaffold-eth: 6 | 7 | ```bash 8 | git clone https://github.com/austintgriffith/scaffold-eth.git 9 | ``` 10 | 11 | > [basic] run the script that sets the stack up and that's it (takes some minutes to finish): 12 | 13 | ```bash 14 | cd scaffold-eth 15 | ./docker/setup.sh start 16 | ``` 17 | 18 | > [basic] to re-deploy your contracts (container must be up and running): 19 | 20 | ```bash 21 | ./docker/setup.sh deploy 22 | ``` 23 | 24 | > [advanced] running front-end on a different port (eg. 8080): 25 | 26 | ```bash 27 | docker rm -f SCAFFOLD_ETH 28 | 29 | docker run \ 30 | --name SCAFFOLD_ETH \ 31 | -v `pwd`:/opt/scaffold-eth \ 32 | -w /opt/scaffold-eth \ 33 | -e PORT=8080 \ 34 | -p 8080:8080 \ 35 | -p 8545:8545 \ 36 | -dt node:16 37 | 38 | ./docker/setup.sh start 39 | ``` 40 | 41 | > [advanced] running the container in interactive mode (must run each tool manually): 42 | 43 | ```bash 44 | docker rm -f SCAFFOLD_ETH 45 | 46 | docker run \ 47 | --name SCAFFOLD_ETH \ 48 | -v `pwd`:/opt/scaffold-eth \ 49 | -w /opt/scaffold-eth \ 50 | -p 3000:3000 \ 51 | -p 8545:8545 \ 52 | --entrypoint /bin/bash \ 53 | -ti node:16 54 | ``` 55 | 56 | 🔏 Edit your smart contract `YourContract.sol` in `packages/hardhat/contracts` 57 | 58 | 📝 Edit your frontend `App.jsx` in `packages/react-app/src` 59 | 60 | 💼 Edit your deployment scripts in `packages/hardhat/deploy` 61 | 62 | 📱 Open http://localhost:3000 to see the app 63 | -------------------------------------------------------------------------------- /docker/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = "start" ]; then 4 | # Run Docker container 5 | # to run the frontend on a different port add the "-e PORT=8080" parameter and change "-p 8080:8080" one. 6 | docker restart SCAFFOLD_ETH || docker run \ 7 | --name SCAFFOLD_ETH \ 8 | -v `pwd`:/opt/scaffold-eth \ 9 | -w /opt/scaffold-eth \ 10 | -p 3000:3000 \ 11 | -p 8545:8545 \ 12 | -dt node:16 13 | 14 | docker exec -ti SCAFFOLD_ETH bash -c "yarn install" 15 | docker exec -dt SCAFFOLD_ETH bash -c "yarn chain" 16 | sleep 5 17 | docker exec -ti SCAFFOLD_ETH bash -c "yarn deploy" 18 | docker exec -dt SCAFFOLD_ETH bash -c "yarn start" 19 | else 20 | if [ "$1" = "deploy" ]; then 21 | docker exec -ti SCAFFOLD_ETH bash -c "yarn deploy" 22 | else 23 | echo "Invalid command. Choose 'start' or 'deploy'." 24 | fi 25 | fi 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/monorepo", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "ethereum", 6 | "react", 7 | "uniswap", 8 | "workspaces", 9 | "yarn" 10 | ], 11 | "private": true, 12 | "scripts": { 13 | "react-app:build": "yarn workspace @scaffold-eth/react-app build --max-old-space-size=12288", 14 | "react-app:eject": "yarn workspace @scaffold-eth/react-app eject", 15 | "react-app:start": "yarn workspace @scaffold-eth/react-app start", 16 | "react-app:test": "yarn workspace @scaffold-eth/react-app test", 17 | "build": "yarn workspace @scaffold-eth/react-app build --max-old-space-size=12288", 18 | "prettier": "yarn workspace @scaffold-eth/react-app prettier", 19 | "chain": "yarn workspace @scaffold-eth/hardhat chain", 20 | "fork": "yarn workspace @scaffold-eth/hardhat fork", 21 | "node": "yarn workspace @scaffold-eth/hardhat chain", 22 | "test": "yarn workspace @scaffold-eth/hardhat test", 23 | "start": "yarn workspace @scaffold-eth/react-app start", 24 | "compile": "yarn workspace @scaffold-eth/hardhat compile", 25 | "deploy": "yarn workspace @scaffold-eth/hardhat deploy", 26 | "watch": "yarn workspace @scaffold-eth/hardhat watch", 27 | "accounts": "yarn workspace @scaffold-eth/hardhat accounts", 28 | "balance": "yarn workspace @scaffold-eth/hardhat balance", 29 | "send": "yarn workspace @scaffold-eth/hardhat send", 30 | "ipfs": "yarn workspace @scaffold-eth/react-app ipfs", 31 | "surge": "yarn workspace @scaffold-eth/react-app surge", 32 | "s3": "yarn workspace @scaffold-eth/react-app s3", 33 | "ship": "yarn workspace @scaffold-eth/react-app ship", 34 | "generate": "yarn workspace @scaffold-eth/hardhat generate", 35 | "account": "yarn workspace @scaffold-eth/hardhat account", 36 | "mineContractAddress": "cd packages/hardhat && npx hardhat mineContractAddress", 37 | "wallet": "cd packages/hardhat && npx hardhat wallet", 38 | "fundedwallet": "cd packages/hardhat && npx hardhat fundedwallet", 39 | "flatten": "cd packages/hardhat && npx hardhat flatten", 40 | "clean": "cd packages/hardhat && npx hardhat clean", 41 | "run-graph-node": "yarn workspace @scaffold-eth/services run-graph-node", 42 | "remove-graph-node": "yarn workspace @scaffold-eth/services remove-graph-node", 43 | "clean-graph-node": "yarn workspace @scaffold-eth/services clean-graph-node", 44 | "graph-prepare": "mustache packages/subgraph/config/config.json packages/subgraph/src/subgraph.template.yaml > packages/subgraph/subgraph.yaml", 45 | "graph-codegen": "yarn workspace @scaffold-eth/subgraph graph codegen", 46 | "graph-build": "yarn workspace @scaffold-eth/subgraph graph build", 47 | "graph-create-local": "yarn workspace @scaffold-eth/subgraph graph create --node http://localhost:8020/ scaffold-eth/your-contract", 48 | "graph-remove-local": "yarn workspace @scaffold-eth/subgraph graph remove --node http://localhost:8020/ scaffold-eth/your-contract", 49 | "graph-deploy-local": "yarn workspace @scaffold-eth/subgraph graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract", 50 | "graph-ship-local": "yarn graph-prepare && yarn graph-codegen && yarn graph-deploy-local", 51 | "deploy-and-graph": "yarn deploy && yarn graph-ship-local", 52 | "theme": "yarn workspace @scaffold-eth/react-app theme", 53 | "watch-theme": "yarn workspace @scaffold-eth/react-app watch", 54 | "postinstall": "husky install" 55 | }, 56 | "workspaces": { 57 | "packages": [ 58 | "packages/*" 59 | ], 60 | "nohoist": [ 61 | "**/@graphprotocol/graph-ts", 62 | "**/@graphprotocol/graph-ts/**", 63 | "**/hardhat", 64 | "**/hardhat/**", 65 | "**/hardhat-ts", 66 | "**/hardhat-ts/**" 67 | ] 68 | }, 69 | "dependencies": {}, 70 | "devDependencies": { 71 | "husky": "^7.0.2" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | ts: "never", 15 | }, 16 | ], 17 | "import/prefer-default-export": "off", 18 | "prefer-destructuring": "off", 19 | "prefer-template": "off", 20 | "no-console": "off", 21 | "func-names": "off", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/YourContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.0 <0.9.0; 2 | //SPDX-License-Identifier: MIT 3 | 4 | import "hardhat/console.sol"; 5 | // import "@openzeppelin/contracts/access/Ownable.sol"; 6 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol 7 | 8 | contract YourContract { 9 | 10 | // event SetPurpose(address sender, string purpose); 11 | 12 | string public purpose = "Building Unstoppable Apps!!!"; 13 | 14 | constructor() { 15 | // what should we do on deploy? 16 | } 17 | 18 | function setPurpose(string memory newPurpose) public { 19 | purpose = newPurpose; 20 | console.log(msg.sender,"set purpose to",purpose); 21 | // emit SetPurpose(msg.sender, purpose); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/hardhat/deploy/00_deploy_your_contract.js: -------------------------------------------------------------------------------- 1 | // deploy/00_deploy_your_contract.js 2 | 3 | const { ethers } = require("hardhat"); 4 | 5 | const localChainId = "31337"; 6 | 7 | module.exports = async ({ getNamedAccounts, deployments, getChainId }) => { 8 | const { deploy } = deployments; 9 | const { deployer } = await getNamedAccounts(); 10 | const chainId = await getChainId(); 11 | 12 | await deploy("YourContract", { 13 | // Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy 14 | from: deployer, 15 | // args: [ "Hello", ethers.utils.parseEther("1.5") ], 16 | log: true, 17 | }); 18 | 19 | // Getting a previously deployed contract 20 | const YourContract = await ethers.getContract("YourContract", deployer); 21 | /* await YourContract.setPurpose("Hello"); 22 | 23 | To take ownership of yourContract using the ownable library uncomment next line and add the 24 | address you want to be the owner. 25 | // yourContract.transferOwnership(YOUR_ADDRESS_HERE); 26 | 27 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 28 | */ 29 | 30 | /* 31 | //If you want to send value to an address from the deployer 32 | const deployerWallet = ethers.provider.getSigner() 33 | await deployerWallet.sendTransaction({ 34 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 35 | value: ethers.utils.parseEther("0.001") 36 | }) 37 | */ 38 | 39 | /* 40 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 41 | const yourContract = await deploy("YourContract", [], { 42 | value: ethers.utils.parseEther("0.05") 43 | }); 44 | */ 45 | 46 | /* 47 | //If you want to link a library into your contract: 48 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 49 | const yourContract = await deploy("YourContract", [], {}, { 50 | LibraryName: **LibraryAddress** 51 | }); 52 | */ 53 | 54 | // Verify your contracts with Etherscan 55 | // You don't want to verify on localhost 56 | if (chainId !== localChainId) { 57 | await run("verify:verify", { 58 | address: YourContract.address, 59 | contract: "contracts/YourContract.sol:YourContract", 60 | contractArguments: [], 61 | }); 62 | } 63 | }; 64 | module.exports.tags = ["YourContract"]; 65 | -------------------------------------------------------------------------------- /packages/hardhat/example.env: -------------------------------------------------------------------------------- 1 | # This is a template for the environment variables you'll need for 2 | # deployment on Rinkeby and mainnet 3 | 4 | # To use, copy this file, rename it .env, and fill in the values 5 | # Everything will be interpreted as a string by JavaScript even though 6 | # you should not wrap values in quotation marks 7 | 8 | # Infura endpoints should be passed in WITHOUT the rest of the url 9 | # Private keys should be pasted WITHOUT the 0x prefix 10 | 11 | # Example 12 | EXAMPLE_INFURA_KEY=582dabbadbeef8... 13 | EXAMPLE_DEPLOYER_PRIV_KEY=deadbeef01EEdf972aBB... 14 | 15 | # Rinkeby 16 | RINKEBY_INFURA_KEY= 17 | RINKEBY_DEPLOYER_PRIV_KEY= 18 | 19 | # Kovan 20 | KOVAN_INFURA_KEY= 21 | KOVAN_DEPLOYER_PRIV_KEY= 22 | 23 | # mainnet 24 | MAINNET_INFURA_KEY= 25 | MAINNET_DEPLOYER_PRIV_KEY= 26 | 27 | # Ropsten 28 | ROPSTEN_INFURA_KEY= 29 | ROPSTEN_DEPLOYER_PRIV_KEY= 30 | 31 | # Goerli 32 | GOERLI_INFURA_KEY= 33 | GOERLI_DEPLOYER_PRIV_KEY= 34 | 35 | # xDai 36 | XDAI_DEPLOYER_PRIV_KEY= -------------------------------------------------------------------------------- /packages/hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/hardhat", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "eslint": "^7.5.0", 8 | "eslint-config-airbnb": "^18.2.0", 9 | "eslint-config-prettier": "^6.11.0", 10 | "eslint-plugin-babel": "^5.3.1", 11 | "eslint-plugin-prettier": "^3.4.0" 12 | }, 13 | "dependencies": { 14 | "@eth-optimism/hardhat-ovm": "^0.2.2", 15 | "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers", 16 | "@nomiclabs/hardhat-waffle": "^2.0.0", 17 | "@openzeppelin/contracts": "^4.3.3", 18 | "@tenderly/hardhat-tenderly": "^1.0.10", 19 | "@nomiclabs/hardhat-etherscan": "^2.1.7", 20 | "chai": "^4.2.0", 21 | "chalk": "^4.1.0", 22 | "dotenv": "^8.2.0", 23 | "ethereum-waffle": "^3.1.1", 24 | "ethers": "^5.4.4", 25 | "hardhat": "2.6.0", 26 | "hardhat-deploy": "^0.9.0", 27 | "hardhat-gas-reporter": "^1.0.4", 28 | "node-watch": "^0.7.0", 29 | "qrcode-terminal": "^0.12.0", 30 | "ramda": "^0.27.1" 31 | }, 32 | "scripts": { 33 | "chain": "hardhat node --network hardhat --no-deploy", 34 | "fork": "hardhat node --no-deploy --network hardhat --fork https://mainnet.infura.io/v3/460f40a260564ac4a4f4b3fffb032dad", 35 | "test": "hardhat test --network hardhat", 36 | "compile": "hardhat compile", 37 | "deploy": "hardhat deploy --export-all ../react-app/src/contracts/hardhat_contracts.json", 38 | "postdeploy": "hardhat run scripts/publish.js", 39 | "watch": "node scripts/watch.js", 40 | "accounts": "hardhat accounts", 41 | "balance": "hardhat balance", 42 | "send": "hardhat send", 43 | "generate": "hardhat generate", 44 | "account": "hardhat account", 45 | "etherscan-verify": "hardhat etherscan-verify --api-key PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint no-use-before-define: "warn" */ 2 | const fs = require("fs"); 3 | const chalk = require("chalk"); 4 | const { config, ethers, tenderly, run } = require("hardhat"); 5 | const { utils } = require("ethers"); 6 | const R = require("ramda"); 7 | 8 | /* 9 | 10 | _______ _________ _______ _______ 11 | ( ____ \\__ __/( ___ )( ____ ) 12 | | ( \/ ) ( | ( ) || ( )| 13 | | (_____ | | | | | || (____)| 14 | (_____ ) | | | | | || _____) 15 | ) | | | | | | || ( 16 | /\____) | | | | (___) || ) 17 | \_______) )_( (_______)|/ 18 | 19 | This deploy script is no longer in use, but is left for reference purposes! 20 | 21 | scaffold-eth now uses hardhat-deploy to manage deployments, see the /deploy folder 22 | And learn more here: https://www.npmjs.com/package/hardhat-deploy 23 | 24 | */ 25 | 26 | const main = async () => { 27 | console.log("\n\n 📡 Deploying...\n"); 28 | 29 | const yourContract = await deploy("YourContract"); // <-- add in constructor args like line 19 vvvv 30 | // use for local token bridging 31 | // const mockToken = await deploy("MockERC20") // <-- add in constructor args like line 19 vvvv 32 | 33 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 34 | //const secondContract = await deploy("SecondContract") 35 | 36 | // const exampleToken = await deploy("ExampleToken") 37 | // const examplePriceOracle = await deploy("ExamplePriceOracle") 38 | // const smartContractWallet = await deploy("SmartContractWallet",[exampleToken.address,examplePriceOracle.address]) 39 | 40 | /* 41 | //If you want to send value to an address from the deployer 42 | const deployerWallet = ethers.provider.getSigner() 43 | await deployerWallet.sendTransaction({ 44 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 45 | value: ethers.utils.parseEther("0.001") 46 | }) 47 | */ 48 | 49 | /* 50 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 51 | const yourContract = await deploy("YourContract", [], { 52 | value: ethers.utils.parseEther("0.05") 53 | }); 54 | */ 55 | 56 | /* 57 | //If you want to link a library into your contract: 58 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 59 | const yourContract = await deploy("YourContract", [], {}, { 60 | LibraryName: **LibraryAddress** 61 | }); 62 | */ 63 | 64 | //If you want to verify your contract on tenderly.co (see setup details in the scaffold-eth README!) 65 | /* 66 | await tenderlyVerify( 67 | {contractName: "YourContract", 68 | contractAddress: yourContract.address 69 | }) 70 | */ 71 | 72 | console.log( 73 | " 💾 Artifacts (address, abi, and args) saved to: ", 74 | chalk.blue("packages/hardhat/artifacts/"), 75 | "\n\n" 76 | ); 77 | }; 78 | 79 | const deploy = async ( 80 | contractName, 81 | _args = [], 82 | overrides = {}, 83 | libraries = {} 84 | ) => { 85 | console.log(` 🛰 Deploying: ${contractName}`); 86 | 87 | const contractArgs = _args || []; 88 | const contractArtifacts = await ethers.getContractFactory(contractName, { 89 | libraries: libraries, 90 | }); 91 | const deployed = await contractArtifacts.deploy(...contractArgs, overrides); 92 | const encoded = abiEncodeArgs(deployed, contractArgs); 93 | fs.writeFileSync(`artifacts/${contractName}.address`, deployed.address); 94 | 95 | let extraGasInfo = ""; 96 | if (deployed && deployed.deployTransaction) { 97 | const gasUsed = deployed.deployTransaction.gasLimit.mul( 98 | deployed.deployTransaction.gasPrice 99 | ); 100 | extraGasInfo = `${utils.formatEther(gasUsed)} ETH, tx hash ${ 101 | deployed.deployTransaction.hash 102 | }`; 103 | } 104 | 105 | console.log( 106 | " 📄", 107 | chalk.cyan(contractName), 108 | "deployed to:", 109 | chalk.magenta(deployed.address) 110 | ); 111 | console.log(" ⛽", chalk.grey(extraGasInfo)); 112 | 113 | await tenderly.persistArtifacts({ 114 | name: contractName, 115 | address: deployed.address, 116 | }); 117 | 118 | if (!encoded || encoded.length <= 2) return deployed; 119 | fs.writeFileSync(`artifacts/${contractName}.args`, encoded.slice(2)); 120 | 121 | return deployed; 122 | }; 123 | 124 | // ------ utils ------- 125 | 126 | // abi encodes contract arguments 127 | // useful when you want to manually verify the contracts 128 | // for example, on Etherscan 129 | const abiEncodeArgs = (deployed, contractArgs) => { 130 | // not writing abi encoded args if this does not pass 131 | if ( 132 | !contractArgs || 133 | !deployed || 134 | !R.hasPath(["interface", "deploy"], deployed) 135 | ) { 136 | return ""; 137 | } 138 | const encoded = utils.defaultAbiCoder.encode( 139 | deployed.interface.deploy.inputs, 140 | contractArgs 141 | ); 142 | return encoded; 143 | }; 144 | 145 | // checks if it is a Solidity file 146 | const isSolidity = (fileName) => 147 | fileName.indexOf(".sol") >= 0 && 148 | fileName.indexOf(".swp") < 0 && 149 | fileName.indexOf(".swap") < 0; 150 | 151 | const readArgsFile = (contractName) => { 152 | let args = []; 153 | try { 154 | const argsFile = `./contracts/${contractName}.args`; 155 | if (!fs.existsSync(argsFile)) return args; 156 | args = JSON.parse(fs.readFileSync(argsFile)); 157 | } catch (e) { 158 | console.log(e); 159 | } 160 | return args; 161 | }; 162 | 163 | function sleep(ms) { 164 | return new Promise((resolve) => setTimeout(resolve, ms)); 165 | } 166 | 167 | // If you want to verify on https://tenderly.co/ 168 | const tenderlyVerify = async ({ contractName, contractAddress }) => { 169 | let tenderlyNetworks = [ 170 | "kovan", 171 | "goerli", 172 | "mainnet", 173 | "rinkeby", 174 | "ropsten", 175 | "matic", 176 | "mumbai", 177 | "xDai", 178 | "POA", 179 | ]; 180 | let targetNetwork = process.env.HARDHAT_NETWORK || config.defaultNetwork; 181 | 182 | if (tenderlyNetworks.includes(targetNetwork)) { 183 | console.log( 184 | chalk.blue( 185 | ` 📁 Attempting tenderly verification of ${contractName} on ${targetNetwork}` 186 | ) 187 | ); 188 | 189 | await tenderly.persistArtifacts({ 190 | name: contractName, 191 | address: contractAddress, 192 | }); 193 | 194 | let verification = await tenderly.verify({ 195 | name: contractName, 196 | address: contractAddress, 197 | network: targetNetwork, 198 | }); 199 | 200 | return verification; 201 | } else { 202 | console.log( 203 | chalk.grey(` 🧐 Contract verification not supported on ${targetNetwork}`) 204 | ); 205 | } 206 | }; 207 | 208 | main() 209 | .then(() => process.exit(0)) 210 | .catch((error) => { 211 | console.error(error); 212 | process.exit(1); 213 | }); 214 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/publish.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const chalk = require("chalk"); 3 | 4 | const graphDir = "../subgraph"; 5 | const deploymentsDir = "./deployments"; 6 | const publishDir = "../react-app/src/contracts"; 7 | 8 | function publishContract(contractName, networkName) { 9 | try { 10 | let contract = fs 11 | .readFileSync(`${deploymentsDir}/${networkName}/${contractName}.json`) 12 | .toString(); 13 | contract = JSON.parse(contract); 14 | const graphConfigPath = `${graphDir}/config/config.json`; 15 | let graphConfig; 16 | try { 17 | if (fs.existsSync(graphConfigPath)) { 18 | graphConfig = fs.readFileSync(graphConfigPath).toString(); 19 | } else { 20 | graphConfig = "{}"; 21 | } 22 | } catch (e) { 23 | console.log(e); 24 | } 25 | 26 | graphConfig = JSON.parse(graphConfig); 27 | graphConfig[`${networkName}_${contractName}Address`] = contract.address; 28 | 29 | const folderPath = graphConfigPath.replace("/config.json", ""); 30 | if (!fs.existsSync(folderPath)) { 31 | fs.mkdirSync(folderPath); 32 | } 33 | fs.writeFileSync(graphConfigPath, JSON.stringify(graphConfig, null, 2)); 34 | if (!fs.existsSync(`${graphDir}/abis`)) fs.mkdirSync(`${graphDir}/abis`); 35 | fs.writeFileSync( 36 | `${graphDir}/abis/${networkName}_${contractName}.json`, 37 | JSON.stringify(contract.abi, null, 2) 38 | ); 39 | 40 | //Hardhat Deploy writes a file with all ABIs in react-app/src/contracts/contracts.json 41 | //If you need the bytecodes and/or you want one file per ABIs, un-comment the following block. 42 | //Write the contracts ABI, address and bytecodes in case the front-end needs them 43 | // fs.writeFileSync( 44 | // `${publishDir}/${contractName}.address.js`, 45 | // `module.exports = "${contract.address}";` 46 | // ); 47 | // fs.writeFileSync( 48 | // `${publishDir}/${contractName}.abi.js`, 49 | // `module.exports = ${JSON.stringify(contract.abi, null, 2)};` 50 | // ); 51 | // fs.writeFileSync( 52 | // `${publishDir}/${contractName}.bytecode.js`, 53 | // `module.exports = "${contract.bytecode}";` 54 | // ); 55 | 56 | return true; 57 | } catch (e) { 58 | console.log( 59 | "Failed to publish " + chalk.red(contractName) + " to the subgraph." 60 | ); 61 | console.log(e); 62 | return false; 63 | } 64 | } 65 | 66 | async function main() { 67 | const directories = fs.readdirSync(deploymentsDir); 68 | directories.forEach(function (directory) { 69 | const files = fs.readdirSync(`${deploymentsDir}/${directory}`); 70 | files.forEach(function (file) { 71 | if (file.indexOf(".json") >= 0) { 72 | const contractName = file.replace(".json", ""); 73 | publishContract(contractName, directory); 74 | } 75 | }); 76 | }); 77 | console.log("✅ Published contracts to the subgraph package."); 78 | } 79 | main() 80 | .then(() => process.exit(0)) 81 | .catch((error) => { 82 | console.error(error); 83 | process.exit(1); 84 | }); 85 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("🛠 Compiling & Deploying..."); 6 | exec("yarn deploy", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Contracts..."); 14 | watch("./contracts", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/hardhat/test/myTest.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | const { use, expect } = require("chai"); 3 | const { solidity } = require("ethereum-waffle"); 4 | 5 | use(solidity); 6 | 7 | describe("My Dapp", function () { 8 | let myContract; 9 | 10 | // quick fix to let gas reporter fetch data from gas station & coinmarketcap 11 | before((done) => { 12 | setTimeout(done, 2000); 13 | }); 14 | 15 | describe("YourContract", function () { 16 | it("Should deploy YourContract", async function () { 17 | const YourContract = await ethers.getContractFactory("YourContract"); 18 | 19 | myContract = await YourContract.deploy(); 20 | }); 21 | 22 | describe("setPurpose()", function () { 23 | it("Should be able to set a new purpose", async function () { 24 | const newPurpose = "Test Purpose"; 25 | 26 | await myContract.setPurpose(newPurpose); 27 | expect(await myContract.purpose()).to.equal(newPurpose); 28 | }); 29 | 30 | // Uncomment the event and emit lines in YourContract.sol to make this test pass 31 | 32 | /*it("Should emit a SetPurpose event ", async function () { 33 | const [owner] = await ethers.getSigners(); 34 | 35 | const newPurpose = "Another Test Purpose"; 36 | 37 | expect(await myContract.setPurpose(newPurpose)).to. 38 | emit(myContract, "SetPurpose"). 39 | withArgs(owner.address, newPurpose); 40 | });*/ 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/react-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | build/ 3 | node_modules/ 4 | # files 5 | **/*.less 6 | **/*.css 7 | **/*.scss 8 | **/*.json 9 | **/*.png 10 | **/*.svg 11 | -------------------------------------------------------------------------------- /packages/react-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | }, 5 | parser: "babel-eslint", 6 | // airbnb disabled after upgrade to cra 4 due to errors in our code 7 | extends: [/*"airbnb"*/ "plugin:prettier/recommended"], 8 | plugins: [ 9 | /*"babel"*/ 10 | ], 11 | rules: { 12 | "prettier/prettier": "warn", 13 | "prettier/prettier": [ 14 | "warn", 15 | { 16 | endOfLine: "auto", 17 | }, 18 | ], 19 | "import/prefer-default-export": "off", 20 | "prefer-destructuring": "off", 21 | "prefer-template": "off", 22 | "react/prop-types": "off", 23 | "react/destructuring-assignment": "off", 24 | "no-console": "off", 25 | "jsx-a11y/accessible-emoji": ["off"], 26 | "jsx-a11y/click-events-have-key-events": ["off"], 27 | "jsx-a11y/no-static-element-interactions": ["off"], 28 | "no-underscore-dangle": "off", 29 | "no-nested-ternary": "off", 30 | "no-restricted-syntax": "off", 31 | "no-plusplus": "off", 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/react-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "printWidth": 120, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-app/.sample.env: -------------------------------------------------------------------------------- 1 | # REACT_APP_PROVIDER=https://rinkeby.infura.io/v3/2717afb6bf164045b5d5468031b93f87 2 | -------------------------------------------------------------------------------- /packages/react-app/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"); 2 | const gulpless = require("gulp-less"); 3 | const postcss = require("gulp-postcss"); 4 | const debug = require("gulp-debug"); 5 | var csso = require("gulp-csso"); 6 | const autoprefixer = require("autoprefixer"); 7 | const NpmImportPlugin = require("less-plugin-npm-import"); 8 | 9 | gulp.task("less", function () { 10 | const plugins = [autoprefixer()]; 11 | 12 | return gulp 13 | .src("src/themes/*-theme.less") 14 | .pipe(debug({ title: "Less files:" })) 15 | .pipe( 16 | gulpless({ 17 | javascriptEnabled: true, 18 | plugins: [new NpmImportPlugin({ prefix: "~" })], 19 | }), 20 | ) 21 | .pipe(postcss(plugins)) 22 | .pipe( 23 | csso({ 24 | debug: true, 25 | }), 26 | ) 27 | .pipe(gulp.dest("./public")); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/react-app", 3 | "version": "1.0.0", 4 | "homepage": ".", 5 | "browserslist": { 6 | "production": [ 7 | ">0.2%", 8 | "not dead", 9 | "not op_mini all" 10 | ], 11 | "development": [ 12 | "last 1 chrome version", 13 | "last 1 firefox version", 14 | "last 1 safari version" 15 | ] 16 | }, 17 | "dependencies": { 18 | "@ant-design/icons": "^4.2.2", 19 | "@apollo/client": "^3.3.21", 20 | "@apollo/react-hooks": "^4.0.0", 21 | "@portis/web3": "^4.0.5", 22 | "@ramp-network/ramp-instant-sdk": "^2.2.0", 23 | "@testing-library/jest-dom": "^5.11.4", 24 | "@testing-library/react": "^11.1.0", 25 | "@testing-library/user-event": "^12.1.8", 26 | "@uniswap/sdk": "^3.0.3", 27 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 28 | "@walletconnect/web3-provider": "^1.5.2", 29 | "antd": "4.16.0", 30 | "apollo-boost": "^0.4.9", 31 | "apollo-client": "^2.6.10", 32 | "apollo-utilities": "^1.3.4", 33 | "arb-ts": "^0.0.18", 34 | "authereum": "^0.1.14", 35 | "axios": "^0.21.4", 36 | "bnc-notify": "^1.5.0", 37 | "dotenv": "^8.2.0", 38 | "eth-hooks": "2.3.8", 39 | "ethers": "^5.4.1", 40 | "fortmatic": "^2.2.1", 41 | "graphiql": "^1.4.7", 42 | "graphql": "^15.3.0", 43 | "isomorphic-fetch": "^3.0.0", 44 | "node-watch": "^0.7.1", 45 | "postcss": "^8.2.6", 46 | "qrcode.react": "^1.0.0", 47 | "react": "^17.0.2", 48 | "react-blockies": "^1.4.1", 49 | "react-css-theme-switcher": "^0.2.2", 50 | "react-dom": "^17.0.2", 51 | "react-qr-reader": "^2.2.1", 52 | "react-router-dom": "^5.2.0", 53 | "react-scripts": "4.0.0", 54 | "walletlink": "^2.1.5", 55 | "web3modal": "^1.9.1" 56 | }, 57 | "devDependencies": { 58 | "@apollo/client": "^3.3.21", 59 | "@testing-library/dom": "^6.12.2", 60 | "@types/react": "^16.9.19", 61 | "autoprefixer": "^10.2.4", 62 | "chalk": "^4.1.0", 63 | "eslint": "^7.5.0", 64 | "eslint-config-airbnb": "^18.2.0", 65 | "eslint-config-prettier": "^8.3.0", 66 | "eslint-plugin-babel": "^5.3.1", 67 | "eslint-plugin-import": "^2.23.4", 68 | "eslint-plugin-jsx-a11y": "^6.4.1", 69 | "eslint-plugin-prettier": "^3.4.0", 70 | "eslint-plugin-react": "^7.22.0", 71 | "eslint-plugin-react-hooks": "^4.2.0", 72 | "gulp": "^4.0.2", 73 | "gulp-csso": "^4.0.1", 74 | "gulp-debug": "^4.0.0", 75 | "gulp-less": "^4.0.1", 76 | "gulp-postcss": "^9.0.0", 77 | "ipfs-http-client": "^45.0.0", 78 | "less-plugin-npm-import": "^2.1.0", 79 | "prettier": "^2.0.5", 80 | "s3-folder-upload": "^2.3.1", 81 | "surge": "^0.21.5" 82 | }, 83 | "eslintConfig": { 84 | "extends": "react-app" 85 | }, 86 | "scripts": { 87 | "build": "react-scripts build", 88 | "eject": "react-scripts eject", 89 | "prestart": "node ./scripts/create_contracts.js", 90 | "start": "react-scripts start", 91 | "test": "react-scripts test", 92 | "lint": "eslint --config ./.eslintrc.js --ignore-path ./.eslintignore ./src/**/*", 93 | "ipfs": "node ./scripts/ipfs.js", 94 | "surge": "cp build/index.html build/200.html && surge ./build", 95 | "s3": "node ./scripts/s3.js", 96 | "ship": "yarn surge", 97 | "theme": "npx gulp less", 98 | "watch": "node ./scripts/watch.js", 99 | "prettier": "npx prettier --write . '!(node_module|build)/**/*'" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-examples/896ea245d30fd8d697c543347a2e13717d394eca/packages/react-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | Ethereum App 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-examples/896ea245d30fd8d697c543347a2e13717d394eca/packages/react-app/public/logo192.png -------------------------------------------------------------------------------- /packages/react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-examples/896ea245d30fd8d697c543347a2e13717d394eca/packages/react-app/public/logo512.png -------------------------------------------------------------------------------- /packages/react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "🏗 Scaffold-Eth App", 3 | "start_url": ".", 4 | "name": "🏗 Scaffold-Eth App", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | }, 11 | { 12 | "src": "logo192.png", 13 | "type": "image/png", 14 | "sizes": "192x192" 15 | }, 16 | { 17 | "src": "logo512.png", 18 | "type": "image/png", 19 | "sizes": "512x512" 20 | } 21 | ], 22 | "start_url": ".", 23 | "display": "standalone", 24 | "theme_color": "#000000", 25 | "background_color": "#ffffff" 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /packages/react-app/public/scaffold-eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-examples/896ea245d30fd8d697c543347a2e13717d394eca/packages/react-app/public/scaffold-eth.png -------------------------------------------------------------------------------- /packages/react-app/scripts/create_contracts.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | if (!fs.existsSync("./src/contracts/hardhat_contracts.json")) { 4 | try { 5 | fs.writeFileSync("./src/contracts/hardhat_contracts.json", JSON.stringify({})); 6 | 7 | console.log("src/contracts/hardhat_contracts.json created."); 8 | } catch (error) { 9 | console.log(error); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-app/scripts/ipfs.js: -------------------------------------------------------------------------------- 1 | const ipfsAPI = require("ipfs-http-client"); 2 | const chalk = require("chalk"); 3 | const { clearLine } = require("readline"); 4 | 5 | const { globSource } = ipfsAPI; 6 | 7 | const infura = { host: "ipfs.infura.io", port: "5001", protocol: "https" }; 8 | // run your own ipfs daemon: https://docs.ipfs.io/how-to/command-line-quick-start/#install-ipfs 9 | // const localhost = { host: "localhost", port: "5001", protocol: "http" }; 10 | 11 | const ipfs = ipfsAPI(infura); 12 | 13 | const ipfsGateway = "https://ipfs.io/ipfs/"; 14 | const ipnsGateway = "https://ipfs.io/ipns/"; 15 | 16 | const addOptions = { 17 | pin: true, 18 | }; 19 | 20 | const pushDirectoryToIPFS = async path => { 21 | try { 22 | const response = await ipfs.add(globSource(path, { recursive: true }), addOptions); 23 | return response; 24 | } catch (e) { 25 | return {}; 26 | } 27 | }; 28 | 29 | const publishHashToIPNS = async ipfsHash => { 30 | try { 31 | const response = await ipfs.name.publish(`/ipfs/${ipfsHash}`); 32 | return response; 33 | } catch (e) { 34 | return {}; 35 | } 36 | }; 37 | 38 | const nodeMayAllowPublish = ipfsClient => { 39 | // You must have your own IPFS node in order to publish an IPNS name 40 | // This contains a blacklist of known nodes which do not allow users to publish IPNS names. 41 | const nonPublishingNodes = ["ipfs.infura.io"]; 42 | const { host } = ipfsClient.getEndpointConfig(); 43 | return !nonPublishingNodes.some(nodeUrl => host.includes(nodeUrl)); 44 | }; 45 | 46 | const deploy = async () => { 47 | console.log("🛰 Sending to IPFS..."); 48 | const { cid } = await pushDirectoryToIPFS("./build"); 49 | if (!cid) { 50 | console.log(`📡 App deployment failed`); 51 | return false; 52 | } 53 | console.log(`📡 App deployed to IPFS with hash: ${chalk.cyan(cid.toString())}`); 54 | 55 | console.log(); 56 | 57 | let ipnsName = ""; 58 | if (nodeMayAllowPublish(ipfs)) { 59 | console.log(`✍️ Publishing /ipfs/${cid.toString()} to IPNS...`); 60 | process.stdout.write(" Publishing to IPNS can take up to roughly two minutes.\r"); 61 | ipnsName = (await publishHashToIPNS(cid.toString())).name; 62 | clearLine(process.stdout, 0); 63 | if (!ipnsName) { 64 | console.log(" Publishing IPNS name on node failed."); 65 | } 66 | console.log(`🔖 App published to IPNS with name: ${chalk.cyan(ipnsName)}`); 67 | console.log(); 68 | } 69 | 70 | console.log("🚀 Deployment to IPFS complete!"); 71 | console.log(); 72 | 73 | console.log(`Use the link${ipnsName && "s"} below to access your app:`); 74 | console.log(` IPFS: ${chalk.cyan(`${ipfsGateway}${cid.toString()}`)}`); 75 | if (ipnsName) { 76 | console.log(` IPNS: ${chalk.cyan(`${ipnsGateway}${ipnsName}`)}`); 77 | console.log(); 78 | console.log( 79 | "Each new deployment will have a unique IPFS hash while the IPNS name will always point at the most recent deployment.", 80 | ); 81 | console.log( 82 | "It is recommended that you share the IPNS link so that people always see the newest version of your app.", 83 | ); 84 | } 85 | console.log(); 86 | return true; 87 | }; 88 | 89 | deploy(); 90 | -------------------------------------------------------------------------------- /packages/react-app/scripts/s3.js: -------------------------------------------------------------------------------- 1 | const s3FolderUpload = require("s3-folder-upload"); 2 | const fs = require("fs"); 3 | 4 | const directoryName = "build"; 5 | 6 | const BUCKETNAME = "YOUR_BUCKET_NAME_HERE"; // <<---- SET YOUR BUCKET NAME AND CREATE aws.json ** see below vvvvvvvvvv 7 | 8 | if (!BUCKETNAME) { 9 | console.log("☢️ Enter a bucket name in packages/react-app/scripts/s3.js "); 10 | process.exit(1); 11 | } 12 | 13 | let credentials = {}; 14 | try { 15 | credentials = JSON.parse(fs.readFileSync("aws.json")); 16 | } catch (e) { 17 | console.log(e); 18 | console.log( 19 | '☢️ Create an aws.json credentials file in packages/react-app/ like { "accessKeyId": "xxx", "secretAccessKey": "xxx", "region": "xxx" } ', 20 | ); 21 | process.exit(1); 22 | } 23 | 24 | credentials.bucket = BUCKETNAME; 25 | 26 | // optional options to be passed as parameter to the method 27 | const options = { 28 | useFoldersForFileTypes: false, 29 | useIAMRoleCredentials: false, 30 | }; 31 | 32 | ///////////// 33 | ///////////// First, let's automatically create the bucket if it doesn't exist... 34 | ///////////// 35 | 36 | var AWS = require('aws-sdk'); 37 | // Load credentials and set Region from JSON file 38 | AWS.config.loadFromPath('./aws.json'); 39 | 40 | // Create S3 service object 41 | s3 = new AWS.S3({apiVersion: '2006-03-01'}); 42 | 43 | // Create params JSON for S3.createBucket 44 | var bucketParams = { 45 | Bucket : BUCKETNAME, 46 | ACL : 'public-read' 47 | }; 48 | 49 | // Create params JSON for S3.setBucketWebsite 50 | var staticHostParams = { 51 | Bucket: BUCKETNAME, 52 | WebsiteConfiguration: { 53 | ErrorDocument: { 54 | Key: 'index.html' 55 | }, 56 | IndexDocument: { 57 | Suffix: 'index.html' 58 | }, 59 | } 60 | }; 61 | 62 | // Call S3 to create the bucket 63 | s3.createBucket(bucketParams, function(err, data) { 64 | if (err) { 65 | console.log("Error", err); 66 | } else { 67 | console.log("Bucket URL is ", data.Location); 68 | // Set the new policy on the newly created bucket 69 | s3.putBucketWebsite(staticHostParams, function(err, data) { 70 | if (err) { 71 | // Display error message 72 | console.log("Error", err); 73 | } else { 74 | // Update the displayed policy for the selected bucket 75 | console.log("Success... UPLOADING!", data); 76 | 77 | /// 78 | /// After the bucket is created, we upload to it: 79 | /// 80 | s3FolderUpload(directoryName, credentials, options /* , invalidation */); 81 | } 82 | }); 83 | } 84 | }); 85 | -------------------------------------------------------------------------------- /packages/react-app/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("Compiling & Generating..."); 6 | exec("npx gulp less", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Themes..."); 14 | watch("./src/themes", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import App from "./App"; 4 | 5 | test("renders learn react link", () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Account.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd"; 2 | import React from "react"; 3 | import { useThemeSwitcher } from "react-css-theme-switcher"; 4 | import Address from "./Address"; 5 | import Balance from "./Balance"; 6 | import Wallet from "./Wallet"; 7 | 8 | /* 9 | ~ What it does? ~ 10 | 11 | Displays an Address, Balance, and Wallet as one Account component, 12 | also allows users to log in to existing accounts and log out 13 | 14 | ~ How can I use? ~ 15 | 16 | 27 | 28 | ~ Features ~ 29 | 30 | - Provide address={address} and get balance corresponding to the given address 31 | - Provide localProvider={localProvider} to access balance on local network 32 | - Provide userProvider={userProvider} to display a wallet 33 | - Provide mainnetProvider={mainnetProvider} and your address will be replaced by ENS name 34 | (ex. "0xa870" => "user.eth") 35 | - Provide price={price} of ether and get your balance converted to dollars 36 | - Provide web3Modal={web3Modal}, loadWeb3Modal={loadWeb3Modal}, logoutOfWeb3Modal={logoutOfWeb3Modal} 37 | to be able to log in/log out to/from existing accounts 38 | - Provide blockExplorer={blockExplorer}, click on address and get the link 39 | (ex. by default "https://etherscan.io/" or for xdai "https://blockscout.com/poa/xdai/") 40 | */ 41 | 42 | export default function Account({ 43 | address, 44 | userSigner, 45 | localProvider, 46 | mainnetProvider, 47 | price, 48 | minimized, 49 | web3Modal, 50 | loadWeb3Modal, 51 | logoutOfWeb3Modal, 52 | blockExplorer, 53 | }) { 54 | const modalButtons = []; 55 | if (web3Modal) { 56 | if (web3Modal.cachedProvider) { 57 | modalButtons.push( 58 | , 67 | ); 68 | } else { 69 | modalButtons.push( 70 | , 80 | ); 81 | } 82 | } 83 | 84 | const { currentTheme } = useThemeSwitcher(); 85 | 86 | const display = minimized ? ( 87 | "" 88 | ) : ( 89 | 90 | {address ? ( 91 |
92 | ) : ( 93 | "Connecting..." 94 | )} 95 | 96 | 104 | 105 | ); 106 | 107 | return ( 108 |
109 | {display} 110 | {modalButtons} 111 |
112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Address.jsx: -------------------------------------------------------------------------------- 1 | import { Skeleton, Typography } from "antd"; 2 | import React from "react"; 3 | import Blockies from "react-blockies"; 4 | import { useThemeSwitcher } from "react-css-theme-switcher"; 5 | import { useLookupAddress } from "eth-hooks/dapps/ens"; 6 | 7 | // changed value={address} to address={address} 8 | 9 | /* 10 | ~ What it does? ~ 11 | 12 | Displays an address with a blockie image and option to copy address 13 | 14 | ~ How can I use? ~ 15 | 16 |
22 | 23 | ~ Features ~ 24 | 25 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 26 | (ex. "0xa870" => "user.eth") 27 | - Provide blockExplorer={blockExplorer}, click on address and get the link 28 | (ex. by default "https://etherscan.io/" or for xdai "https://blockscout.com/poa/xdai/") 29 | - Provide fontSize={fontSize} to change the size of address text 30 | */ 31 | 32 | const { Text } = Typography; 33 | 34 | const blockExplorerLink = (address, blockExplorer) => 35 | `${blockExplorer || "https://etherscan.io/"}${"address/"}${address}`; 36 | 37 | export default function Address(props) { 38 | const address = props.value || props.address; 39 | 40 | const ens = useLookupAddress(props.ensProvider, address); 41 | 42 | const { currentTheme } = useThemeSwitcher(); 43 | 44 | if (!address) { 45 | return ( 46 | 47 | 48 | 49 | ); 50 | } 51 | 52 | let displayAddress = address.substr(0, 6); 53 | 54 | const ensSplit = ens && ens.split("."); 55 | const validEnsCheck = ensSplit && ensSplit[ensSplit.length - 1] === "eth"; 56 | 57 | if (validEnsCheck) { 58 | displayAddress = ens; 59 | } else if (props.size === "short") { 60 | displayAddress += "..." + address.substr(-4); 61 | } else if (props.size === "long") { 62 | displayAddress = address; 63 | } 64 | 65 | const etherscanLink = blockExplorerLink(address, props.blockExplorer); 66 | if (props.minimized) { 67 | return ( 68 | 69 | 75 | 76 | 77 | 78 | ); 79 | } 80 | 81 | let text; 82 | if (props.onChange) { 83 | text = ( 84 | 85 | 91 | {displayAddress} 92 | 93 | 94 | ); 95 | } else { 96 | text = ( 97 | 98 | 104 | {displayAddress} 105 | 106 | 107 | ); 108 | } 109 | 110 | return ( 111 | 112 | 113 | 114 | 115 | 116 | {text} 117 | 118 | 119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /packages/react-app/src/components/AddressInput.jsx: -------------------------------------------------------------------------------- 1 | import { CameraOutlined, QrcodeOutlined } from "@ant-design/icons"; 2 | import { Badge, Input } from "antd"; 3 | import { useLookupAddress } from "eth-hooks/dapps/ens"; 4 | import React, { useCallback, useState } from "react"; 5 | import QrReader from "react-qr-reader"; 6 | import Blockie from "./Blockie"; 7 | 8 | // probably we need to change value={toAddress} to address={toAddress} 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Displays an address input with QR scan option 14 | 15 | ~ How can I use? ~ 16 | 17 | 24 | 25 | ~ Features ~ 26 | 27 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 28 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 29 | - Provide placeholder="Enter address" value for the input 30 | - Value of the address input is stored in value={toAddress} 31 | - Control input change by onChange={setToAddress} 32 | or onChange={address => { setToAddress(address);}} 33 | */ 34 | 35 | export default function AddressInput(props) { 36 | const [value, setValue] = useState(props.value); 37 | const [scan, setScan] = useState(false); 38 | 39 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 40 | const ens = useLookupAddress(props.ensProvider, currentValue); 41 | 42 | const scannerButton = ( 43 |
{ 46 | setScan(!scan); 47 | }} 48 | > 49 | }> 50 | 51 | {" "} 52 | Scan 53 |
54 | ); 55 | 56 | const { ensProvider, onChange } = props; 57 | const updateAddress = useCallback( 58 | async newValue => { 59 | if (typeof newValue !== "undefined") { 60 | let address = newValue; 61 | if (address.indexOf(".eth") > 0 || address.indexOf(".xyz") > 0) { 62 | try { 63 | const possibleAddress = await ensProvider.resolveName(address); 64 | if (possibleAddress) { 65 | address = possibleAddress; 66 | } 67 | // eslint-disable-next-line no-empty 68 | } catch (e) {} 69 | } 70 | setValue(address); 71 | if (typeof onChange === "function") { 72 | onChange(address); 73 | } 74 | } 75 | }, 76 | [ensProvider, onChange], 77 | ); 78 | 79 | const scanner = scan ? ( 80 |
{ 89 | setScan(false); 90 | }} 91 | > 92 | { 96 | console.log("SCAN ERROR", e); 97 | setScan(false); 98 | }} 99 | onScan={newValue => { 100 | if (newValue) { 101 | console.log("SCAN VALUE", newValue); 102 | let possibleNewValue = newValue; 103 | if (possibleNewValue.indexOf("/") >= 0) { 104 | possibleNewValue = possibleNewValue.substr(possibleNewValue.lastIndexOf("0x")); 105 | console.log("CLEANED VALUE", possibleNewValue); 106 | } 107 | setScan(false); 108 | updateAddress(possibleNewValue); 109 | } 110 | }} 111 | style={{ width: "100%" }} 112 | /> 113 |
114 | ) : ( 115 | "" 116 | ); 117 | 118 | return ( 119 |
120 | {scanner} 121 | } 128 | value={ens || currentValue} 129 | addonAfter={scannerButton} 130 | onChange={e => { 131 | updateAddress(e.target.value); 132 | }} 133 | /> 134 |
135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Balance.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useBalance } from "eth-hooks"; 3 | 4 | const { utils } = require("ethers"); 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Displays a balance of given address in ether & dollar 10 | 11 | ~ How can I use? ~ 12 | 13 | 18 | 19 | ~ If you already have the balance as a bignumber ~ 20 | 24 | 25 | ~ Features ~ 26 | 27 | - Provide address={address} and get balance corresponding to given address 28 | - Provide provider={mainnetProvider} to access balance on mainnet or any other network (ex. localProvider) 29 | - Provide price={price} of ether and get your balance converted to dollars 30 | */ 31 | 32 | export default function Balance(props) { 33 | const [dollarMode, setDollarMode] = useState(true); 34 | 35 | // const [listening, setListening] = useState(false); 36 | 37 | const balance = useBalance(props.provider, props.address); 38 | 39 | let floatBalance = parseFloat("0.00"); 40 | 41 | let usingBalance = balance; 42 | 43 | if (typeof props.balance !== "undefined") { 44 | usingBalance = props.balance; 45 | } 46 | if (typeof props.value !== "undefined") { 47 | usingBalance = props.value; 48 | } 49 | 50 | if (usingBalance) { 51 | const etherBalance = utils.formatEther(usingBalance); 52 | parseFloat(etherBalance).toFixed(2); 53 | floatBalance = parseFloat(etherBalance); 54 | } 55 | 56 | let displayBalance = floatBalance.toFixed(4); 57 | 58 | const price = props.price || props.dollarMultiplier || 1; 59 | 60 | if (dollarMode) { 61 | displayBalance = "$" + (floatBalance * price).toFixed(2); 62 | } 63 | 64 | return ( 65 | { 73 | setDollarMode(!dollarMode); 74 | }} 75 | > 76 | {displayBalance} 77 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Blockie.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blockies from "react-blockies"; 3 | 4 | // provides a blockie image for the address using "react-blockies" library 5 | 6 | export default function Blockie(props) { 7 | if (!props.address || typeof props.address.toLowerCase !== "function") { 8 | return ; 9 | } 10 | // eslint-disable-next-line react/jsx-props-no-spreading 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-app/src/components/BytesStringInput.jsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | const { utils, constants } = require("ethers"); 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Displays input field with options to convert between STRING and BYTES32 10 | 11 | ~ How can I use? ~ 12 | 13 | { 18 | setValue(value); 19 | }} 20 | /> 21 | 22 | ~ Features ~ 23 | 24 | - Provide value={value} to specify initial string 25 | - Provide placeholder="Enter value..." value for the input 26 | - Control input change by onChange={value => { setValue(value);}} 27 | 28 | */ 29 | 30 | export default function BytesStringInput(props) { 31 | const [mode, setMode] = useState("STRING"); 32 | const [display, setDisplay] = useState(); 33 | const [value, setValue] = useState(constants.HashZero); 34 | 35 | // current value is the value in bytes32 36 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 37 | 38 | const option = title => { 39 | return ( 40 |
{ 43 | if (mode === "STRING") { 44 | setMode("BYTES32"); 45 | if (!utils.isHexString(currentValue)) { 46 | /* in case user enters invalid bytes32 number, 47 | it considers it as string and converts to bytes32 */ 48 | const changedValue = utils.formatBytes32String(currentValue); 49 | setDisplay(changedValue); 50 | } else { 51 | setDisplay(currentValue); 52 | } 53 | } else { 54 | setMode("STRING"); 55 | if (currentValue && utils.isHexString(currentValue)) { 56 | setDisplay(utils.parseBytes32String(currentValue)); 57 | } else { 58 | setDisplay(currentValue); 59 | } 60 | } 61 | }} 62 | > 63 | {title} 64 |
65 | ); 66 | }; 67 | 68 | let addonAfter; 69 | if (mode === "STRING") { 70 | addonAfter = option("STRING 🔀"); 71 | } else { 72 | addonAfter = option("BYTES32 🔀"); 73 | } 74 | 75 | useEffect(() => { 76 | if (!currentValue) { 77 | setDisplay(""); 78 | } 79 | }, [currentValue]); 80 | 81 | return ( 82 | { 88 | const newValue = e.target.value; 89 | if (mode === "STRING") { 90 | // const ethValue = parseFloat(newValue) / props.price; 91 | // setValue(ethValue); 92 | if (typeof props.onChange === "function") { 93 | props.onChange(utils.formatBytes32String(newValue)); 94 | } 95 | setValue(utils.formatBytes32String(newValue)); 96 | setDisplay(newValue); 97 | } else { 98 | if (typeof props.onChange === "function") { 99 | props.onChange(newValue); 100 | } 101 | setValue(newValue); 102 | setDisplay(newValue); 103 | } 104 | }} 105 | /> 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/DisplayVariable.jsx: -------------------------------------------------------------------------------- 1 | import { Col, Divider, Row } from "antd"; 2 | import React, { useCallback, useEffect, useState } from "react"; 3 | import tryToDisplay from "./utils"; 4 | 5 | const DisplayVariable = ({ contractFunction, functionInfo, refreshRequired, triggerRefresh }) => { 6 | const [variable, setVariable] = useState(""); 7 | 8 | const refresh = useCallback(async () => { 9 | try { 10 | const funcResponse = await contractFunction(); 11 | setVariable(funcResponse); 12 | triggerRefresh(false); 13 | } catch (e) { 14 | console.log(e); 15 | } 16 | }, [setVariable, contractFunction, triggerRefresh]); 17 | 18 | useEffect(() => { 19 | refresh(); 20 | }, [refresh, refreshRequired, contractFunction]); 21 | 22 | return ( 23 |
24 | 25 | 34 | {functionInfo.name} 35 | 36 | 37 |

{tryToDisplay(variable)}

38 | 39 | 40 |

41 | 42 | 🔄 43 | 44 |

45 | 46 |
47 | 48 |
49 | ); 50 | }; 51 | 52 | export default DisplayVariable; 53 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/FunctionForm.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Col, Divider, Input, Row, Tooltip } from "antd"; 2 | import React, { useState } from "react"; 3 | import Blockies from "react-blockies"; 4 | import { Transactor } from "../../helpers"; 5 | import tryToDisplay from "./utils"; 6 | 7 | const { utils, BigNumber } = require("ethers"); 8 | 9 | const getFunctionInputKey = (functionInfo, input, inputIndex) => { 10 | const name = input?.name ? input.name : "input_" + inputIndex + "_"; 11 | return functionInfo.name + "_" + name + "_" + input.type; 12 | }; 13 | 14 | export default function FunctionForm({ contractFunction, functionInfo, provider, gasPrice, triggerRefresh }) { 15 | const [form, setForm] = useState({}); 16 | const [txValue, setTxValue] = useState(); 17 | const [returnValue, setReturnValue] = useState(); 18 | 19 | const tx = Transactor(provider, gasPrice); 20 | 21 | const inputs = functionInfo.inputs.map((input, inputIndex) => { 22 | const key = getFunctionInputKey(functionInfo, input, inputIndex); 23 | 24 | let buttons = ""; 25 | if (input.type === "bytes32") { 26 | buttons = ( 27 | 28 |
{ 32 | if (utils.isHexString(form[key])) { 33 | const formUpdate = { ...form }; 34 | formUpdate[key] = utils.parseBytes32String(form[key]); 35 | setForm(formUpdate); 36 | } else { 37 | const formUpdate = { ...form }; 38 | formUpdate[key] = utils.formatBytes32String(form[key]); 39 | setForm(formUpdate); 40 | } 41 | }} 42 | > 43 | #️⃣ 44 |
45 |
46 | ); 47 | } else if (input.type === "bytes") { 48 | buttons = ( 49 | 50 |
{ 54 | if (utils.isHexString(form[key])) { 55 | const formUpdate = { ...form }; 56 | formUpdate[key] = utils.toUtf8String(form[key]); 57 | setForm(formUpdate); 58 | } else { 59 | const formUpdate = { ...form }; 60 | formUpdate[key] = utils.hexlify(utils.toUtf8Bytes(form[key])); 61 | setForm(formUpdate); 62 | } 63 | }} 64 | > 65 | #️⃣ 66 |
67 |
68 | ); 69 | } else if (input.type === "uint256") { 70 | buttons = ( 71 | 72 |
{ 76 | const formUpdate = { ...form }; 77 | formUpdate[key] = utils.parseEther(form[key]); 78 | setForm(formUpdate); 79 | }} 80 | > 81 | ✴️ 82 |
83 |
84 | ); 85 | } else if (input.type === "address") { 86 | const possibleAddress = form[key] && form[key].toLowerCase && form[key].toLowerCase().trim(); 87 | if (possibleAddress && possibleAddress.length === 42) { 88 | buttons = ( 89 | 90 | 91 | 92 | ); 93 | } 94 | } 95 | 96 | return ( 97 |
98 | { 105 | const formUpdate = { ...form }; 106 | formUpdate[event.target.name] = event.target.value; 107 | setForm(formUpdate); 108 | }} 109 | suffix={buttons} 110 | /> 111 |
112 | ); 113 | }); 114 | 115 | const txValueInput = ( 116 |
117 | setTxValue(e.target.value)} 120 | value={txValue} 121 | addonAfter={ 122 |
123 | 124 | 125 | 126 |
{ 130 | const floatValue = parseFloat(txValue); 131 | if (floatValue) setTxValue("" + floatValue * 10 ** 18); 132 | }} 133 | > 134 | ✳️ 135 |
136 |
137 | 138 | 139 | 140 |
{ 144 | setTxValue(BigNumber.from(txValue).toHexString()); 145 | }} 146 | > 147 | #️⃣ 148 |
149 |
150 | 151 |
152 |
153 | } 154 | /> 155 |
156 | ); 157 | 158 | if (functionInfo.payable) { 159 | inputs.push(txValueInput); 160 | } 161 | 162 | const buttonIcon = 163 | functionInfo.type === "call" ? ( 164 | 165 | ) : ( 166 | 167 | ); 168 | inputs.push( 169 |
170 | setReturnValue(e.target.value)} 172 | defaultValue="" 173 | bordered={false} 174 | disabled 175 | value={returnValue} 176 | suffix={ 177 |
{ 181 | const args = functionInfo.inputs.map((input, inputIndex) => { 182 | const key = getFunctionInputKey(functionInfo, input, inputIndex); 183 | let value = form[key]; 184 | if (input.baseType === "array") { 185 | value = JSON.parse(value); 186 | } else if (input.type === "bool") { 187 | if (value === "true" || value === "1" || value === "0x1" || value === "0x01" || value === "0x0001") { 188 | value = 1; 189 | } else { 190 | value = 0; 191 | } 192 | } 193 | return value; 194 | }); 195 | 196 | let result; 197 | if (functionInfo.stateMutability === "view" || functionInfo.stateMutability === "pure") { 198 | try { 199 | const returned = await contractFunction(...args); 200 | result = tryToDisplay(returned); 201 | } catch (err) { 202 | console.error(err); 203 | } 204 | } else { 205 | const overrides = {}; 206 | if (txValue) { 207 | overrides.value = txValue; // ethers.utils.parseEther() 208 | } 209 | if (gasPrice) { 210 | overrides.gasPrice = gasPrice; 211 | } 212 | // Uncomment this if you want to skip the gas estimation for each transaction 213 | // overrides.gasLimit = hexlify(1200000); 214 | 215 | // console.log("Running with extras",extras) 216 | const returned = await tx(contractFunction(...args, overrides)); 217 | result = tryToDisplay(returned); 218 | } 219 | 220 | console.log("SETTING RESULT:", result); 221 | setReturnValue(result); 222 | triggerRefresh(true); 223 | }} 224 | > 225 | {buttonIcon} 226 |
227 | } 228 | /> 229 |
, 230 | ); 231 | 232 | return ( 233 |
234 | 235 | 244 | {functionInfo.name} 245 | 246 | {inputs} 247 | 248 | 249 |
250 | ); 251 | } 252 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from "antd"; 2 | import React, { useMemo, useState } from "react"; 3 | import { useContractExistsAtAddress, useContractLoader } from "eth-hooks"; 4 | import Account from "../Account"; 5 | import DisplayVariable from "./DisplayVariable"; 6 | import FunctionForm from "./FunctionForm"; 7 | 8 | const noContractDisplay = ( 9 |
10 | Loading...{" "} 11 |
12 | You need to run{" "} 13 | 17 | yarn run chain 18 | {" "} 19 | and{" "} 20 | 24 | yarn run deploy 25 | {" "} 26 | to see your contract here. 27 |
28 |
29 | 30 | ☢️ 31 | 32 | Warning: You might need to run 33 | 37 | yarn run deploy 38 | {" "} 39 | again after the frontend comes up! 40 |
41 |
42 | ); 43 | 44 | const isQueryable = fn => (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; 45 | 46 | export default function Contract({ 47 | customContract, 48 | account, 49 | gasPrice, 50 | signer, 51 | provider, 52 | name, 53 | show, 54 | price, 55 | blockExplorer, 56 | chainId, 57 | contractConfig, 58 | }) { 59 | const contracts = useContractLoader(provider, contractConfig, chainId); 60 | let contract; 61 | if (!customContract) { 62 | contract = contracts ? contracts[name] : ""; 63 | } else { 64 | contract = customContract; 65 | } 66 | 67 | const address = contract ? contract.address : ""; 68 | const contractIsDeployed = useContractExistsAtAddress(provider, address); 69 | 70 | const displayedContractFunctions = useMemo(() => { 71 | const results = contract 72 | ? Object.entries(contract.interface.functions).filter( 73 | fn => fn[1]["type"] === "function" && !(show && show.indexOf(fn[1]["name"]) < 0), 74 | ) 75 | : []; 76 | return results; 77 | }, [contract, show]); 78 | 79 | const [refreshRequired, triggerRefresh] = useState(false); 80 | const contractDisplay = displayedContractFunctions.map(contractFuncInfo => { 81 | const contractFunc = 82 | contractFuncInfo[1].stateMutability === "view" || contractFuncInfo[1].stateMutability === "pure" 83 | ? contract[contractFuncInfo[0]] 84 | : contract.connect(signer)[contractFuncInfo[0]]; 85 | 86 | if (typeof contractFunc === "function") { 87 | if (isQueryable(contractFuncInfo[1])) { 88 | // If there are no inputs, just display return value 89 | return ( 90 | 97 | ); 98 | } 99 | 100 | // If there are inputs, display a form to allow users to provide these 101 | return ( 102 | 110 | ); 111 | } 112 | return null; 113 | }); 114 | 115 | return ( 116 |
117 | 120 | {name} 121 |
122 | 130 | {account} 131 |
132 |
133 | } 134 | size="large" 135 | style={{ marginTop: 25, width: "100%" }} 136 | loading={contractDisplay && contractDisplay.length <= 0} 137 | > 138 | {contractIsDeployed ? contractDisplay : noContractDisplay} 139 | 140 | 141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Address } from ".."; 3 | 4 | const { utils } = require("ethers"); 5 | 6 | const tryToDisplay = thing => { 7 | if (thing && thing.toNumber) { 8 | try { 9 | return thing.toNumber(); 10 | } catch (e) { 11 | return "Ξ" + utils.formatUnits(thing, "ether"); 12 | } 13 | } 14 | if (thing && thing.indexOf && thing.indexOf("0x") === 0 && thing.length === 42) { 15 | return
; 16 | } 17 | return JSON.stringify(thing); 18 | }; 19 | 20 | export default tryToDisplay; 21 | -------------------------------------------------------------------------------- /packages/react-app/src/components/EtherInput.jsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | // small change in useEffect, display currentValue if it's provided by user 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Displays input field for ETH/USD amount, with an option to convert between ETH and USD 10 | 11 | ~ How can I use? ~ 12 | 13 | { 19 | setAmount(value); 20 | }} 21 | /> 22 | 23 | ~ Features ~ 24 | 25 | - Provide price={price} of ether and easily convert between USD and ETH 26 | - Provide value={value} to specify initial amount of ether 27 | - Provide placeholder="Enter amount" value for the input 28 | - Control input change by onChange={value => { setAmount(value);}} 29 | */ 30 | 31 | export default function EtherInput(props) { 32 | const [mode, setMode] = useState(props.price ? "USD" : "ETH"); 33 | const [display, setDisplay] = useState(); 34 | const [value, setValue] = useState(); 35 | 36 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 37 | 38 | const option = title => { 39 | if (!props.price) return ""; 40 | return ( 41 |
{ 44 | if (mode === "USD") { 45 | setMode("ETH"); 46 | setDisplay(currentValue); 47 | } else { 48 | setMode("USD"); 49 | if (currentValue) { 50 | const usdValue = "" + (parseFloat(currentValue) * props.price).toFixed(2); 51 | setDisplay(usdValue); 52 | } else { 53 | setDisplay(currentValue); 54 | } 55 | } 56 | }} 57 | > 58 | {title} 59 |
60 | ); 61 | }; 62 | 63 | let prefix; 64 | let addonAfter; 65 | if (mode === "USD") { 66 | prefix = "$"; 67 | addonAfter = option("USD 🔀"); 68 | } else { 69 | prefix = "Ξ"; 70 | addonAfter = option("ETH 🔀"); 71 | } 72 | 73 | useEffect(() => { 74 | if (!currentValue) { 75 | setDisplay(""); 76 | } 77 | }, [currentValue]); 78 | 79 | return ( 80 | { 87 | const newValue = e.target.value; 88 | if (mode === "USD") { 89 | const possibleNewValue = parseFloat(newValue); 90 | if (possibleNewValue) { 91 | const ethValue = possibleNewValue / props.price; 92 | setValue(ethValue); 93 | if (typeof props.onChange === "function") { 94 | props.onChange(ethValue); 95 | } 96 | setDisplay(newValue); 97 | } else { 98 | setDisplay(newValue); 99 | } 100 | } else { 101 | setValue(newValue); 102 | if (typeof props.onChange === "function") { 103 | props.onChange(newValue); 104 | } 105 | setDisplay(newValue); 106 | } 107 | }} 108 | /> 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Events.jsx: -------------------------------------------------------------------------------- 1 | import { List } from "antd"; 2 | import { useEventListener } from "eth-hooks/events/useEventListener"; 3 | import { Address } from "../components"; 4 | 5 | /* 6 | ~ What it does? ~ 7 | 8 | Displays a lists of events 9 | 10 | ~ How can I use? ~ 11 | 12 | 20 | */ 21 | 22 | export default function Events({ contracts, contractName, eventName, localProvider, mainnetProvider, startBlock }) { 23 | // 📟 Listen for broadcast events 24 | const events = useEventListener(contracts, contractName, eventName, localProvider, startBlock); 25 | 26 | return ( 27 |
28 |

Events:

29 | { 33 | return ( 34 | 35 |
36 | {item.args[1]} 37 | 38 | ); 39 | }} 40 | /> 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Faucet.jsx: -------------------------------------------------------------------------------- 1 | import { SendOutlined } from "@ant-design/icons"; 2 | import { Button, Input, Tooltip } from "antd"; 3 | // import { useLookupAddress } from "eth-hooks/dapps/ens"; 4 | import React, { useCallback, useState, useEffect } from "react"; 5 | import Blockies from "react-blockies"; 6 | import { Transactor } from "../helpers"; 7 | import Wallet from "./Wallet"; 8 | 9 | const { utils } = require("ethers"); 10 | 11 | // improved a bit by converting address to ens if it exists 12 | // added option to directly input ens name 13 | // added placeholder option 14 | 15 | /* 16 | ~ What it does? ~ 17 | 18 | Displays a local faucet to send ETH to given address, also wallet is provided 19 | 20 | ~ How can I use? ~ 21 | 22 | 28 | 29 | ~ Features ~ 30 | 31 | - Provide price={price} of ether and convert between USD and ETH in a wallet 32 | - Provide localProvider={localProvider} to be able to send ETH to given address 33 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 34 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 35 | works both in input field & wallet 36 | - Provide placeholder="Send local faucet" value for the input 37 | */ 38 | 39 | export default function Faucet(props) { 40 | const [address, setAddress] = useState(); 41 | const [faucetAddress, setFaucetAddress] = useState(); 42 | 43 | const { price, placeholder, localProvider, ensProvider, onChange } = props; 44 | 45 | useEffect(() => { 46 | const getFaucetAddress = async () => { 47 | if (localProvider) { 48 | const _faucetAddress = await localProvider.listAccounts(); 49 | setFaucetAddress(_faucetAddress[0]); 50 | //console.log(_faucetAddress); 51 | } 52 | }; 53 | getFaucetAddress(); 54 | }, [localProvider]); 55 | 56 | let blockie; 57 | if (address && typeof address.toLowerCase === "function") { 58 | blockie = ; 59 | } else { 60 | blockie =
; 61 | } 62 | 63 | // const ens = useLookupAddress(ensProvider, address); 64 | 65 | const updateAddress = useCallback( 66 | async newValue => { 67 | if (typeof newValue !== "undefined" && utils.isAddress(newValue)) { 68 | let newAddress = newValue; 69 | // if (newAddress.indexOf(".eth") > 0 || newAddress.indexOf(".xyz") > 0) { 70 | // try { 71 | // const possibleAddress = await ensProvider.resolveName(newAddress); 72 | // if (possibleAddress) { 73 | // newAddress = possibleAddress; 74 | // } 75 | // // eslint-disable-next-line no-empty 76 | // } catch (e) { } 77 | // } 78 | setAddress(newAddress); 79 | } 80 | }, 81 | [ensProvider, onChange], 82 | ); 83 | 84 | const tx = Transactor(localProvider); 85 | 86 | return ( 87 | 88 | { 95 | // setAddress(e.target.value); 96 | updateAddress(e.target.value); 97 | }} 98 | suffix={ 99 | 100 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from "antd"; 2 | import React from "react"; 3 | 4 | // displays a page header 5 | 6 | export default function Header() { 7 | return ( 8 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-app/src/components/L2Bridge.jsx: -------------------------------------------------------------------------------- 1 | import { utils, ethers } from "ethers"; 2 | import { Button, Input, Form, Select, InputNumber, Table, Radio } from "antd"; 3 | import React, { useState, useEffect } from "react"; 4 | import { useContractLoader, useOnBlock } from "eth-hooks"; 5 | import { NETWORKS } from "../constants"; 6 | import { Transactor } from "../helpers"; 7 | 8 | /* 9 | This is a component for bridging between L1 & L2 10 | Currently it supports Testnet deposits for Arbitrum & Optimism 11 | 12 | __ _______ _____ 13 | \ \ / /_ _| __ \ 14 | \ \ /\ / / | | | |__) | 15 | \ \/ \/ / | | | ___/ 16 | \ /\ / _| |_| | 17 | \/ \/ |_____|_| 18 | 19 | 20 | */ 21 | 22 | export default function L2ArbitrumBridge({ address, userSigner }) { 23 | const [L1EthBalance, setL1EthBalance] = useState("..."); 24 | const [L2EthBalance, setL2EthBalance] = useState("..."); 25 | const [L1Provider, setL1Provider] = useState(""); 26 | const [L2Provider, setL2Provider] = useState(""); 27 | const [rollup, setRollup] = useState("arbitrum"); 28 | const [environment, setEnvironment] = useState("test"); 29 | 30 | const rollupConfig = { 31 | arbitrum: { 32 | test: { L1: NETWORKS.rinkeby, L2: NETWORKS.rinkebyArbitrum }, 33 | main: { L1: NETWORKS.mainnet, L2: NETWORKS.arbitrum }, 34 | local: { L1: NETWORKS.localArbitrumL1, L2: NETWORKS.localArbitrum }, 35 | }, 36 | optimism: { 37 | test: { L1: NETWORKS.kovan, L2: NETWORKS.kovanOptimism }, 38 | local: { L1: NETWORKS.localOptimismL1, L2: NETWORKS.localOptimism }, 39 | }, 40 | }; 41 | 42 | const activeConfig = rollupConfig[rollup][environment]; 43 | 44 | const selectedChainId = 45 | userSigner && userSigner.provider && userSigner.provider._network && userSigner.provider._network.chainId; 46 | 47 | const tx = Transactor(userSigner); 48 | 49 | useEffect(() => { 50 | async function setProviders() { 51 | const L1 = activeConfig.L1; 52 | const L2 = activeConfig.L2; 53 | setL1Provider(new ethers.providers.StaticJsonRpcProvider(L1.rpcUrl)); 54 | setL2Provider(new ethers.providers.StaticJsonRpcProvider(L2.rpcUrl)); 55 | setL1EthBalance("..."); 56 | setL2EthBalance("..."); 57 | } 58 | setProviders(); 59 | }, [rollup]); 60 | 61 | const contracts = useContractLoader(userSigner, { externalContracts: L1BridgeMetadata, hardhatContracts: {} }); 62 | 63 | useOnBlock(L1Provider, async () => { 64 | console.log(`⛓ A new mainnet block is here: ${L1Provider._lastBlockNumber}`); 65 | const yourL1Balance = await L1Provider.getBalance(address); 66 | setL1EthBalance(yourL1Balance ? ethers.utils.formatEther(yourL1Balance) : "..."); 67 | const yourL2Balance = await L2Provider.getBalance(address); 68 | setL2EthBalance(yourL2Balance ? ethers.utils.formatEther(yourL2Balance) : "..."); 69 | }); 70 | 71 | const { Option } = Select; 72 | const formItemLayout = { 73 | labelCol: { 74 | xs: { span: 24 }, 75 | sm: { span: 8 }, 76 | }, 77 | wrapperCol: { 78 | xs: { span: 24 }, 79 | sm: { span: 12 }, 80 | }, 81 | }; 82 | const tailFormItemLayout = { 83 | wrapperCol: { 84 | xs: { 85 | span: 24, 86 | offset: 0, 87 | }, 88 | sm: { 89 | span: 12, 90 | offset: 8, 91 | }, 92 | }, 93 | }; 94 | 95 | const columns = [ 96 | { 97 | title: "", 98 | dataIndex: "token", 99 | key: "token", 100 | align: "center", 101 | }, 102 | { 103 | title: `${activeConfig.L1.name} L1 Balance`, 104 | dataIndex: "l1", 105 | key: "l1", 106 | align: "center", 107 | }, 108 | { 109 | title: `${activeConfig.L1.name} ${rollup} Balance`, 110 | dataIndex: "l2", 111 | key: "l2", 112 | align: "center", 113 | }, 114 | ]; 115 | 116 | const data = [ 117 | { 118 | key: "1", 119 | token: "ETH", 120 | l1: "Ξ" + L1EthBalance, 121 | l2: "Ξ" + L2EthBalance, 122 | }, 123 | ]; 124 | 125 | const [form] = Form.useForm(); 126 | 127 | const onAssetChange = value => { 128 | console.log(value); 129 | }; 130 | 131 | async function onFinish(values) { 132 | console.log(contracts); 133 | console.log(values.amount.toString()); 134 | console.log(rollup); 135 | let newTx; 136 | try { 137 | if (rollup === "arbitrum") { 138 | newTx = await tx( 139 | contracts.Inbox.depositEth(1_300_000, { 140 | value: utils.parseEther(values.amount.toString()), 141 | gasLimit: 300000, 142 | }), 143 | ); 144 | } else if (rollup === "optimism") { 145 | newTx = await tx( 146 | contracts.OVM_L1StandardBridge.depositETH(1_300_000, "0x", { 147 | value: utils.parseEther(values.amount.toString()), 148 | }), 149 | ); 150 | } 151 | await newTx.wait(); 152 | console.log("woop!"); 153 | } catch (e) { 154 | console.log(e); 155 | console.log("something went wrong!"); 156 | } 157 | } 158 | 159 | const onReset = () => { 160 | form.resetFields(); 161 | }; 162 | 163 | const wrongNetwork = selectedChainId !== activeConfig.L1.chainId; 164 | 165 | return ( 166 |
167 |
168 |

Welcome to the L2 Deposit Bridge!

169 | { 172 | setRollup(e.target.value); 173 | }} 174 | style={{ marginBottom: 10 }} 175 | > 176 | Arbitrum 177 | Optimism 178 | 179 | 180 | 181 | 182 | 189 | 198 | 204 | 205 | 206 | 207 | 208 | 217 | 218 | 219 | 220 | 223 | 224 | 225 | 226 | 227 | ); 228 | } 229 | 230 | // Arbitrum Inbox https://rinkeby.etherscan.io/address/0xa157dc79ca26d69c3b1282d03ec42bdee2790a8f#code 231 | const ArbitrumInboxABI = [ 232 | { 233 | anonymous: false, 234 | inputs: [ 235 | { indexed: true, internalType: "uint256", name: "messageNum", type: "uint256" }, 236 | { indexed: false, internalType: "bytes", name: "data", type: "bytes" }, 237 | ], 238 | name: "InboxMessageDelivered", 239 | type: "event", 240 | }, 241 | { 242 | anonymous: false, 243 | inputs: [{ indexed: true, internalType: "uint256", name: "messageNum", type: "uint256" }], 244 | name: "InboxMessageDeliveredFromOrigin", 245 | type: "event", 246 | }, 247 | { 248 | anonymous: false, 249 | inputs: [{ indexed: false, internalType: "address", name: "newSource", type: "address" }], 250 | name: "WhitelistSourceUpdated", 251 | type: "event", 252 | }, 253 | { 254 | inputs: [], 255 | name: "bridge", 256 | outputs: [{ internalType: "contract IBridge", name: "", type: "address" }], 257 | stateMutability: "view", 258 | type: "function", 259 | }, 260 | { 261 | inputs: [ 262 | { internalType: "address", name: "destAddr", type: "address" }, 263 | { internalType: "uint256", name: "l2CallValue", type: "uint256" }, 264 | { internalType: "uint256", name: "maxSubmissionCost", type: "uint256" }, 265 | { internalType: "address", name: "excessFeeRefundAddress", type: "address" }, 266 | { internalType: "address", name: "callValueRefundAddress", type: "address" }, 267 | { internalType: "uint256", name: "maxGas", type: "uint256" }, 268 | { internalType: "uint256", name: "gasPriceBid", type: "uint256" }, 269 | { internalType: "bytes", name: "data", type: "bytes" }, 270 | ], 271 | name: "createRetryableTicket", 272 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 273 | stateMutability: "payable", 274 | type: "function", 275 | }, 276 | { 277 | inputs: [{ internalType: "uint256", name: "maxSubmissionCost", type: "uint256" }], 278 | name: "depositEth", 279 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 280 | stateMutability: "payable", 281 | type: "function", 282 | }, 283 | { 284 | inputs: [ 285 | { internalType: "contract IBridge", name: "_bridge", type: "address" }, 286 | { internalType: "address", name: "_whitelist", type: "address" }, 287 | ], 288 | name: "initialize", 289 | outputs: [], 290 | stateMutability: "nonpayable", 291 | type: "function", 292 | }, 293 | { 294 | inputs: [], 295 | name: "isMaster", 296 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 297 | stateMutability: "view", 298 | type: "function", 299 | }, 300 | { 301 | inputs: [ 302 | { internalType: "uint256", name: "maxGas", type: "uint256" }, 303 | { internalType: "uint256", name: "gasPriceBid", type: "uint256" }, 304 | { internalType: "address", name: "destAddr", type: "address" }, 305 | { internalType: "uint256", name: "amount", type: "uint256" }, 306 | { internalType: "bytes", name: "data", type: "bytes" }, 307 | ], 308 | name: "sendContractTransaction", 309 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 310 | stateMutability: "nonpayable", 311 | type: "function", 312 | }, 313 | { 314 | inputs: [ 315 | { internalType: "uint256", name: "maxGas", type: "uint256" }, 316 | { internalType: "uint256", name: "gasPriceBid", type: "uint256" }, 317 | { internalType: "address", name: "destAddr", type: "address" }, 318 | { internalType: "bytes", name: "data", type: "bytes" }, 319 | ], 320 | name: "sendL1FundedContractTransaction", 321 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 322 | stateMutability: "payable", 323 | type: "function", 324 | }, 325 | { 326 | inputs: [ 327 | { internalType: "uint256", name: "maxGas", type: "uint256" }, 328 | { internalType: "uint256", name: "gasPriceBid", type: "uint256" }, 329 | { internalType: "uint256", name: "nonce", type: "uint256" }, 330 | { internalType: "address", name: "destAddr", type: "address" }, 331 | { internalType: "bytes", name: "data", type: "bytes" }, 332 | ], 333 | name: "sendL1FundedUnsignedTransaction", 334 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 335 | stateMutability: "payable", 336 | type: "function", 337 | }, 338 | { 339 | inputs: [{ internalType: "bytes", name: "messageData", type: "bytes" }], 340 | name: "sendL2Message", 341 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 342 | stateMutability: "nonpayable", 343 | type: "function", 344 | }, 345 | { 346 | inputs: [{ internalType: "bytes", name: "messageData", type: "bytes" }], 347 | name: "sendL2MessageFromOrigin", 348 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 349 | stateMutability: "nonpayable", 350 | type: "function", 351 | }, 352 | { 353 | inputs: [ 354 | { internalType: "uint256", name: "maxGas", type: "uint256" }, 355 | { internalType: "uint256", name: "gasPriceBid", type: "uint256" }, 356 | { internalType: "uint256", name: "nonce", type: "uint256" }, 357 | { internalType: "address", name: "destAddr", type: "address" }, 358 | { internalType: "uint256", name: "amount", type: "uint256" }, 359 | { internalType: "bytes", name: "data", type: "bytes" }, 360 | ], 361 | name: "sendUnsignedTransaction", 362 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 363 | stateMutability: "nonpayable", 364 | type: "function", 365 | }, 366 | { 367 | inputs: [{ internalType: "address", name: "newSource", type: "address" }], 368 | name: "updateWhitelistSource", 369 | outputs: [], 370 | stateMutability: "nonpayable", 371 | type: "function", 372 | }, 373 | { 374 | inputs: [], 375 | name: "whitelist", 376 | outputs: [{ internalType: "address", name: "", type: "address" }], 377 | stateMutability: "view", 378 | type: "function", 379 | }, 380 | ]; 381 | 382 | // https://github.com/ethereum-optimism/optimism/blob/2bd49730fa8d2c10953873f0ccc792198a49d5c9/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/tokens/iOVM_L1StandardBridge.sol 383 | const OVM_L1StandardBridgeABI = [ 384 | "function depositETH(uint32 _l2Gas,bytes calldata _data) external payable", 385 | "function depositETHTo(address _to,uint32 _l2Gas,bytes calldata _data) external payable", 386 | "function finalizeETHWithdrawal (address _from,address _to,uint _amount,bytes calldata _data) external", 387 | ]; 388 | 389 | const L1BridgeMetadata = { 390 | // Arbitrium Contract's 391 | 44010: { 392 | contracts: { 393 | Inbox: { 394 | address: "0xA4d796Ad4e79aFB703340a596AEd88f8a5924183", 395 | abi: ArbitrumInboxABI, 396 | }, 397 | }, 398 | }, 399 | 4: { 400 | contracts: { 401 | Inbox: { 402 | address: "0x578bade599406a8fe3d24fd7f7211c0911f5b29e", 403 | abi: ArbitrumInboxABI, 404 | }, 405 | }, 406 | }, 407 | // Optimism Contract's 408 | 31337: { 409 | contracts: { 410 | OVM_L1StandardBridge: { 411 | address: "0x998abeb3E57409262aE5b751f60747921B33613E", 412 | abi: OVM_L1StandardBridgeABI, 413 | }, 414 | }, 415 | }, 416 | 42: { 417 | contracts: { 418 | OVM_L1StandardBridge: { 419 | address: "0x22F24361D548e5FaAfb36d1437839f080363982B", 420 | abi: OVM_L1StandardBridgeABI, 421 | }, 422 | }, 423 | }, 424 | }; 425 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Provider.jsx: -------------------------------------------------------------------------------- 1 | import { Badge, Button } from "antd"; 2 | import { useBlockNumber, usePoller } from "eth-hooks"; 3 | import React, { useState } from "react"; 4 | // import { WalletOutlined } from '@ant-design/icons'; 5 | import Address from "./Address"; 6 | 7 | export default function Provider(props) { 8 | const [showMore, setShowMore] = useState(false); 9 | const [status, setStatus] = useState("processing"); 10 | const [network, setNetwork] = useState(); 11 | const [signer, setSigner] = useState(); 12 | const [address, setAddress] = useState(); 13 | 14 | const blockNumber = useBlockNumber(props.provider); 15 | 16 | usePoller(async () => { 17 | if (props.provider && typeof props.provider.getNetwork === "function") { 18 | try { 19 | const newNetwork = await props.provider.getNetwork(); 20 | setNetwork(newNetwork); 21 | if (newNetwork.chainId > 0) { 22 | setStatus("success"); 23 | } else { 24 | setStatus("warning"); 25 | } 26 | } catch (e) { 27 | console.log(e); 28 | setStatus("processing"); 29 | } 30 | try { 31 | const newSigner = await props.provider.getSigner(); 32 | setSigner(newSigner); 33 | const newAddress = await newSigner.getAddress(); 34 | setAddress(newAddress); 35 | // eslint-disable-next-line no-empty 36 | } catch (e) {} 37 | } 38 | }, 1377); 39 | 40 | if ( 41 | typeof props.provider === "undefined" || 42 | typeof props.provider.getNetwork !== "function" || 43 | !network || 44 | !network.chainId 45 | ) { 46 | return ( 47 | 56 | ); 57 | } 58 | 59 | let showExtra = ""; 60 | if (showMore) { 61 | showExtra = ( 62 | 63 | 64 | id: 65 | {network ? network.chainId : ""} 66 | 67 | 68 | name: 69 | {network ? network.name : ""} 70 | 71 | 72 | ); 73 | } 74 | 75 | let showWallet = ""; 76 | if (typeof signer !== "undefined" && address) { 77 | showWallet = ( 78 | 79 | 80 |
81 | 82 | 83 | ); 84 | } 85 | 86 | return ( 87 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Ramp.jsx: -------------------------------------------------------------------------------- 1 | import { DollarCircleOutlined } from "@ant-design/icons"; 2 | import { RampInstantSDK } from "@ramp-network/ramp-instant-sdk"; 3 | import { Button, Divider, Modal } from "antd"; 4 | import React, { useState } from "react"; 5 | 6 | // added display of 0 if price={price} is not provided 7 | 8 | /* 9 | ~ What it does? ~ 10 | 11 | Displays current ETH price and gives options to buy ETH through Wyre/Ramp/Coinbase 12 | or get through Rinkeby/Ropsten/Kovan/Goerli 13 | 14 | ~ How can I use? ~ 15 | 16 | 20 | 21 | ~ Features ~ 22 | 23 | - Ramp opens directly in the application, component uses RampInstantSDK 24 | - Provide price={price} and current ETH price will be displayed 25 | - Provide address={address} and your address will be pasted into Wyre/Ramp instantly 26 | */ 27 | 28 | export default function Ramp(props) { 29 | const [modalUp, setModalUp] = useState("down"); 30 | 31 | const type = "default"; 32 | 33 | const allFaucets = []; 34 | for (const n in props.networks) { 35 | if (props.networks[n].chainId !== 31337 && props.networks[n].chainId !== 1) { 36 | allFaucets.push( 37 |

38 | 49 |

, 50 | ); 51 | } 52 | } 53 | 54 | return ( 55 |
56 | 66 | { 70 | setModalUp("down"); 71 | }} 72 | footer={[ 73 | , 81 | ]} 82 | > 83 |

84 | 99 |

100 |

101 | {" "} 102 | 125 |

126 | 127 |

128 | 141 |

142 | 143 | 144 | 145 |

Testnet ETH

146 | 147 | {allFaucets} 148 |
149 |
150 | ); 151 | } 152 | -------------------------------------------------------------------------------- /packages/react-app/src/components/ThemeSwitch.jsx: -------------------------------------------------------------------------------- 1 | import { Switch } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useThemeSwitcher } from "react-css-theme-switcher"; 4 | 5 | export default function ThemeSwitcher() { 6 | const theme = window.localStorage.getItem("theme"); 7 | const [isDarkMode, setIsDarkMode] = useState(!(!theme || theme === "light")); 8 | const { switcher, currentTheme, status, themes } = useThemeSwitcher(); 9 | 10 | useEffect(() => { 11 | window.localStorage.setItem("theme", currentTheme); 12 | }, [currentTheme]); 13 | 14 | const toggleTheme = isChecked => { 15 | setIsDarkMode(isChecked); 16 | switcher({ theme: isChecked ? themes.dark : themes.light }); 17 | }; 18 | 19 | // Avoid theme change flicker 20 | // if (status === "loading") { 21 | // return null; 22 | // } 23 | 24 | return ( 25 |
26 | {currentTheme === "light" ? "☀️" : "🌜"} 27 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Timeline.jsx: -------------------------------------------------------------------------------- 1 | import { DownloadOutlined, EditOutlined, SendOutlined } from "@ant-design/icons"; 2 | import { Timeline, Typography } from "antd"; 3 | import React from "react"; 4 | import Blockies from "react-blockies"; 5 | 6 | const { Text } = Typography; 7 | 8 | // displays a timeline for scaffold-eth usage 9 | 10 | export default function TimelineDisplay(props) { 11 | return ( 12 | 13 | 14 | 15 | Clone and Install from the{" "} 16 | 17 | github repo 18 | 19 | 20 | 21 | 22 | 23 | 24 | Start your frontend app with: yarn start 25 | 26 | 27 | 28 | 29 | 30 | Start your local blockchain with: yarn run chain (and refresh) 31 | 32 | 33 | 34 | 35 | 36 | Compile and deploy your smart contract: yarn run deploy 37 | 38 | 39 | 40 | 41 | 42 | Fix error in SmartContractWallet.sol then: yarn run deploy 43 | 44 | 45 | 46 | } color={props.hasEther ? "green" : "blue"}> 47 | 48 | Send test ether to your{" "} 49 | address using 50 | (bottom left) faucet 51 | 52 | 53 | 54 | } 56 | color={props.contractHasEther ? "green" : "blue"} 57 | > 58 | 59 | Deposit some funds into your{" "} 60 | {" "} 61 | smart contract wallet 62 | 63 | 64 | 65 | } 67 | color={props.amOwnerOfContract ? "green" : "blue"} 68 | > 69 | 70 | Set owner of your{" "} 71 | {" "} 72 | smart contract wallet to your{" "} 73 | address 74 | 75 | 76 | 77 | 78 | 79 | Yikes, anyone can take ownership of SmartContractWallet.sol 80 | 81 | 82 | 83 | 84 | 85 | Test your contract with buidler/test/myTest.js then: 86 | yarn run test 87 | 88 | 89 | 90 | 91 | 92 | Build something awesome with 🏗 scaffold-eth and{" "} 93 | 94 | @ me 95 | 96 | ! 97 | 98 | 99 | 100 | 101 | 102 | Read more about{" "} 103 | 104 | Ethereum 105 | 106 | ,{" "} 107 | 108 | Solidity 109 | 110 | , and{" "} 111 | 112 | Buidler 113 | 114 | 115 | 116 | 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /packages/react-app/src/components/TokenBalance.jsx: -------------------------------------------------------------------------------- 1 | import { useTokenBalance } from "eth-hooks/erc/erc-20/useTokenBalance"; 2 | import React, { useState } from "react"; 3 | 4 | import { utils } from "ethers"; 5 | 6 | export default function TokenBalance(props) { 7 | const [dollarMode, setDollarMode] = useState(true); 8 | 9 | const tokenContract = props.contracts && props.contracts[props.name]; 10 | const balance = useTokenBalance(tokenContract, props.address, 1777); 11 | 12 | let floatBalance = parseFloat("0.00"); 13 | 14 | let usingBalance = balance; 15 | 16 | if (typeof props.balance !== "undefined") { 17 | usingBalance = props.balance; 18 | } 19 | 20 | if (usingBalance) { 21 | const etherBalance = utils.formatEther(usingBalance); 22 | parseFloat(etherBalance).toFixed(2); 23 | floatBalance = parseFloat(etherBalance); 24 | } 25 | 26 | let displayBalance = floatBalance.toFixed(4); 27 | 28 | if (props.dollarMultiplier && dollarMode) { 29 | displayBalance = "$" + (floatBalance * props.dollarMultiplier).toFixed(2); 30 | } 31 | 32 | return ( 33 | { 41 | setDollarMode(!dollarMode); 42 | }} 43 | > 44 | {props.img} {displayBalance} 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Wallet.jsx: -------------------------------------------------------------------------------- 1 | import { KeyOutlined, QrcodeOutlined, SendOutlined, WalletOutlined } from "@ant-design/icons"; 2 | import { Button, Modal, Spin, Tooltip, Typography } from "antd"; 3 | import { ethers } from "ethers"; 4 | import QR from "qrcode.react"; 5 | import React, { useState, useEffect } from "react"; 6 | import { Transactor } from "../helpers"; 7 | import Address from "./Address"; 8 | import AddressInput from "./AddressInput"; 9 | import Balance from "./Balance"; 10 | import EtherInput from "./EtherInput"; 11 | 12 | const { Text, Paragraph } = Typography; 13 | 14 | /* 15 | ~ What it does? ~ 16 | 17 | Displays a wallet where you can specify address and send USD/ETH, with options to 18 | scan address, to convert between USD and ETH, to see and generate private keys, 19 | to send, receive and extract the burner wallet 20 | 21 | ~ How can I use? ~ 22 | 23 | 30 | 31 | ~ Features ~ 32 | 33 | - Provide provider={userProvider} to display a wallet 34 | - Provide address={address} if you want to specify address, otherwise 35 | your default address will be used 36 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 37 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 38 | - Provide price={price} of ether and easily convert between USD and ETH 39 | - Provide color to specify the color of wallet icon 40 | */ 41 | 42 | export default function Wallet(props) { 43 | const [signerAddress, setSignerAddress] = useState(); 44 | useEffect(() => { 45 | async function getAddress() { 46 | if (props.signer) { 47 | const newAddress = await props.signer.getAddress(); 48 | setSignerAddress(newAddress); 49 | } 50 | } 51 | getAddress(); 52 | }, [props.signer]); 53 | 54 | const selectedAddress = props.address || signerAddress; 55 | 56 | const [open, setOpen] = useState(); 57 | const [qr, setQr] = useState(); 58 | const [amount, setAmount] = useState(); 59 | const [toAddress, setToAddress] = useState(); 60 | const [pk, setPK] = useState(); 61 | 62 | const providerSend = props.provider ? ( 63 | 64 | { 66 | setOpen(!open); 67 | }} 68 | rotate={-90} 69 | style={{ 70 | padding: 7, 71 | color: props.color ? props.color : "", 72 | cursor: "pointer", 73 | fontSize: 28, 74 | verticalAlign: "middle", 75 | }} 76 | /> 77 | 78 | ) : ( 79 | "" 80 | ); 81 | 82 | let display; 83 | let receiveButton; 84 | let privateKeyButton; 85 | if (qr) { 86 | display = ( 87 |
88 |
89 | {selectedAddress} 90 |
91 | 99 |
100 | ); 101 | receiveButton = ( 102 | 110 | ); 111 | privateKeyButton = ( 112 | 121 | ); 122 | } else if (pk) { 123 | const pk = localStorage.getItem("metaPrivateKey"); 124 | const wallet = new ethers.Wallet(pk); 125 | 126 | if (wallet.address !== selectedAddress) { 127 | display = ( 128 |
129 | *injected account*, private key unknown 130 |
131 | ); 132 | } else { 133 | const extraPkDisplayAdded = {}; 134 | const extraPkDisplay = []; 135 | extraPkDisplayAdded[wallet.address] = true; 136 | extraPkDisplay.push( 137 | , 142 | ); 143 | for (const key in localStorage) { 144 | if (key.indexOf("metaPrivateKey_backup") >= 0) { 145 | console.log(key); 146 | const pastpk = localStorage.getItem(key); 147 | const pastwallet = new ethers.Wallet(pastpk); 148 | if (!extraPkDisplayAdded[pastwallet.address] /* && selectedAddress!=pastwallet.address */) { 149 | extraPkDisplayAdded[pastwallet.address] = true; 150 | extraPkDisplay.push( 151 | , 157 | ); 158 | } 159 | } 160 | } 161 | 162 | display = ( 163 |
164 | Private Key: 165 | 166 |
167 | {pk} 168 |
169 | 170 |
171 | 172 | 173 | Point your camera phone at qr code to open in 174 | 175 | burner wallet 176 | 177 | : 178 | 179 | 187 | 188 | 189 | {"https://xdai.io/" + pk} 190 | 191 | 192 | {extraPkDisplay ? ( 193 |
194 |

Known Private Keys:

195 | {extraPkDisplay} 196 | 210 |
211 | ) : ( 212 | "" 213 | )} 214 |
215 | ); 216 | } 217 | 218 | receiveButton = ( 219 | 228 | ); 229 | privateKeyButton = ( 230 | 239 | ); 240 | } else { 241 | const inputStyle = { 242 | padding: 10, 243 | }; 244 | 245 | display = ( 246 |
247 |
248 | 255 |
256 |
257 | { 261 | setAmount(value); 262 | }} 263 | /> 264 |
265 |
266 | ); 267 | receiveButton = ( 268 | 277 | ); 278 | privateKeyButton = ( 279 | 288 | ); 289 | } 290 | 291 | return ( 292 | 293 | {providerSend} 294 | 298 | {selectedAddress ?
: } 299 |
300 | 301 |
302 | 303 | } 304 | onOk={() => { 305 | setQr(); 306 | setPK(); 307 | setOpen(!open); 308 | }} 309 | onCancel={() => { 310 | setQr(); 311 | setPK(); 312 | setOpen(!open); 313 | }} 314 | footer={[ 315 | privateKeyButton, 316 | receiveButton, 317 | , 343 | ]} 344 | > 345 | {display} 346 | 347 | 348 | ); 349 | } 350 | -------------------------------------------------------------------------------- /packages/react-app/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Account } from "./Account"; 2 | export { default as Address } from "./Address"; 3 | export { default as AddressInput } from "./AddressInput"; 4 | export { default as Balance } from "./Balance"; 5 | export { default as Blockie } from "./Blockie"; 6 | export { default as BytesStringInput } from "./BytesStringInput"; 7 | export { default as Contract } from "./Contract"; 8 | export { default as EtherInput } from "./EtherInput"; 9 | export { default as Events } from "./Events"; 10 | export { default as Faucet } from "./Faucet"; 11 | export { default as GasGauge } from "./GasGauge"; 12 | export { default as Header } from "./Header"; 13 | export { default as Provider } from "./Provider"; 14 | export { default as Ramp } from "./Ramp"; 15 | export { default as Swap } from "./Swap"; 16 | export { default as ThemeSwitch } from "./ThemeSwitch"; 17 | export { default as Timeline } from "./Timeline"; 18 | export { default as TokenBalance } from "./TokenBalance"; 19 | export { default as Wallet } from "./Wallet"; 20 | export { default as L2Bridge } from "./L2Bridge"; 21 | -------------------------------------------------------------------------------- /packages/react-app/src/constants.js: -------------------------------------------------------------------------------- 1 | // MY INFURA_ID, SWAP IN YOURS FROM https://infura.io/dashboard/ethereum 2 | export const INFURA_ID = "7b0e75d38d424750b92791477924d133"; 3 | 4 | // MY ETHERSCAN_ID, SWAP IN YOURS FROM https://etherscan.io/myapikey 5 | export const ETHERSCAN_KEY = "DNXJA8RX2Q3VZ4URQIWP7Z68CJXQZSC6AW"; 6 | 7 | // BLOCKNATIVE ID FOR Notify.js: 8 | export const BLOCKNATIVE_DAPPID = "0b58206a-f3c0-4701-a62f-73c7243e8c77"; 9 | 10 | export const ALCHEMY_KEY = "oKxs-03sij-U_N0iOlrSsZFr29-IqbuF"; 11 | 12 | export const NETWORKS = { 13 | localhost: { 14 | name: "localhost", 15 | color: "#666666", 16 | chainId: 31337, 17 | blockExplorer: "", 18 | rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":8545", 19 | }, 20 | mainnet: { 21 | name: "mainnet", 22 | color: "#ff8b9e", 23 | chainId: 1, 24 | rpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`, 25 | blockExplorer: "https://etherscan.io/", 26 | }, 27 | kovan: { 28 | name: "kovan", 29 | color: "#7003DD", 30 | chainId: 42, 31 | rpcUrl: `https://kovan.infura.io/v3/${INFURA_ID}`, 32 | blockExplorer: "https://kovan.etherscan.io/", 33 | faucet: "https://gitter.im/kovan-testnet/faucet", // https://faucet.kovan.network/ 34 | }, 35 | rinkeby: { 36 | name: "rinkeby", 37 | color: "#e0d068", 38 | chainId: 4, 39 | rpcUrl: `https://rinkeby.infura.io/v3/${INFURA_ID}`, 40 | faucet: "https://faucet.rinkeby.io/", 41 | blockExplorer: "https://rinkeby.etherscan.io/", 42 | }, 43 | ropsten: { 44 | name: "ropsten", 45 | color: "#F60D09", 46 | chainId: 3, 47 | faucet: "https://faucet.ropsten.be/", 48 | blockExplorer: "https://ropsten.etherscan.io/", 49 | rpcUrl: `https://ropsten.infura.io/v3/${INFURA_ID}`, 50 | }, 51 | goerli: { 52 | name: "goerli", 53 | color: "#0975F6", 54 | chainId: 5, 55 | faucet: "https://goerli-faucet.slock.it/", 56 | blockExplorer: "https://goerli.etherscan.io/", 57 | rpcUrl: `https://goerli.infura.io/v3/${INFURA_ID}`, 58 | }, 59 | xdai: { 60 | name: "xdai", 61 | color: "#48a9a6", 62 | chainId: 100, 63 | price: 1, 64 | gasPrice: 1000000000, 65 | rpcUrl: "https://dai.poa.network", 66 | faucet: "https://xdai-faucet.top/", 67 | blockExplorer: "https://blockscout.com/poa/xdai/", 68 | }, 69 | matic: { 70 | name: "matic", 71 | color: "#2bbdf7", 72 | chainId: 137, 73 | price: 1, 74 | gasPrice: 1000000000, 75 | rpcUrl: "https://rpc-mainnet.maticvigil.com", 76 | faucet: "https://faucet.matic.network/", 77 | blockExplorer: "https://explorer-mainnet.maticvigil.com//", 78 | }, 79 | mumbai: { 80 | name: "mumbai", 81 | color: "#92D9FA", 82 | chainId: 80001, 83 | price: 1, 84 | gasPrice: 1000000000, 85 | rpcUrl: "https://rpc-mumbai.maticvigil.com", 86 | faucet: "https://faucet.matic.network/", 87 | blockExplorer: "https://mumbai-explorer.matic.today/", 88 | }, 89 | localArbitrum: { 90 | name: "localArbitrum", 91 | color: "#50a0ea", 92 | chainId: 153869338190755, 93 | blockExplorer: "", 94 | rpcUrl: `http://localhost:8547`, 95 | }, 96 | localArbitrumL1: { 97 | name: "localArbitrumL1", 98 | color: "#50a0ea", 99 | chainId: 44010, 100 | blockExplorer: "", 101 | rpcUrl: `http://localhost:7545`, 102 | }, 103 | rinkebyArbitrum: { 104 | name: "Arbitrum Testnet", 105 | color: "#50a0ea", 106 | chainId: 421611, 107 | blockExplorer: "https://rinkeby-explorer.arbitrum.io/#/", 108 | rpcUrl: `https://rinkeby.arbitrum.io/rpc`, 109 | }, 110 | arbitrum: { 111 | name: "Arbitrum", 112 | color: "#50a0ea", 113 | chainId: 42161, 114 | blockExplorer: "https://explorer.arbitrum.io/#/", 115 | rpcUrl: `https://arb1.arbitrum.io/rpc`, 116 | gasPrice: 0, 117 | }, 118 | localOptimismL1: { 119 | name: "localOptimismL1", 120 | color: "#f01a37", 121 | chainId: 31337, 122 | blockExplorer: "", 123 | rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":9545", 124 | }, 125 | localOptimism: { 126 | name: "localOptimism", 127 | color: "#f01a37", 128 | chainId: 420, 129 | blockExplorer: "", 130 | rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":8545", 131 | gasPrice: 0, 132 | }, 133 | kovanOptimism: { 134 | name: "kovanOptimism", 135 | color: "#f01a37", 136 | chainId: 69, 137 | blockExplorer: "https://kovan-optimistic.etherscan.io/", 138 | rpcUrl: `https://kovan.optimism.io`, 139 | gasPrice: 0, 140 | }, 141 | optimism: { 142 | name: "optimism", 143 | color: "#f01a37", 144 | chainId: 10, 145 | blockExplorer: "https://optimistic.etherscan.io/", 146 | rpcUrl: `https://mainnet.optimism.io`, 147 | }, 148 | localAvalanche: { 149 | name: "localAvalanche", 150 | color: "#666666", 151 | chainId: 43112, 152 | blockExplorer: "", 153 | rpcUrl: `http://localhost:9650/ext/bc/C/rpc`, 154 | gasPrice: 225000000000, 155 | }, 156 | fujiAvalanche: { 157 | name: "fujiAvalanche", 158 | color: "#666666", 159 | chainId: 43113, 160 | blockExplorer: "https://cchain.explorer.avax-test.network/", 161 | rpcUrl: `https://api.avax-test.network/ext/bc/C/rpc`, 162 | gasPrice: 225000000000, 163 | }, 164 | mainnetAvalanche: { 165 | name: "mainnetAvalanche", 166 | color: "#666666", 167 | chainId: 43114, 168 | blockExplorer: "https://cchain.explorer.avax.network/", 169 | rpcUrl: `https://api.avax.network/ext/bc/C/rpc`, 170 | gasPrice: 225000000000, 171 | }, 172 | testnetHarmony: { 173 | name: "Harmony Testnet", 174 | color: "#00b0ef", 175 | chainId: 1666700000, 176 | blockExplorer: "https://explorer.pops.one/", 177 | rpcUrl: `https://api.s0.b.hmny.io`, 178 | gasPrice: 1000000000, 179 | }, 180 | mainnetHarmony: { 181 | name: "Harmony Mainnet", 182 | color: "#00b0ef", 183 | chainId: 1666600000, 184 | blockExplorer: "https://explorer.harmony.one/", 185 | rpcUrl: `https://api.harmony.one`, 186 | gasPrice: 1000000000, 187 | }, 188 | }; 189 | 190 | export const NETWORK = chainId => { 191 | for (const n in NETWORKS) { 192 | if (NETWORKS[n].chainId === chainId) { 193 | return NETWORKS[n]; 194 | } 195 | } 196 | }; 197 | -------------------------------------------------------------------------------- /packages/react-app/src/ethereumLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-examples/896ea245d30fd8d697c543347a2e13717d394eca/packages/react-app/src/ethereumLogo.png -------------------------------------------------------------------------------- /packages/react-app/src/helpers/Transactor.js: -------------------------------------------------------------------------------- 1 | import { notification } from "antd"; 2 | import Notify from "bnc-notify"; 3 | import { BLOCKNATIVE_DAPPID } from "../constants"; 4 | 5 | const { ethers } = require("ethers"); 6 | 7 | // this should probably just be renamed to "notifier" 8 | // it is basically just a wrapper around BlockNative's wonderful Notify.js 9 | // https://docs.blocknative.com/notify 10 | const callbacks = {}; 11 | 12 | const DEBUG = true; 13 | 14 | export default function Transactor(providerOrSigner, gasPrice, etherscan) { 15 | if (typeof providerOrSigner !== "undefined") { 16 | // eslint-disable-next-line consistent-return 17 | return async (tx, callback) => { 18 | let signer; 19 | let network; 20 | let provider; 21 | if (ethers.Signer.isSigner(providerOrSigner) === true) { 22 | provider = providerOrSigner.provider; 23 | signer = providerOrSigner; 24 | network = providerOrSigner.provider && (await providerOrSigner.provider.getNetwork()); 25 | } else if (providerOrSigner._isProvider) { 26 | provider = providerOrSigner; 27 | signer = providerOrSigner.getSigner(); 28 | network = await providerOrSigner.getNetwork(); 29 | } 30 | 31 | console.log("network", network); 32 | var options = null; 33 | var notify = null; 34 | options = { 35 | dappId: BLOCKNATIVE_DAPPID, // GET YOUR OWN KEY AT https://account.blocknative.com 36 | system: "ethereum", 37 | networkId: network.chainId, 38 | // darkMode: Boolean, // (default: false) 39 | transactionHandler: txInformation => { 40 | if (DEBUG) console.log("HANDLE TX", txInformation); 41 | const possibleFunction = callbacks[txInformation.transaction.hash]; 42 | if (typeof possibleFunction === "function") { 43 | possibleFunction(txInformation.transaction); 44 | } 45 | }, 46 | }; 47 | 48 | notify = Notify(options); 49 | 50 | let etherscanNetwork = ""; 51 | if (network.name && network.chainId > 1) { 52 | etherscanNetwork = network.name + "."; 53 | } 54 | 55 | let etherscanTxUrl = "https://" + etherscanNetwork + "etherscan.io/tx/"; 56 | if (network.chainId === 100) { 57 | etherscanTxUrl = "https://blockscout.com/poa/xdai/tx/"; 58 | } 59 | 60 | try { 61 | let result; 62 | if (tx instanceof Promise) { 63 | if (DEBUG) console.log("AWAITING TX", tx); 64 | result = await tx; 65 | } else { 66 | if (!tx.gasPrice) { 67 | tx.gasPrice = gasPrice || ethers.utils.parseUnits("4.1", "gwei"); 68 | } 69 | if (!tx.gasLimit) { 70 | tx.gasLimit = ethers.utils.hexlify(120000); 71 | } 72 | if (DEBUG) console.log("RUNNING TX", tx); 73 | result = await signer.sendTransaction(tx); 74 | } 75 | if (DEBUG) console.log("RESULT:", result); 76 | // console.log("Notify", notify); 77 | 78 | if (callback) { 79 | callbacks[result.hash] = callback; 80 | } 81 | 82 | // if it is a valid Notify.js network, use that, if not, just send a default notification 83 | if (notify && [1, 3, 4, 5, 42, 100].indexOf(network.chainId) >= 0) { 84 | const { emitter } = notify.hash(result.hash); 85 | emitter.on("all", transaction => { 86 | return { 87 | onclick: () => window.open((etherscan || etherscanTxUrl) + transaction.hash), 88 | }; 89 | }); 90 | } else { 91 | notification.info({ 92 | message: "Local Transaction Sent", 93 | description: result.hash, 94 | placement: "bottomRight", 95 | }); 96 | // on most networks BlockNative will update a transaction handler, 97 | // but locally we will set an interval to listen... 98 | if (callback) { 99 | const txResult = await tx; 100 | const listeningInterval = setInterval(async () => { 101 | console.log("CHECK IN ON THE TX", txResult, provider); 102 | const currentTransactionReceipt = await provider.getTransactionReceipt(txResult.hash); 103 | if (currentTransactionReceipt && currentTransactionReceipt.confirmations) { 104 | callback({ ...txResult, ...currentTransactionReceipt }); 105 | clearInterval(listeningInterval); 106 | } 107 | }, 500); 108 | } 109 | } 110 | 111 | if (typeof result.wait === "function") { 112 | await result.wait(); 113 | } 114 | 115 | return result; 116 | } catch (e) { 117 | if (DEBUG) console.log(e); 118 | // Accounts for Metamask and default signer on all networks 119 | let message = 120 | e.data && e.data.message 121 | ? e.data.message 122 | : e.error && JSON.parse(JSON.stringify(e.error)).body 123 | ? JSON.parse(JSON.parse(JSON.stringify(e.error)).body).error.message 124 | : e.data 125 | ? e.data 126 | : JSON.stringify(e); 127 | if (!e.error && e.message) { 128 | message = e.message; 129 | } 130 | 131 | console.log("Attempt to clean up:", message); 132 | try { 133 | let obj = JSON.parse(message); 134 | if (obj && obj.body) { 135 | let errorObj = JSON.parse(obj.body); 136 | if (errorObj && errorObj.error && errorObj.error.message) { 137 | message = errorObj.error.message; 138 | } 139 | } 140 | } catch (e) { 141 | //ignore 142 | } 143 | 144 | notification.error({ 145 | message: "Transaction Error", 146 | description: message, 147 | }); 148 | if (callback && typeof callback === "function") { 149 | callback(e); 150 | } 151 | } 152 | }; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as Transactor } from "./Transactor"; 2 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/loadAppContracts.js: -------------------------------------------------------------------------------- 1 | const contractListPromise = import("../contracts/hardhat_contracts.json"); 2 | // @ts-ignore 3 | const externalContractsPromise = import("../contracts/external_contracts"); 4 | 5 | export const loadAppContracts = async () => { 6 | const config = {}; 7 | config.deployedContracts = (await contractListPromise).default ?? {}; 8 | config.externalContracts = (await externalContractsPromise).default ?? {}; 9 | return config; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Debounce.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useDebounce(value, delay) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/GasPrice.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { usePoller } from "eth-hooks"; 3 | import { useState } from "react"; 4 | 5 | export default function useGasPrice(targetNetwork, speed) { 6 | const [gasPrice, setGasPrice] = useState(); 7 | const loadGasPrice = async () => { 8 | if (targetNetwork.hasOwnProperty("gasPrice")) { 9 | setGasPrice(targetNetwork.gasPrice); 10 | } else { 11 | axios 12 | .get("https://ethgasstation.info/json/ethgasAPI.json") 13 | .then(response => { 14 | const newGasPrice = response.data[speed || "fast"] * 100000000; 15 | if (newGasPrice !== gasPrice) { 16 | setGasPrice(newGasPrice); 17 | } 18 | }) 19 | .catch(error => console.log(error)); 20 | } 21 | }; 22 | 23 | usePoller(loadGasPrice, 39999); 24 | return gasPrice; 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | // Hook from useHooks! (https://usehooks.com/useLocalStorage/) 3 | export default function useLocalStorage(key, initialValue, ttl) { 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 | const parsedItem = item ? JSON.parse(item) : initialValue; 11 | 12 | if (typeof parsedItem === "object" && parsedItem !== null && "expiry" in parsedItem && "value" in parsedItem) { 13 | const now = new Date(); 14 | if (ttl && now.getTime() > parsedItem.expiry) { 15 | // If the item is expired, delete the item from storage 16 | // and return null 17 | window.localStorage.removeItem(key); 18 | return initialValue; 19 | } 20 | return parsedItem.value; 21 | } 22 | // Parse stored json or if none return initialValue 23 | return parsedItem; 24 | } catch (error) { 25 | // If error also return initialValue 26 | console.log(error); 27 | return initialValue; 28 | } 29 | }); 30 | 31 | // Return a wrapped version of useState's setter function that ... 32 | // ... persists the new value to localStorage. 33 | const setValue = value => { 34 | try { 35 | // Allow value to be a function so we have same API as useState 36 | const valueToStore = value instanceof Function ? value(storedValue) : value; 37 | // Save state 38 | setStoredValue(valueToStore); 39 | // Save to local storage 40 | if (ttl) { 41 | const now = new Date(); 42 | 43 | // `item` is an object which contains the original value 44 | // as well as the time when it's supposed to expire 45 | const item = { 46 | value: valueToStore, 47 | expiry: now.getTime() + ttl, 48 | }; 49 | window.localStorage.setItem(key, JSON.stringify(item)); 50 | } else { 51 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 52 | } 53 | } catch (error) { 54 | // A more advanced implementation would handle the error case 55 | console.log(error); 56 | } 57 | }; 58 | 59 | return [storedValue, setValue]; 60 | } 61 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/TokenList.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Gets a tokenlist (see more at https://tokenlists.org/), returning the .tokens only 7 | 8 | ~ How can I use? ~ 9 | 10 | const tokenList = useTokenList(); <- default returns the Unsiwap tokens 11 | const tokenList = useTokenList("https://gateway.ipfs.io/ipns/tokens.uniswap.org"); 12 | 13 | ~ Features ~ 14 | 15 | - Optional - specify chainId to filter by chainId 16 | */ 17 | 18 | const useTokenList = (tokenListUri, chainId) => { 19 | const [tokenList, setTokenList] = useState([]); 20 | 21 | const _tokenListUri = tokenListUri || "https://gateway.ipfs.io/ipns/tokens.uniswap.org"; 22 | 23 | useEffect(() => { 24 | const getTokenList = async () => { 25 | try { 26 | const tokenList = await fetch(_tokenListUri); 27 | const tokenListJson = await tokenList.json(); 28 | let _tokenList; 29 | 30 | if (chainId) { 31 | _tokenList = tokenListJson.tokens.filter(function (t) { 32 | return t.chainId === chainId; 33 | }); 34 | } else { 35 | _tokenList = tokenListJson; 36 | } 37 | 38 | setTokenList(_tokenList.tokens); 39 | } catch (e) { 40 | console.log(e); 41 | } 42 | }; 43 | getTokenList(); 44 | }, [tokenListUri]); 45 | 46 | return tokenList; 47 | }; 48 | 49 | export default useTokenList; 50 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useDebounce } from "./Debounce"; 2 | export { default as useLocalStorage } from "./LocalStorage"; 3 | export * from "./useContractConfig"; 4 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/useContractConfig.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { loadAppContracts } from "../helpers/loadAppContracts"; 3 | 4 | export const useContractConfig = () => { 5 | const [contractsConfig, setContractsConfig] = useState({}); 6 | 7 | useEffect(() => { 8 | const loadFunc = async () => { 9 | const result = await loadAppContracts(); 10 | setContractsConfig(result); 11 | }; 12 | void loadFunc(); 13 | }, []); 14 | return contractsConfig; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | code { 6 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; 7 | } 8 | -------------------------------------------------------------------------------- /packages/react-app/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"; 2 | import React from "react"; 3 | import { ThemeSwitcherProvider } from "react-css-theme-switcher"; 4 | import ReactDOM from "react-dom"; 5 | import App from "./App"; 6 | import "./index.css"; 7 | 8 | const themes = { 9 | dark: `${process.env.PUBLIC_URL}/dark-theme.css`, 10 | light: `${process.env.PUBLIC_URL}/light-theme.css`, 11 | }; 12 | 13 | const prevTheme = window.localStorage.getItem("theme"); 14 | 15 | const subgraphUri = "http://localhost:8000/subgraphs/name/scaffold-eth/your-contract"; 16 | 17 | const client = new ApolloClient({ 18 | uri: subgraphUri, 19 | cache: new InMemoryCache(), 20 | }); 21 | 22 | ReactDOM.render( 23 | 24 | 25 | 26 | 27 | , 28 | document.getElementById("root"), 29 | ); 30 | -------------------------------------------------------------------------------- /packages/react-app/src/setupTests.js: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/dark-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/dark.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | @primary-color: #2caad9; 7 | @border-radius-base: 4px; 8 | 9 | @component-background: #212121; 10 | @body-background: #212121; 11 | @popover-background: #212121; 12 | @border-color-base: #6f6c6c; 13 | @border-color-split: #424242; 14 | @table-header-sort-active-bg: #424242; 15 | @card-skeleton-bg: #424242; 16 | @skeleton-color: #424242; 17 | @table-header-sort-active-bg: #424242; 18 | 19 | .highlight { 20 | background-color: #3f3f3f; 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/light-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/default.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | // @primary-color: #00adb5; 7 | @border-radius-base: 4px; 8 | 9 | .highlight { 10 | background-color: #f9f9f9; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-app/src/views/ExampleUI.jsx: -------------------------------------------------------------------------------- 1 | import { SyncOutlined } from "@ant-design/icons"; 2 | import { utils } from "ethers"; 3 | import { Button, Card, DatePicker, Divider, Input, Progress, Slider, Spin, Switch } from "antd"; 4 | import React, { useState } from "react"; 5 | import { Address, Balance, Events } from "../components"; 6 | 7 | export default function ExampleUI({ 8 | purpose, 9 | address, 10 | mainnetProvider, 11 | localProvider, 12 | yourLocalBalance, 13 | price, 14 | tx, 15 | readContracts, 16 | writeContracts, 17 | }) { 18 | const [newPurpose, setNewPurpose] = useState("loading..."); 19 | 20 | return ( 21 |
22 | {/* 23 | ⚙️ Here is an example UI that displays and sets the purpose in your smart contract: 24 | */} 25 |
26 |

Example UI:

27 |

purpose: {purpose}

28 | 29 |
30 | { 32 | setNewPurpose(e.target.value); 33 | }} 34 | /> 35 | 61 |
62 | 63 | Your Address: 64 |
65 | 66 | ENS Address Example: 67 |
72 | 73 | {/* use utils.formatEther to display a BigNumber: */} 74 |

Your Balance: {yourLocalBalance ? utils.formatEther(yourLocalBalance) : "..."}

75 |
OR
76 | 77 | 78 |
🐳 Example Whale Balance:
79 | 80 | 81 | {/* use utils.formatEther to display a BigNumber: */} 82 |

Your Balance: {yourLocalBalance ? utils.formatEther(yourLocalBalance) : "..."}

83 | 84 | Your Contract Address: 85 |
90 | 91 |
92 | 100 |
101 |
102 | 117 |
118 |
119 | 132 |
133 |
134 | 149 |
150 |
151 | 152 | {/* 153 | 📑 Maybe display a list of events? 154 | (uncomment the event and emit line in YourContract.sol! ) 155 | */} 156 | 164 | 165 |
166 | 167 | Check out all the{" "} 168 | 173 | 📦 components 174 | 175 | 176 | 177 | 178 |
179 | There are tons of generic components included from{" "} 180 | 181 | 🐜 ant.design 182 | {" "} 183 | too! 184 |
185 | 186 |
187 | 188 |
189 | 190 |
191 | Icons 192 |
193 | 194 |
195 | Date Pickers? 196 |
197 | {}} /> 198 |
199 |
200 | 201 |
202 | {}} /> 203 |
204 | 205 |
206 | {}} /> 207 |
208 | 209 |
210 | 211 |
212 | 213 |
214 | 215 |
216 |
217 |
218 |
219 | ); 220 | } 221 | -------------------------------------------------------------------------------- /packages/react-app/src/views/Hints.jsx: -------------------------------------------------------------------------------- 1 | import { utils } from "ethers"; 2 | import { Select } from "antd"; 3 | import React, { useState } from "react"; 4 | import { Address, AddressInput } from "../components"; 5 | import { useTokenList } from "eth-hooks/dapps/dex"; 6 | 7 | const { Option } = Select; 8 | 9 | export default function Hints({ yourLocalBalance, mainnetProvider, price, address }) { 10 | // Get a list of tokens from a tokenlist -> see tokenlists.org! 11 | const [selectedToken, setSelectedToken] = useState("Pick a token!"); 12 | const listOfTokens = useTokenList( 13 | "https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json", 14 | ); 15 | 16 | return ( 17 |
18 |
19 | 👷 20 | Edit your contract in 21 | 25 | packages/hardhat/contracts 26 | 27 |
28 | 29 |
30 | 🛰 31 | compile/deploy with 32 | 36 | yarn run deploy 37 | 38 |
39 | 40 |
41 | 🚀 42 | Your contract artifacts are automatically injected into your frontend at 43 | 47 | packages/react-app/src/contracts/ 48 | 49 |
50 | 51 |
52 | 🎛 53 | Edit your frontend in 54 | 58 | packages/reactapp/src/App.js 59 | 60 |
61 | 62 |
63 | 🔭 64 | explore the 65 | 76 | 🖇 hooks 77 | 78 | and 79 | 83 | 📦 components 84 | 85 |
86 | 87 |
88 | for example, the 89 | 93 | useBalance() 94 | {" "} 95 | hook keeps track of your balance: {utils.formatEther(yourLocalBalance || 0)} 96 |
97 | 98 |
99 |
100 | useTokenList() can get you an array of tokens from{" "} 101 | 102 | tokenlists.org! 103 | 104 |
105 | 121 |
122 | 123 |
124 | as you build your app you'll need web3 specific components like an 125 | 129 | {""} 130 | 131 | component: 132 |
133 | 134 |
135 |
(try putting in your address, an ens address, or scanning a QR code)
136 |
137 | 138 |
139 | this balance could be multiplied by 140 | 144 | price 145 | {" "} 146 | that is loaded with the 147 | 151 | usePrice 152 | {" "} 153 | hook with the current value: ${price} 154 |
155 | 156 |
157 | 💧 158 | use the faucet to send funds to 159 | 163 |
{address} 164 | 165 |
166 | 167 |
168 | 📡 169 | deploy to a testnet or mainnet by editing 170 | 174 | packages/hardhat/hardhat.config.js 175 | 176 | and running 177 | 181 | yarn run deploy 182 | 183 |
184 | 185 |
186 | 🔑 187 | 191 | yarn run generate 192 | 193 | will create a deployer account in 194 | 198 | packages/hardhat 199 | 200 |
201 | (use{" "} 202 | 211 | yarn run account 212 | {" "} 213 | to display deployer address and balance) 214 |
215 |
216 | 217 |
218 | ⚙️ 219 | build your app with 220 | 224 | yarn run build 225 | 226 |
227 | 228 |
229 | 🚢 230 | ship it! 231 | 235 | yarn run surge 236 | 237 | or 238 | 242 | yarn run s3 243 | 244 | or 245 | 249 | yarn run ipfs 250 | 251 |
252 | 253 |
254 | 💬 255 | for support, join this 256 | 260 | 261 | Telegram Chat 262 | 263 | 264 |
265 |
266 | 🛠 Check out your browser's developer console for more... (inspect console) 🚀 267 |
268 |
269 | ); 270 | } 271 | -------------------------------------------------------------------------------- /packages/react-app/src/views/Subgraph.jsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Button, Input, Table, Typography } from "antd"; 3 | import "antd/dist/antd.css"; 4 | import GraphiQL from "graphiql"; 5 | import "graphiql/graphiql.min.css"; 6 | import fetch from "isomorphic-fetch"; 7 | import React, { useState } from "react"; 8 | import { Address } from "../components"; 9 | 10 | const highlight = { 11 | marginLeft: 4, 12 | marginRight: 8, 13 | /* backgroundColor: "#f9f9f9", */ padding: 4, 14 | borderRadius: 4, 15 | fontWeight: "bolder", 16 | }; 17 | 18 | function Subgraph(props) { 19 | function graphQLFetcher(graphQLParams) { 20 | return fetch(props.subgraphUri, { 21 | method: "post", 22 | headers: { "Content-Type": "application/json" }, 23 | body: JSON.stringify(graphQLParams), 24 | }).then(response => response.json()); 25 | } 26 | 27 | const EXAMPLE_GRAPHQL = ` 28 | { 29 | purposes(first: 25, orderBy: createdAt, orderDirection: desc) { 30 | id 31 | purpose 32 | createdAt 33 | sender { 34 | id 35 | } 36 | } 37 | senders { 38 | id 39 | address 40 | purposeCount 41 | } 42 | } 43 | `; 44 | const EXAMPLE_GQL = gql(EXAMPLE_GRAPHQL); 45 | const { loading, data } = useQuery(EXAMPLE_GQL, { pollInterval: 2500 }); 46 | 47 | const purposeColumns = [ 48 | { 49 | title: "Purpose", 50 | dataIndex: "purpose", 51 | key: "purpose", 52 | }, 53 | { 54 | title: "Sender", 55 | key: "id", 56 | render: record =>
, 57 | }, 58 | { 59 | title: "createdAt", 60 | key: "createdAt", 61 | dataIndex: "createdAt", 62 | render: d => new Date(d * 1000).toISOString(), 63 | }, 64 | ]; 65 | 66 | const [newPurpose, setNewPurpose] = useState("loading..."); 67 | 68 | const deployWarning = ( 69 |
Warning: 🤔 Have you deployed your subgraph yet?
70 | ); 71 | 72 | return ( 73 | <> 74 |
75 | You will find that parsing/tracking events with the{" "} 76 | 77 | useEventListener 78 | {" "} 79 | hook becomes a chore for every new project. 80 |
81 |
82 | Instead, you can use{" "} 83 | 84 | The Graph 85 | {" "} 86 | with 🏗 scaffold-eth ( 87 | 88 | learn more 89 | 90 | ): 91 |
92 | 93 |
94 | 🚮 95 | Clean up previous data: 96 | 97 | yarn clean-graph-node 98 | 99 |
100 | 101 |
102 | 📡 103 | Spin up a local graph node by running 104 | 105 | yarn run-graph-node 106 | 107 | 108 | {" "} 109 | (requires{" "} 110 | 111 | {" "} 112 | Docker 113 | 114 | ){" "} 115 | 116 |
117 | 118 |
119 | 📝 120 | Create your local subgraph by running 121 | 122 | yarn graph-create-local 123 | 124 | (only required once!) 125 |
126 | 127 |
128 | 🚢 129 | Deploy your local subgraph by running 130 | 131 | yarn graph-ship-local 132 | 133 |
134 | 135 |
136 | 🖍️ 137 | Edit your local subgraph in 138 | 139 | packages/subgraph/src 140 | 141 | (learn more about subgraph definition{" "} 142 | 143 | here 144 | 145 | ) 146 |
147 | 148 |
149 | 🤩 150 | Deploy your contracts and your subgraph in one go by running 151 | 152 | yarn deploy-and-graph 153 | 154 |
155 | 156 |
157 |
158 | { 160 | setNewPurpose(e.target.value); 161 | }} 162 | /> 163 | 172 |
173 | 174 | {data ? ( 175 |
176 | ) : ( 177 | {loading ? "Loading..." : deployWarning} 178 | )} 179 | 180 |
181 | 182 |
183 | 184 | 185 |
...
186 | 187 | ); 188 | } 189 | 190 | export default Subgraph; 191 | -------------------------------------------------------------------------------- /packages/react-app/src/views/index.js: -------------------------------------------------------------------------------- 1 | export { default as ExampleUI } from "./ExampleUI"; 2 | export { default as Hints } from "./Hints"; 3 | export { default as Subgraph } from "./Subgraph"; 4 | -------------------------------------------------------------------------------- /packages/services/graph-node/Dockerfile: -------------------------------------------------------------------------------- 1 | # Full build with debuginfo for graph-node 2 | # 3 | # The expectation if that the docker build uses the parent directory as PWD 4 | # by running something like the following 5 | # docker build --target STAGE -f docker/Dockerfile . 6 | 7 | FROM rust:latest as graph-node-build 8 | 9 | ARG COMMIT_SHA=unknown 10 | ARG REPO_NAME=unknown 11 | ARG BRANCH_NAME=unknown 12 | ARG TAG_NAME=unknown 13 | 14 | ADD . /graph-node 15 | 16 | RUN cd /graph-node \ 17 | && RUSTFLAGS="-g" cargo install --locked --path node \ 18 | && cargo clean \ 19 | && objcopy --only-keep-debug /usr/local/cargo/bin/graph-node /usr/local/cargo/bin/graph-node.debug \ 20 | && strip -g /usr/local/cargo/bin/graph-node \ 21 | && cd /usr/local/cargo/bin \ 22 | && objcopy --add-gnu-debuglink=graph-node.debug graph-node \ 23 | && echo "REPO_NAME='$REPO_NAME'" > /etc/image-info \ 24 | && echo "TAG_NAME='$TAG_NAME'" >> /etc/image-info \ 25 | && echo "BRANCH_NAME='$BRANCH_NAME'" >> /etc/image-info \ 26 | && echo "COMMIT_SHA='$COMMIT_SHA'" >> /etc/image-info \ 27 | && echo "CARGO_VERSION='$(cargo --version)'" >> /etc/image-info \ 28 | && echo "RUST_VERSION='$(rustc --version)'" >> /etc/image-info 29 | 30 | # The graph-node runtime image with only the executable 31 | FROM debian:buster-slim as graph-node 32 | ENV RUST_LOG "" 33 | ENV GRAPH_LOG "" 34 | ENV EARLY_LOG_CHUNK_SIZE "" 35 | ENV ETHEREUM_RPC_PARALLEL_REQUESTS "" 36 | ENV ETHEREUM_BLOCK_CHUNK_SIZE "" 37 | 38 | ENV postgres_host "" 39 | ENV postgres_user "" 40 | ENV postgres_pass "" 41 | ENV postgres_db "" 42 | # The full URL to the IPFS node 43 | ENV ipfs "" 44 | # The etherum network(s) to connect to. Set this to a space-separated 45 | # list of the networks where each entry has the form NAME:URL 46 | ENV ethereum "" 47 | # The role the node should have, one of index-node, query-node, or 48 | # combined-node 49 | ENV node_role "combined-node" 50 | # The name of this node 51 | ENV node_id "default" 52 | 53 | # HTTP port 54 | EXPOSE 8000 55 | # WebSocket port 56 | EXPOSE 8001 57 | # JSON-RPC port 58 | EXPOSE 8020 59 | 60 | RUN apt-get update \ 61 | && apt-get install -y libpq-dev ca-certificates netcat 62 | 63 | ADD docker/wait_for docker/start /usr/local/bin/ 64 | COPY --from=graph-node-build /usr/local/cargo/bin/graph-node /usr/local/bin 65 | COPY --from=graph-node-build /etc/image-info /etc/image-info 66 | COPY docker/Dockerfile /Dockerfile 67 | CMD start 68 | 69 | # Debug image to access core dumps 70 | FROM graph-node-build as graph-node-debug 71 | RUN apt-get update \ 72 | && apt-get install -y curl gdb postgresql-client 73 | 74 | COPY docker/Dockerfile /Dockerfile 75 | COPY docker/bin/* /usr/local/bin/ 76 | -------------------------------------------------------------------------------- /packages/services/graph-node/README.md: -------------------------------------------------------------------------------- 1 | # Graph Node Docker Image 2 | 3 | Preconfigured Docker image for running a Graph Node. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | docker run -it \ 9 | -e postgres_host=[:] \ 10 | -e postgres_user= \ 11 | -e postgres_pass= \ 12 | -e postgres_db= \ 13 | -e ipfs=: \ 14 | -e ethereum=: \ 15 | graphprotocol/graph-node:latest 16 | ``` 17 | 18 | ### Example usage 19 | 20 | ```sh 21 | docker run -it \ 22 | -e postgres_host=host.docker.internal:5432 23 | -e postgres_user=graph-node \ 24 | -e postgres_pass=oh-hello \ 25 | -e postgres_db=graph-node \ 26 | -e ipfs=host.docker.internal:5001 \ 27 | -e ethereum=mainnet:http://localhost:8545/ \ 28 | graphprotocol/graph-node:latest 29 | ``` 30 | 31 | ## Docker Compose 32 | 33 | The Docker Compose setup requires an Ethereum network name and node 34 | to connect to. By default, it will use `mainnet:http://host.docker.internal:8545` 35 | in order to connect to an Ethereum node running on your host machine. 36 | You can replace this with anything else in `docker-compose.yaml`. 37 | 38 | > **Note for Linux users:** On Linux, `host.docker.internal` is not 39 | > currently supported. Instead, you will have to replace it with the 40 | > IP address of your Docker host (from the perspective of the Graph 41 | > Node container). 42 | > To do this, run: 43 | > 44 | > ``` 45 | > CONTAINER_ID=$(docker container ls | grep graph-node | cut -d' ' -f1) 46 | > docker exec $CONTAINER_ID /bin/bash -c 'ip route | awk \'/^default via /{print $3}\'' 47 | > ``` 48 | > 49 | > This will print the host's IP address. Then, put it into `docker-compose.yml`: 50 | > 51 | > ``` 52 | > sed -i -e 's/host.docker.internal//g' docker-compose.yml 53 | > ``` 54 | 55 | After you have set up an Ethereum node—e.g. Ganache or Parity—simply 56 | clone this repository and run 57 | 58 | ```sh 59 | docker-compose up 60 | ``` 61 | 62 | This will start IPFS, Postgres and Graph Node in Docker and create persistent 63 | data directories for IPFS and Postgres in `./data/ipfs` and `./data/postgres`. You 64 | can access these via: 65 | 66 | - Graph Node: 67 | - GraphiQL: `http://localhost:8000/` 68 | - HTTP: `http://localhost:8000/subgraphs/name/` 69 | - WebSockets: `ws://localhost:8001/subgraphs/name/` 70 | - Admin: `http://localhost:8020/` 71 | - IPFS: 72 | - `127.0.0.1:5001` or `/ip4/127.0.0.1/tcp/5001` 73 | - Postgres: 74 | - `postgresql://graph-node:let-me-in@localhost:5432/graph-node` 75 | 76 | Once this is up and running, you can use 77 | [`graph-cli`](https://github.com/graphprotocol/graph-cli) to create and 78 | deploy your subgraph to the running Graph Node. 79 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/create: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo "usage: create " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_create", "params": {"name":"%s"}, "id":"1"}' "$1") 11 | curl -s -H "content-type: application/json" --data "$data" "$api" 12 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/debug: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ -f "$1" ] 4 | then 5 | exec rust-gdb -c "$1" /usr/local/cargo/bin/graph-node 6 | else 7 | echo "usage: debug " 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/deploy: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 3 ]; then 4 | echo "usage: deploy " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | echo "Deploying $1 (deployment $2)" 11 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_deploy", "params": {"name":"%s", "ipfs_hash":"%s", "node_id":"%s"}, "id":"1"}' "$1" "$2" "$3") 12 | curl -s -H "content-type: application/json" --data "$data" "$api" 13 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/reassign: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: reassign " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | echo Assigning to "$3" 11 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_reassign", "params": {"name":"%s", "ipfs_hash":"%s", "node_id":"%s"}, "id":"1"}' "$1" "$2" "$3") 12 | curl -s -H "content-type: application/json" --data "$data" "$api" 13 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/remove: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo "usage: create " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_remove", "params": {"name":"%s"}, "id":"1"}' "$1") 11 | curl -s -H "content-type: application/json" --data "$data" "$api" 12 | -------------------------------------------------------------------------------- /packages/services/graph-node/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # This file is only here to ease testing/development. Official images are 4 | # built using the 'cloudbuild.yaml' file 5 | 6 | type -p podman > /dev/null && docker=podman || docker=docker 7 | 8 | cd $(dirname $0)/.. 9 | 10 | if [ -d .git ] 11 | then 12 | COMMIT_SHA=$(git rev-parse HEAD) 13 | TAG_NAME=$(git tag --points-at HEAD) 14 | REPO_NAME="Checkout of $(git remote get-url origin) at $(git describe --dirty)" 15 | BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) 16 | fi 17 | for stage in graph-node-build graph-node graph-node-debug 18 | do 19 | $docker build --target $stage \ 20 | --build-arg "COMMIT_SHA=$COMMIT_SHA" \ 21 | --build-arg "REPO_NAME=$REPO_NAME" \ 22 | --build-arg "BRANCH_NAME=$BRANCH_NAME" \ 23 | --build-arg "TAG_NAME=$TAG_NAME" \ 24 | -t $stage \ 25 | -f docker/Dockerfile . 26 | done 27 | -------------------------------------------------------------------------------- /packages/services/graph-node/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | machineType: "N1_HIGHCPU_32" 3 | timeout: 1800s 4 | steps: 5 | - name: 'gcr.io/cloud-builders/docker' 6 | args: ['build', '--target', 'graph-node-build', 7 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 8 | '--build-arg', 'REPO_NAME=$REPO_NAME', 9 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 10 | '--build-arg', 'TAG_NAME=$TAG_NAME', 11 | '-t', 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA', 12 | '-f', 'docker/Dockerfile', '.'] 13 | - name: 'gcr.io/cloud-builders/docker' 14 | args: ['build', '--target', 'graph-node', 15 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 16 | '--build-arg', 'REPO_NAME=$REPO_NAME', 17 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 18 | '--build-arg', 'TAG_NAME=$TAG_NAME', 19 | '-t', 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', 20 | '-f', 'docker/Dockerfile', '.'] 21 | - name: 'gcr.io/cloud-builders/docker' 22 | args: ['build', '--target', 'graph-node-debug', 23 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 24 | '--build-arg', 'REPO_NAME=$REPO_NAME', 25 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 26 | '--build-arg', 'TAG_NAME=$TAG_NAME', 27 | '-t', 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA', 28 | '-f', 'docker/Dockerfile', '.'] 29 | - name: 'gcr.io/cloud-builders/docker' 30 | args: ['tag', 31 | 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', 32 | 'lutter/graph-node:$SHORT_SHA'] 33 | - name: 'gcr.io/cloud-builders/docker' 34 | entrypoint: 'bash' 35 | args: ['docker/tag.sh'] 36 | secretEnv: ['PASSWORD'] 37 | env: 38 | - 'SHORT_SHA=$SHORT_SHA' 39 | - 'TAG_NAME=$TAG_NAME' 40 | - 'PROJECT_ID=$PROJECT_ID' 41 | - 'DOCKER_HUB_USER=$_DOCKER_HUB_USER' 42 | - 'BRANCH_NAME=$BRANCH_NAME' 43 | images: 44 | - 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA' 45 | - 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA' 46 | - 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA' 47 | substitutions: 48 | # The owner of the access token whose encrypted value is in PASSWORD 49 | _DOCKER_HUB_USER: "lutter" 50 | secrets: 51 | - kmsKeyName: projects/the-graph-staging/locations/global/keyRings/docker/cryptoKeys/docker-hub-push 52 | secretEnv: 53 | PASSWORD: 'CiQAdfFldbmUiHgGP1lPq6bAOfd+VQ/dFwyohB1IQwiwQg03ZE8STQDvWKpv6eJHVUN1YoFC5FcooJrH+Stvx9oMD7jBjgxEH5ngIiAysWP3E4Pgxt/73xnaanbM1EQ94eVFKCiY0GaEKFNu0BJx22vCYmU4' 54 | -------------------------------------------------------------------------------- /packages/services/graph-node/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | graph-node: 4 | image: graphprotocol/graph-node:latest 5 | ports: 6 | - "8000:8000" 7 | - "8001:8001" 8 | - "8020:8020" 9 | - "8030:8030" 10 | - "8040:8040" 11 | depends_on: 12 | - ipfs 13 | - postgres 14 | environment: 15 | postgres_host: postgres 16 | postgres_user: graph-node 17 | postgres_pass: let-me-in 18 | postgres_db: graph-node 19 | ipfs: "ipfs:5001" 20 | ethereum: "localhost:http://host.docker.internal:8545" 21 | GRAPH_LOG: info 22 | ipfs: 23 | image: ipfs/go-ipfs:v0.4.23 24 | ports: 25 | - "5001:5001" 26 | volumes: 27 | - ./data/ipfs:/data/ipfs 28 | postgres: 29 | image: postgres 30 | ports: 31 | - "5432:5432" 32 | command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] 33 | environment: 34 | POSTGRES_USER: graph-node 35 | POSTGRES_PASSWORD: let-me-in 36 | POSTGRES_DB: graph-node 37 | volumes: 38 | - ./data/postgres:/var/lib/postgresql/data 39 | -------------------------------------------------------------------------------- /packages/services/graph-node/hooks/post_checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | echo "Setting SOURCE_BRANCH to ${SOURCE_BRANCH}" 7 | 8 | sed -i "s@^ENV SOURCE_BRANCH \"master\"@ENV SOURCE_BRANCH \"${SOURCE_BRANCH}\"@g" Dockerfile 9 | -------------------------------------------------------------------------------- /packages/services/graph-node/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if ! which docker 2>&1 > /dev/null; then 6 | echo "Please install 'docker' first" 7 | exit 1 8 | fi 9 | 10 | if ! which docker-compose 2>&1 > /dev/null; then 11 | echo "Please install 'docker-compose' first" 12 | exit 1 13 | fi 14 | 15 | if ! which jq 2>&1 > /dev/null; then 16 | echo "Please install 'jq' first" 17 | exit 1 18 | fi 19 | 20 | # Create the graph-node container 21 | docker-compose up --no-start graph-node 22 | 23 | # Start graph-node so we can inspect it 24 | docker-compose start graph-node 25 | 26 | # Identify the container ID 27 | CONTAINER_ID=$(docker container ls | grep graph-node | cut -d' ' -f1) 28 | 29 | # Inspect the container to identify the host IP address 30 | HOST_IP=$(docker inspect "$CONTAINER_ID" | jq -r .[0].NetworkSettings.Networks[].Gateway) 31 | 32 | echo "Host IP: $HOST_IP" 33 | 34 | # Inject the host IP into docker-compose.yml 35 | sed -i -e "s/host.docker.internal/$HOST_IP/g" docker-compose.yml 36 | 37 | function stop_graph_node { 38 | # Ensure graph-node is stopped 39 | docker-compose stop graph-node 40 | } 41 | 42 | trap stop_graph_node EXIT 43 | -------------------------------------------------------------------------------- /packages/services/graph-node/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | save_coredumps() { 4 | graph_dir=/var/lib/graph 5 | datestamp=$(date +"%Y-%m-%dT%H:%M:%S") 6 | ls /core.* >& /dev/null && have_cores=yes || have_cores=no 7 | if [ -d "$graph_dir" -a "$have_cores" = yes ] 8 | then 9 | core_dir=$graph_dir/cores 10 | mkdir -p $core_dir 11 | exec >> "$core_dir"/messages 2>&1 12 | echo "${HOSTNAME##*-} Saving core dump on ${HOSTNAME} at ${datestamp}" 13 | 14 | dst="$core_dir/$datestamp-${HOSTNAME}" 15 | mkdir "$dst" 16 | cp /usr/local/bin/graph-node "$dst" 17 | cp /proc/loadavg "$dst" 18 | [ -f /Dockerfile ] && cp /Dockerfile "$dst" 19 | tar czf "$dst/etc.tgz" /etc/ 20 | dmesg -e > "$dst/dmesg" 21 | # Capture environment variables, but filter out passwords 22 | env | sort | sed -r -e 's/^(postgres_pass|ELASTICSEARCH_PASSWORD)=.*$/\1=REDACTED/' > "$dst/env" 23 | 24 | for f in /core.* 25 | do 26 | echo "${HOSTNAME##*-} Found core dump $f" 27 | mv "$f" "$dst" 28 | done 29 | echo "${HOSTNAME##*-} Saving done" 30 | fi 31 | } 32 | 33 | wait_for_ipfs() { 34 | # Take the IPFS URL in $1 apart and extract host and port. If no explicit 35 | # host is given, use 443 for https, and 80 otherwise 36 | if [[ "$1" =~ ^((https?)://)?([^:/]+)(:([0-9]+))? ]] 37 | then 38 | proto=${BASH_REMATCH[2]:-http} 39 | host=${BASH_REMATCH[3]} 40 | port=${BASH_REMATCH[5]} 41 | if [ -z "$port" ] 42 | then 43 | [ "$proto" = "https" ] && port=443 || port=80 44 | fi 45 | wait_for "$host:$port" -t 120 46 | else 47 | echo "invalid IPFS URL: $1" 48 | exit 1 49 | fi 50 | } 51 | 52 | start_query_node() { 53 | export DISABLE_BLOCK_INGESTOR=true 54 | graph-node \ 55 | --postgres-url "$postgres_url" \ 56 | --ethereum-rpc $ethereum \ 57 | --ipfs "$ipfs" 58 | } 59 | 60 | start_index_node() { 61 | # Only the index node with the name set in BLOCK_INGESTOR should ingest 62 | # blocks 63 | if [[ ${node_id} != "${BLOCK_INGESTOR}" ]]; then 64 | export DISABLE_BLOCK_INGESTOR=true 65 | fi 66 | 67 | graph-node \ 68 | --node-id "${node_id//-/_}" \ 69 | --postgres-url "$postgres_url" \ 70 | --ethereum-rpc $ethereum \ 71 | --ipfs "$ipfs" 72 | } 73 | 74 | start_combined_node() { 75 | graph-node \ 76 | --postgres-url "$postgres_url" \ 77 | --ethereum-rpc $ethereum \ 78 | --ipfs "$ipfs" 79 | } 80 | 81 | postgres_url="postgresql://$postgres_user:$postgres_pass@$postgres_host/$postgres_db" 82 | 83 | wait_for_ipfs "$ipfs" 84 | wait_for "$postgres_host:5432" -t 120 85 | sleep 5 86 | 87 | trap save_coredumps EXIT 88 | 89 | export PGAPPNAME="${node_id-$HOSTNAME}" 90 | 91 | case "${node_role-combined-node}" in 92 | query-node) 93 | start_query_node 94 | ;; 95 | index-node) 96 | start_index_node 97 | ;; 98 | combined-node) 99 | start_combined_node 100 | ;; 101 | *) 102 | echo "Unknown mode for start-node: $1" 103 | echo "usage: start (combined-node|query-node|index-node)" 104 | exit 1 105 | esac 106 | -------------------------------------------------------------------------------- /packages/services/graph-node/tag.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # This script is used by cloud build to push Docker images into Docker hub 4 | 5 | tag_and_push() { 6 | tag=$1 7 | docker tag gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA \ 8 | graphprotocol/graph-node:$tag 9 | docker push graphprotocol/graph-node:$tag 10 | 11 | docker tag gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA \ 12 | graphprotocol/graph-node-debug:$tag 13 | docker push graphprotocol/graph-node-debug:$tag 14 | } 15 | 16 | echo "Logging into Docker Hub" 17 | echo $PASSWORD | docker login --username="$DOCKER_HUB_USER" --password-stdin 18 | 19 | set -ex 20 | 21 | tag_and_push "$SHORT_SHA" 22 | 23 | # Builds on the master branch become the 'latest' 24 | [ "$BRANCH_NAME" = master ] && tag_and_push latest 25 | # Builds of tags set the tag in Docker Hub, too 26 | [ -n "$TAG_NAME" ] && tag_and_push "$TAG_NAME" 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /packages/services/graph-node/wait_for: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # POSIX compatible clone of wait-for-it.sh 4 | # This copy is from https://github.com/eficode/wait-for/commits/master 5 | # at commit 8d9b4446 6 | 7 | TIMEOUT=15 8 | QUIET=0 9 | 10 | echoerr() { 11 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi 12 | } 13 | 14 | usage() { 15 | exitcode="$1" 16 | cat << USAGE >&2 17 | Usage: 18 | $cmdname host:port [-t timeout] [-- command args] 19 | -q | --quiet Do not output any status messages 20 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout 21 | -- COMMAND ARGS Execute command with args after the test finishes 22 | USAGE 23 | exit "$exitcode" 24 | } 25 | 26 | wait_for() { 27 | for i in `seq $TIMEOUT` ; do 28 | nc -z "$HOST" "$PORT" > /dev/null 2>&1 29 | 30 | result=$? 31 | if [ $result -eq 0 ] ; then 32 | if [ $# -gt 0 ] ; then 33 | exec "$@" 34 | fi 35 | exit 0 36 | fi 37 | sleep 1 38 | done 39 | echo "Operation timed out" >&2 40 | exit 1 41 | } 42 | 43 | while [ $# -gt 0 ] 44 | do 45 | case "$1" in 46 | *:* ) 47 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1) 48 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2) 49 | shift 1 50 | ;; 51 | -q | --quiet) 52 | QUIET=1 53 | shift 1 54 | ;; 55 | -t) 56 | TIMEOUT="$2" 57 | if [ "$TIMEOUT" = "" ]; then break; fi 58 | shift 2 59 | ;; 60 | --timeout=*) 61 | TIMEOUT="${1#*=}" 62 | shift 1 63 | ;; 64 | --) 65 | shift 66 | break 67 | ;; 68 | --help) 69 | usage 0 70 | ;; 71 | *) 72 | echoerr "Unknown argument: $1" 73 | usage 1 74 | ;; 75 | esac 76 | done 77 | 78 | if [ "$HOST" = "" -o "$PORT" = "" ]; then 79 | echoerr "Error: you need to provide a host and port to test." 80 | usage 2 81 | fi 82 | 83 | wait_for "$@" 84 | -------------------------------------------------------------------------------- /packages/services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/services", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "submodule-init": "git submodule init && git submodule update --remote", 7 | "arbitrum-init": "cd arbitrum && git submodule init && git submodule update && yarn install", 8 | "arbitrum-build-l1": "cd arbitrum && yarn docker:build:geth", 9 | "arbitrum-run-l1": "cd arbitrum && yarn docker:geth", 10 | "arbitrum-init-l2": "cd arbitrum && yarn demo:initialize", 11 | "arbitrum-run-l2": "cd arbitrum && yarn demo:deploy", 12 | "run-optimism": "cd optimism/ops && make up", 13 | "stop-optimism": "cd optimism/ops && make down", 14 | "run-graph-node": "cd graph-node && docker-compose up", 15 | "remove-graph-node": "cd graph-node && docker-compose down", 16 | "clean-graph-node": "rm -rf graph-node/data/" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/subgraph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/subgraph", 3 | "license": "UNLICENSED", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "codegen": "graph codegen", 7 | "build": "graph build", 8 | "deploy": "graph deploy --node https://api.thegraph.com/deploy/ --ipfs https://api.thegraph.com/ipfs/ GITHUB_USERNAME/your-contract", 9 | "create-local": "graph create --node http://localhost:8020/ scaffold-eth/your-contract", 10 | "remove-local": "graph remove --node http://localhost:8020/ scaffold-eth/your-contract", 11 | "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract" 12 | }, 13 | "dependencies": { 14 | "@graphprotocol/graph-cli": "^0.22.1", 15 | "@graphprotocol/graph-ts": "^0.22.1" 16 | }, 17 | "devDependencies": { 18 | "mustache": "^3.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/subgraph/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, Address } from "@graphprotocol/graph-ts"; 2 | import { 3 | YourContract, 4 | SetPurpose, 5 | } from "../generated/YourContract/YourContract"; 6 | import { Purpose, Sender } from "../generated/schema"; 7 | 8 | export function handleSetPurpose(event: SetPurpose): void { 9 | let senderString = event.params.sender.toHexString(); 10 | 11 | let sender = Sender.load(senderString); 12 | 13 | if (sender === null) { 14 | sender = new Sender(senderString); 15 | sender.address = event.params.sender; 16 | sender.createdAt = event.block.timestamp; 17 | sender.purposeCount = BigInt.fromI32(1); 18 | } else { 19 | sender.purposeCount = sender.purposeCount.plus(BigInt.fromI32(1)); 20 | } 21 | 22 | let purpose = new Purpose( 23 | event.transaction.hash.toHex() + "-" + event.logIndex.toString() 24 | ); 25 | 26 | purpose.purpose = event.params.purpose; 27 | purpose.sender = senderString; 28 | purpose.createdAt = event.block.timestamp; 29 | purpose.transactionHash = event.transaction.hash.toHex(); 30 | 31 | purpose.save(); 32 | sender.save(); 33 | } 34 | -------------------------------------------------------------------------------- /packages/subgraph/src/schema.graphql: -------------------------------------------------------------------------------- 1 | type Purpose @entity { 2 | id: ID! 3 | sender: Sender! 4 | purpose: String! 5 | createdAt: BigInt! 6 | transactionHash: String! 7 | } 8 | 9 | type Sender @entity { 10 | id: ID! 11 | address: Bytes! 12 | purposes: [Purpose!] @derivedFrom(field: "sender") 13 | createdAt: BigInt! 14 | purposeCount: BigInt! 15 | } 16 | -------------------------------------------------------------------------------- /packages/subgraph/src/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | schema: 3 | file: ./src/schema.graphql 4 | dataSources: 5 | - kind: ethereum/contract 6 | name: YourContract 7 | network: localhost 8 | source: 9 | address: "{{localhost_YourContractAddress}}" 10 | abi: YourContract 11 | startBlock: 1 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.5 15 | language: wasm/assemblyscript 16 | entities: 17 | - Purpose 18 | - Sender 19 | abis: 20 | - name: YourContract 21 | file: ./abis/localhost_YourContract.json 22 | eventHandlers: 23 | - event: SetPurpose(address,string) 24 | handler: handleSetPurpose 25 | file: ./src/mapping.ts 26 | --------------------------------------------------------------------------------