├── .husky └── post-checkout ├── packages ├── react-app │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── scaffold-eth.png │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── App.css │ │ ├── ethereumLogo.png │ │ ├── moonbeamlogo.png │ │ ├── helpers │ │ │ ├── index.js │ │ │ ├── loadAppContracts.js │ │ │ ├── ipfs.js │ │ │ ├── Web3ModalSetup.js │ │ │ └── Transactor.js │ │ ├── index.css │ │ ├── views │ │ │ ├── index.js │ │ │ ├── Home.jsx │ │ │ ├── ExampleUI.jsx │ │ │ ├── Subgraph.jsx │ │ │ └── Hints.jsx │ │ ├── hooks │ │ │ ├── index.js │ │ │ ├── useDebounce.js │ │ │ ├── useContractConfig.js │ │ │ ├── GasPrice.js │ │ │ ├── useStaticJsonRPC.js │ │ │ ├── TokenList.js │ │ │ └── useLocalStorage.js │ │ ├── setupTests.js │ │ ├── App.test.js │ │ ├── themes │ │ │ ├── light-theme.less │ │ │ └── dark-theme.less │ │ ├── components │ │ │ ├── Blockie.jsx │ │ │ ├── Header.jsx │ │ │ ├── NetworkSwitch.jsx │ │ │ ├── GasGauge.jsx │ │ │ ├── ThemeSwitch.jsx │ │ │ ├── Contract │ │ │ │ ├── utils.jsx │ │ │ │ ├── DisplayVariable.jsx │ │ │ │ ├── index.jsx │ │ │ │ └── FunctionForm.jsx │ │ │ ├── Events.jsx │ │ │ ├── TokenBalance.jsx │ │ │ ├── index.js │ │ │ ├── FaucetHint.jsx │ │ │ ├── Balance.jsx │ │ │ ├── Provider.jsx │ │ │ ├── EtherInput.jsx │ │ │ ├── Faucet.jsx │ │ │ ├── BytesStringInput.jsx │ │ │ ├── Address.jsx │ │ │ ├── NetworkDisplay.jsx │ │ │ ├── Ramp.jsx │ │ │ ├── Timeline.jsx │ │ │ ├── Account.jsx │ │ │ ├── AddressInput.jsx │ │ │ ├── MultiAddressInput.jsx │ │ │ └── TokenSelect.jsx │ │ ├── index.jsx │ │ └── constants.js │ ├── .sample.env │ ├── .eslintignore │ ├── .prettierrc │ ├── scripts │ │ ├── create_contracts.js │ │ ├── watch.js │ │ ├── s3.js │ │ └── ipfs.js │ ├── gulpfile.js │ └── package.json ├── services │ ├── graph-node │ │ ├── bin │ │ │ ├── debug │ │ │ ├── create │ │ │ ├── remove │ │ │ ├── reassign │ │ │ └── deploy │ │ ├── hooks │ │ │ └── post_checkout │ │ ├── tag.sh │ │ ├── build.sh │ │ ├── docker-compose.yml │ │ ├── setup.sh │ │ ├── wait_for │ │ ├── cloudbuild.yaml │ │ ├── Dockerfile │ │ ├── README.md │ │ └── start │ └── package.json ├── subgraph │ ├── src │ │ ├── schema.graphql │ │ ├── subgraph.template.yaml │ │ └── mapping.ts │ └── package.json └── hardhat │ ├── scripts │ ├── watch.js │ ├── publish.js │ └── deploy.js │ ├── .eslintrc.js │ ├── contracts │ ├── YourContract.sol │ └── DelegationDAO.sol │ ├── example.env │ ├── test │ └── myTest.js │ ├── package.json │ └── deploy │ └── 00_deploy_your_contract.js ├── Delegation DAO Workshop.pdf ├── .editorconfig ├── .gitmodules ├── ISSUE_TEMPLATE ├── feature_request.md └── bug_report.md ├── .gitpod.yml ├── docker ├── setup.sh └── README.md ├── LICENSE ├── .vscode └── settings.json ├── .gitignore ├── README.md └── package.json /.husky/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn install 5 | -------------------------------------------------------------------------------- /packages/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /packages/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | padding-left: calc(100vw - 100%); 4 | } 5 | -------------------------------------------------------------------------------- /Delegation DAO Workshop.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/Delegation DAO Workshop.pdf -------------------------------------------------------------------------------- /packages/react-app/.sample.env: -------------------------------------------------------------------------------- 1 | # REACT_APP_PROVIDER=https://rinkeby.infura.io/v3/2717afb6bf164045b5d5468031b93f87 2 | -------------------------------------------------------------------------------- /packages/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/packages/react-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/packages/react-app/public/logo192.png -------------------------------------------------------------------------------- /packages/react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/packages/react-app/public/logo512.png -------------------------------------------------------------------------------- /packages/react-app/src/ethereumLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/packages/react-app/src/ethereumLogo.png -------------------------------------------------------------------------------- /packages/react-app/src/moonbeamlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/packages/react-app/src/moonbeamlogo.png -------------------------------------------------------------------------------- /packages/react-app/public/scaffold-eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyd628/delegation-dao-demo/HEAD/packages/react-app/public/scaffold-eth.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as Transactor } from "./Transactor"; 2 | export { default as Web3ModalSetup } from "./Web3ModalSetup"; 3 | export * as ipfs from "./ipfs"; 4 | -------------------------------------------------------------------------------- /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/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/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | 6 | body { 7 | margin: 0; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-app/src/views/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from "./Home"; 2 | export { default as ExampleUI } from "./ExampleUI"; 3 | export { default as Hints } from "./Hints"; 4 | export { default as Subgraph } from "./Subgraph"; 5 | -------------------------------------------------------------------------------- /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/react-app/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useDebounce } from "./useDebounce"; 2 | export { default as useLocalStorage } from "./useLocalStorage"; 3 | export { default as useStaticJsonRPC } from "./useStaticJsonRPC"; 4 | export * from "./useContractConfig"; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/useDebounce.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, delay]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /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/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/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/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/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/react-app/src/helpers/ipfs.js: -------------------------------------------------------------------------------- 1 | import { create } from "ipfs-http-client"; 2 | import { BufferList } from "bl"; 3 | export const ipfs = create({ host: "ipfs.infura.io", port: "5001", protocol: "https" }); 4 | 5 | export async function addToIPFS(file) { 6 | const fileAdded = await ipfs.add(file); 7 | 8 | return fileAdded; 9 | } 10 | 11 | export function urlFromCID(cid) { 12 | return `https://ipfs.infura.io/ipfs/${cid}`; 13 | } 14 | 15 | export async function getFromIPFS(hashToGet) { 16 | for await (const file of ipfs.cat(hashToGet)) { 17 | const content = new BufferList(file).toString(); 18 | 19 | return content; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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/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({link, title, subTitle}) { 7 | return ( 8 | 9 | 14 | 15 | ); 16 | } 17 | 18 | 19 | Header.defaultProps = { 20 | link: "https://github.com/austintgriffith/scaffold-eth", 21 | title: "🏗 scaffold-eth", 22 | subTitle: "forkable Ethereum dev stack focused on fast product iteration", 23 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 --hostname 0.0.0.0 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 | visibility: public 21 | github: 22 | prebuilds: 23 | pullRequestsFromForks: true 24 | addComment: true 25 | vscode: 26 | extensions: 27 | - dbaeumer.vscode-eslint 28 | - esbenp.prettier-vscode 29 | - juanblanco.solidity 30 | -------------------------------------------------------------------------------- /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: mbase 8 | source: 9 | address: "{{moonbaseAlpha_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/moonbaseAlpha_YourContract.json 22 | eventHandlers: 23 | - event: SetPurpose(address,string) 24 | handler: handleSetPurpose 25 | file: ./src/mapping.ts 26 | -------------------------------------------------------------------------------- /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/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/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/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/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() payable { 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 | // to support receiving ETH by default 25 | receive() external payable {} 26 | fallback() external payable {} 27 | } 28 | -------------------------------------------------------------------------------- /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/react-app/src/components/NetworkSwitch.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Dropdown, Menu, Button } from "antd"; 3 | 4 | function NetworkSwitch({ networkOptions, selectedNetwork, setSelectedNetwork }) { 5 | const menu = ( 6 | 7 | {networkOptions 8 | .filter(i => i !== selectedNetwork) 9 | .map(i => ( 10 | 11 | 14 | 15 | ))} 16 | 17 | ); 18 | 19 | return ( 20 |
21 | 22 | {selectedNetwork} 23 | 24 |
25 | ); 26 | } 27 | 28 | export default NetworkSwitch; 29 | -------------------------------------------------------------------------------- /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/react-app/src/hooks/useStaticJsonRPC.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | import { ethers } from "ethers"; 3 | 4 | const createProvider = async url => { 5 | const p = new ethers.providers.StaticJsonRpcProvider(url); 6 | 7 | await p.ready; 8 | 9 | return p; 10 | }; 11 | 12 | export default function useStaticJsonRPC(urlArray) { 13 | const [provider, setProvider] = useState(null); 14 | 15 | const handleProviders = useCallback(async () => { 16 | try { 17 | const p = await Promise.race(urlArray.map(createProvider)); 18 | const _p = await p; 19 | 20 | setProvider(_p); 21 | } catch (error) { 22 | // todo: show notification error about provider issues 23 | console.log(error); 24 | } 25 | }, [urlArray]); 26 | 27 | useEffect(() => { 28 | handleProviders(); 29 | // eslint-disable-next-line 30 | }, [JSON.stringify(urlArray)]); 31 | 32 | return provider; 33 | } 34 | -------------------------------------------------------------------------------- /packages/react-app/src/components/GasGauge.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "antd"; 3 | 4 | // added display of 0 instead of NaN if gas price is not provided 5 | 6 | /** 7 | ~ What it does? ~ 8 | 9 | Displays gas gauge 10 | 11 | ~ How can I use? ~ 12 | 13 | 16 | 17 | ~ Features ~ 18 | 19 | - Provide gasPrice={gasPrice} and get current gas gauge 20 | **/ 21 | 22 | export default function GasGauge(props) { 23 | return ( 24 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /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= -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, 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 | return ( 20 |
21 | {currentTheme === "light" ? "☀️" : "🌜"} 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /docker/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DOCKER_IMAGE=$(docker ps --filter name=SCAFFOLD_ETH -q) 4 | 5 | if [ "$1" = "start" ]; then 6 | # Run Docker container 7 | # to run the frontend on a different port add the "-e PORT=8080" parameter and change "-p 8080:8080" one. 8 | [ -z "$DOCKER_IMAGE" ] && docker run \ 9 | --name SCAFFOLD_ETH \ 10 | -v `pwd`:/opt/scaffold-eth \ 11 | -w /opt/scaffold-eth \ 12 | -p 3000:3000 \ 13 | -p 8545:8545 \ 14 | -dt node:16 || docker restart SCAFFOLD_ETH 15 | 16 | docker exec -ti SCAFFOLD_ETH bash -c "yarn install" 17 | docker exec -dt SCAFFOLD_ETH bash -c "yarn chain" 18 | sleep 5 19 | docker exec -ti SCAFFOLD_ETH bash -c "yarn deploy" 20 | docker exec -dt SCAFFOLD_ETH bash -c "yarn start" 21 | else 22 | if [ "$1" = "deploy" ]; then 23 | [ -z "$DOCKER_IMAGE" ] && echo "Container does not exist. Run the script with 'start' before running it with the 'deploy' option." \ 24 | || docker exec -ti SCAFFOLD_ETH bash -c "yarn deploy" 25 | else 26 | echo "Invalid command. Choose 'start' or 'deploy'." 27 | fi 28 | fi 29 | 30 | 31 | -------------------------------------------------------------------------------- /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: "mbase:https://rpc.api.moonbase.moonbeam.network" 21 | GRAPH_LOG: info 22 | ipfs: 23 | image: ipfs/go-ipfs:v0.10.0 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/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 { BrowserRouter } from "react-router-dom"; 5 | import ReactDOM from "react-dom"; 6 | import App from "./App"; 7 | import "./index.css"; 8 | 9 | const themes = { 10 | dark: `${process.env.PUBLIC_URL}/dark-theme.css`, 11 | light: `${process.env.PUBLIC_URL}/light-theme.css`, 12 | }; 13 | 14 | const prevTheme = window.localStorage.getItem("theme"); 15 | 16 | const subgraphUri = "http://localhost:8000/subgraphs/name/scaffold-eth/your-contract"; 17 | 18 | const client = new ApolloClient({ 19 | uri: subgraphUri, 20 | cache: new InMemoryCache(), 21 | }); 22 | 23 | ReactDOM.render( 24 | 25 | 26 | 27 | 28 | 29 | 30 | , 31 | document.getElementById("root"), 32 | ); 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/utils.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Address from "../Address"; 4 | 5 | const { utils } = require("ethers"); 6 | 7 | const tryToDisplay = (thing, asText = false, blockExplorer) => { 8 | if (thing && thing.toNumber) { 9 | try { 10 | return thing.toNumber(); 11 | } catch (e) { 12 | const displayable = "Ξ" + utils.formatUnits(thing, "ether"); 13 | return asText ? displayable : {displayable}; 14 | } 15 | } 16 | if (thing && thing.indexOf && thing.indexOf("0x") === 0 && thing.length === 42) { 17 | return asText ? thing :
; 18 | } 19 | if (thing && thing.constructor && thing.constructor.name === "Array") { 20 | const mostReadable = v => (["number", "boolean"].includes(typeof v) ? v : tryToDisplayAsText(v)); 21 | const displayable = JSON.stringify(thing.map(mostReadable)); 22 | return asText ? ( 23 | displayable 24 | ) : ( 25 | {displayable.replaceAll(",", ",\n")} 26 | ); 27 | } 28 | return JSON.stringify(thing); 29 | }; 30 | 31 | const tryToDisplayAsText = thing => tryToDisplay(thing, true); 32 | 33 | export { tryToDisplay, tryToDisplayAsText }; 34 | -------------------------------------------------------------------------------- /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 | it("Should emit a SetPurpose event ", async function () { 31 | const [owner] = await ethers.getSigners(); 32 | 33 | const newPurpose = "Another Test Purpose"; 34 | 35 | expect(await myContract.setPurpose(newPurpose)) 36 | .to.emit(myContract, "SetPurpose") 37 | .withArgs(owner.address, newPurpose); 38 | }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Events.jsx: -------------------------------------------------------------------------------- 1 | import { List } from "antd"; 2 | import { useEventListener } from "eth-hooks/events/useEventListener"; 3 | import Address from "./Address"; 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/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/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 | export { default as NetworkDisplay } from "./NetworkDisplay"; 22 | export { default as FaucetHint } from "./FaucetHint"; 23 | export { default as NetworkSwitch } from "./NetworkSwitch"; 24 | export { default as MultiAddressInput } from "./MultiAddressInput"; 25 | export { default as TokenSelect } from "./TokenSelect"; 26 | -------------------------------------------------------------------------------- /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.tokens; 36 | } 37 | 38 | setTokenList(_tokenList); 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/components/Contract/DisplayVariable.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Col, Divider, Row } from "antd"; 2 | import React, { useCallback, useEffect, useState } from "react"; 3 | 4 | import { tryToDisplay } from "./utils"; 5 | 6 | const DisplayVariable = ({ contractFunction, functionInfo, refreshRequired, triggerRefresh, blockExplorer }) => { 7 | const [variable, setVariable] = useState(""); 8 | 9 | const refresh = useCallback(async () => { 10 | try { 11 | const funcResponse = await contractFunction(); 12 | setVariable(funcResponse); 13 | triggerRefresh(false); 14 | } catch (e) { 15 | console.log(e); 16 | } 17 | }, [setVariable, contractFunction, triggerRefresh]); 18 | 19 | useEffect(() => { 20 | refresh(); 21 | }, [refresh, refreshRequired, contractFunction]); 22 | 23 | return ( 24 |
25 | 26 | 35 | {functionInfo.name} 36 | 37 | 38 |

{tryToDisplay(variable, false, blockExplorer)}

39 | 40 | 41 |

42 |

44 | 45 |
46 | 47 |
48 | ); 49 | }; 50 | 51 | export default DisplayVariable; 52 | -------------------------------------------------------------------------------- /packages/react-app/src/components/FaucetHint.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd"; 2 | import React, { useState } from "react"; 3 | import { ethers } from "ethers"; 4 | import { useBalance, useGasPrice } from "eth-hooks"; 5 | 6 | import { Transactor } from "../helpers"; 7 | 8 | function FaucetHint({ localProvider, targetNetwork, address }) { 9 | const [faucetClicked, setFaucetClicked] = useState(false); 10 | 11 | // fetch local balance 12 | const yourLocalBalance = useBalance(localProvider, address); 13 | 14 | // get gas Price from network 15 | const gasPrice = useGasPrice(targetNetwork, "fast"); 16 | 17 | // Faucet Tx can be used to send funds from the faucet 18 | const faucetTx = Transactor(localProvider, gasPrice); 19 | 20 | let faucetHint = ""; 21 | 22 | if ( 23 | !faucetClicked && 24 | localProvider && 25 | localProvider._network && 26 | localProvider._network.chainId === 31337 && 27 | yourLocalBalance && 28 | ethers.utils.formatEther(yourLocalBalance) <= 0 29 | ) { 30 | faucetHint = ( 31 |
32 | 44 |
45 | ); 46 | } 47 | 48 | return faucetHint; 49 | } 50 | 51 | export default FaucetHint; 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌕 Useful Links 2 | 3 | [Presentation Slides](https://github.com/hyd628/delegation-dao-demo/blob/master/Delegation%20DAO%20Workshop.pdf) 4 | 5 | [Moonbeam Developer Documentation](https://docs.moonbeam.network/) 6 | 7 | [Smart Contracts Used](https://github.com/hyd628/delegation-dao-demo/tree/master/packages/hardhat/contracts) 8 | 9 | [Connect to Moonbase Alpha RPC](https://docs.moonbeam.network/builders/get-started/networks/moonbase/#network-endpoints) 10 | 11 | [Moonbase Alpha Faucet](https://discord.gg/SZNP8bWHZq) 12 | 13 | [Moonscan for Moonbase Alpha](https://moonbase.moonscan.io/) 14 | 15 | [Subscan for Moonbase Alpha](https://moonbase.subscan.io/) 16 | 17 | [Remix IDE](https://remix.ethereum.org/) 18 | 19 | [Scaffold-ETH](https://github.com/scaffold-eth/scaffold-eth) 20 | 21 | # 🏄‍♂️ Quick Start 22 | 23 | Prerequisites: [Node (v16 LTS)](https://nodejs.org/en/download/) plus [Yarn](https://classic.yarnpkg.com/en/docs/install/) and [Git](https://git-scm.com/downloads) 24 | 25 | > Clone and install dependencies: 26 | 27 | ```bash 28 | git clone https://github.com/hyd628/delegation-dao-demo.git 29 | yarn install 30 | ``` 31 | 32 | > Add deployment account's mnemonic to /delegation-dao-demo/packages/hardhat/mnemonic.txt 33 | 34 | > Deploy your contracts: 35 | 36 | ```bash 37 | yarn deploy 38 | ``` 39 | 40 | > In a second terminal window, start your 📱 frontend: 41 | 42 | ```bash 43 | yarn start 44 | ``` 45 | 46 | 🔏 Edit your smart contract in `packages/hardhat/contracts` 47 | 48 | 📝 Edit your frontend `App.jsx` in `packages/react-app/src` 49 | 50 | 💼 Edit your deployment scripts in `packages/hardhat/deploy` 51 | 52 | 📱 Open http://localhost:3000 to see the app 53 | -------------------------------------------------------------------------------- /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 | "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers", 15 | "@nomiclabs/hardhat-waffle": "^2.0.0", 16 | "@openzeppelin/contracts": "^4.4.2", 17 | "@tenderly/hardhat-tenderly": "^1.0.10", 18 | "@nomiclabs/hardhat-etherscan": "^3.0.1", 19 | "chai": "^4.2.0", 20 | "chalk": "^4.1.0", 21 | "dotenv": "^8.2.0", 22 | "ethereum-waffle": "^3.1.1", 23 | "ethers": "^5.4.4", 24 | "hardhat": "2.6.0", 25 | "hardhat-deploy": "^0.9.0", 26 | "hardhat-gas-reporter": "^1.0.4", 27 | "node-watch": "^0.7.0", 28 | "qrcode-terminal": "^0.12.0", 29 | "ramda": "^0.27.1" 30 | }, 31 | "scripts": { 32 | "chain": "hardhat node --network hardhat --no-deploy", 33 | "fork": "hardhat node --no-deploy --network hardhat --fork https://mainnet.infura.io/v3/460f40a260564ac4a4f4b3fffb032dad", 34 | "test": "hardhat test --network hardhat", 35 | "compile": "hardhat compile", 36 | "deploy": "hardhat deploy --export-all ../react-app/src/contracts/hardhat_contracts.json", 37 | "postdeploy": "hardhat run scripts/publish.js", 38 | "watch": "node scripts/watch.js", 39 | "accounts": "hardhat accounts", 40 | "balance": "hardhat balance", 41 | "send": "hardhat send", 42 | "generate": "hardhat generate", 43 | "account": "hardhat account", 44 | "verify": "hardhat verify" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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/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/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 balance = useBalance(props.provider, props.address); 36 | let floatBalance = parseFloat("0.00"); 37 | let usingBalance = balance; 38 | 39 | if (typeof props.balance !== "undefined") usingBalance = props.balance; 40 | if (typeof props.value !== "undefined") usingBalance = props.value; 41 | 42 | if (usingBalance) { 43 | const etherBalance = utils.formatEther(usingBalance); 44 | parseFloat(etherBalance).toFixed(2); 45 | floatBalance = parseFloat(etherBalance); 46 | } 47 | 48 | let displayBalance = floatBalance.toFixed(4); 49 | 50 | const price = props.price || props.dollarMultiplier || 1; 51 | 52 | if (dollarMode) { 53 | displayBalance = "$" + (floatBalance * price).toFixed(2); 54 | } 55 | 56 | return ( 57 | { 65 | setDollarMode(!dollarMode); 66 | }} 67 | > 68 | {displayBalance} 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # 🏄‍♂️ Using Docker 2 | 3 | Prerequisites: 4 | - [Docker](https://docs.docker.com/engine/install/) 5 | - [Git](https://git-scm.com/) 6 | - Bash Shell: available in macOS by default and the vast majority of Linux distros 7 | 8 | ***Note**: If you are using a Windows environment, you can use [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/) or a Bash emulator like "Git BASH" (which its included in [Git for Windows](https://gitforwindows.org/)). If you use WSL take into account that you should [configure Docker to use the WSL 2 backend](https://docs.docker.com/desktop/windows/wsl/).* 9 | 10 | 11 | > clone/fork 🏗 scaffold-eth: 12 | 13 | ```bash 14 | git clone https://github.com/scaffold-eth/scaffold-eth.git 15 | ``` 16 | 17 | > [basic] run the script that sets the stack up and that's it (takes some minutes to finish): 18 | 19 | ```bash 20 | cd scaffold-eth 21 | ./docker/setup.sh start 22 | ``` 23 | 24 | > [basic] to re-deploy your contracts (container must be up and running): 25 | 26 | ```bash 27 | ./docker/setup.sh deploy 28 | ``` 29 | 30 | > [advanced] running front-end on a different port (eg. 8080): 31 | 32 | ```bash 33 | DOCKER_IMAGE=$(docker ps --filter name=SCAFFOLD_ETH -q) 34 | [ -z "$DOCKER_IMAGE" ] || docker rm -f SCAFFOLD_ETH 35 | 36 | docker run \ 37 | --name SCAFFOLD_ETH \ 38 | -v `pwd`:/opt/scaffold-eth \ 39 | -w /opt/scaffold-eth \ 40 | -e PORT=8080 \ 41 | -p 8080:8080 \ 42 | -p 8545:8545 \ 43 | -dt node:16 44 | 45 | ./docker/setup.sh start 46 | ``` 47 | 48 | > [advanced] running the container in interactive mode (must run each tool manually): 49 | 50 | ```bash 51 | DOCKER_IMAGE=$(docker ps --filter name=SCAFFOLD_ETH -q) 52 | [ -z "$DOCKER_IMAGE" ] || docker rm -f SCAFFOLD_ETH 53 | 54 | docker run \ 55 | --name SCAFFOLD_ETH \ 56 | -v `pwd`:/opt/scaffold-eth \ 57 | -w /opt/scaffold-eth \ 58 | -p 3000:3000 \ 59 | -p 8545:8545 \ 60 | --entrypoint /bin/bash \ 61 | -ti node:16 62 | ``` 63 | 64 | 🔏 Edit your smart contract `YourContract.sol` in `packages/hardhat/contracts` 65 | 66 | 📝 Edit your frontend `App.jsx` in `packages/react-app/src` 67 | 68 | 💼 Edit your deployment scripts in `packages/hardhat/deploy` 69 | 70 | 📱 Open http://localhost:3000 to see the app 71 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/useLocalStorage.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/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/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/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 | /* 9 | const invalidation = { 10 | awsDistributionId: "E224H0HK9AWILY", 11 | awsInvalidationPath: "/*" 12 | } 13 | */ 14 | 15 | if (!BUCKETNAME) { 16 | console.log("☢️ Enter a bucket name in packages/react-app/scripts/s3.js "); 17 | process.exit(1); 18 | } 19 | 20 | let credentials = {}; 21 | try { 22 | credentials = JSON.parse(fs.readFileSync("aws.json")); 23 | } catch (e) { 24 | console.log(e); 25 | console.log( 26 | '☢️ Create an aws.json credentials file in packages/react-app/ like { "accessKeyId": "xxx", "secretAccessKey": "xxx", "region": "xxx" } ', 27 | ); 28 | process.exit(1); 29 | } 30 | 31 | credentials.bucket = BUCKETNAME; 32 | 33 | // optional options to be passed as parameter to the method 34 | const options = { 35 | useFoldersForFileTypes: false, 36 | useIAMRoleCredentials: false, 37 | }; 38 | 39 | ///////////// 40 | ///////////// First, let's automatically create the bucket if it doesn't exist... 41 | ///////////// 42 | 43 | var AWS = require('aws-sdk'); 44 | // Load credentials and set Region from JSON file 45 | AWS.config.loadFromPath('./aws.json'); 46 | 47 | // Create S3 service object 48 | s3 = new AWS.S3({apiVersion: '2006-03-01'}); 49 | 50 | // Create params JSON for S3.createBucket 51 | var bucketParams = { 52 | Bucket : BUCKETNAME, 53 | ACL : 'public-read' 54 | }; 55 | 56 | // Create params JSON for S3.setBucketWebsite 57 | var staticHostParams = { 58 | Bucket: BUCKETNAME, 59 | WebsiteConfiguration: { 60 | ErrorDocument: { 61 | Key: 'index.html' 62 | }, 63 | IndexDocument: { 64 | Suffix: 'index.html' 65 | }, 66 | } 67 | }; 68 | 69 | // Call S3 to create the bucket 70 | s3.createBucket(bucketParams, function(err, data) { 71 | if (err) { 72 | console.log("Error", err); 73 | } else { 74 | console.log("Bucket URL is ", data.Location); 75 | // Set the new policy on the newly created bucket 76 | s3.putBucketWebsite(staticHostParams, function(err, data) { 77 | if (err) { 78 | // Display error message 79 | console.log("Error", err); 80 | } else { 81 | // Update the displayed policy for the selected bucket 82 | console.log("Success... UPLOADING!", data); 83 | 84 | /// 85 | /// After the bucket is created, we upload to it: 86 | /// 87 | s3FolderUpload(directoryName, credentials, options /* , invalidation */); 88 | 89 | } 90 | }); 91 | } 92 | }); 93 | -------------------------------------------------------------------------------- /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/react-app/src/components/Provider.jsx: -------------------------------------------------------------------------------- 1 | import { Badge, Button } from "antd"; 2 | import React, { useState } from "react"; 3 | import { useBlockNumber, usePoller } from "eth-hooks"; 4 | // import { WalletOutlined } from '@ant-design/icons'; 5 | 6 | import Address from "./Address"; 7 | 8 | export default function Provider(props) { 9 | const [showMore, setShowMore] = useState(false); 10 | const [status, setStatus] = useState("processing"); 11 | const [network, setNetwork] = useState(); 12 | const [signer, setSigner] = useState(); 13 | const [address, setAddress] = useState(); 14 | 15 | const blockNumber = useBlockNumber(props.provider); 16 | 17 | usePoller(async () => { 18 | if (props.provider && typeof props.provider.getNetwork === "function") { 19 | try { 20 | const newNetwork = await props.provider.getNetwork(); 21 | setNetwork(newNetwork); 22 | if (newNetwork.chainId > 0) { 23 | setStatus("success"); 24 | } else { 25 | setStatus("warning"); 26 | } 27 | } catch (e) { 28 | console.log(e); 29 | setStatus("processing"); 30 | } 31 | try { 32 | const newSigner = await props.provider.getSigner(); 33 | setSigner(newSigner); 34 | const newAddress = await newSigner.getAddress(); 35 | setAddress(newAddress); 36 | // eslint-disable-next-line no-empty 37 | } catch (e) {} 38 | } 39 | }, 1377); 40 | 41 | if ( 42 | typeof props.provider === "undefined" || 43 | typeof props.provider.getNetwork !== "function" || 44 | !network || 45 | !network.chainId 46 | ) { 47 | return ( 48 | 57 | ); 58 | } 59 | 60 | let showExtra = ""; 61 | if (showMore) { 62 | showExtra = ( 63 | 64 | 65 | id: 66 | {network ? network.chainId : ""} 67 | 68 | 69 | name: 70 | {network ? network.name : ""} 71 | 72 | 73 | ); 74 | } 75 | 76 | let showWallet = ""; 77 | if (typeof signer !== "undefined" && address) { 78 | showWallet = ( 79 | 80 | 81 |
82 | 83 | 84 | ); 85 | } 86 | 87 | return ( 88 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /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 | // const sleep = (ms) => 8 | // new Promise((r) => 9 | // setTimeout(() => { 10 | // console.log(`waited for ${(ms / 1000).toFixed(3)} seconds`); 11 | // r(); 12 | // }, ms) 13 | // ); 14 | 15 | module.exports = async ({ getNamedAccounts, deployments, getChainId }) => { 16 | const { deploy } = deployments; 17 | const { deployer } = await getNamedAccounts(); 18 | const chainId = await getChainId(); 19 | 20 | await deploy("DelegationDAO", { 21 | // Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy 22 | from: deployer, 23 | args: ["0x9658121Ae8e9Afb2a0134D8C3B5121aF915B10B0", "0x578002f699722394afc52169069a1FfC98DA36f1"], 24 | log: true, 25 | waitConfirmations: 5, 26 | }); 27 | 28 | // Getting a previously deployed contract 29 | const YourContract = await ethers.getContract("DelegationDAO", deployer); 30 | /* await YourContract.setPurpose("Hello"); 31 | 32 | To take ownership of yourContract using the ownable library uncomment next line and add the 33 | address you want to be the owner. 34 | // await yourContract.transferOwnership(YOUR_ADDRESS_HERE); 35 | 36 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 37 | */ 38 | 39 | /* 40 | //If you want to send value to an address from the deployer 41 | const deployerWallet = ethers.provider.getSigner() 42 | await deployerWallet.sendTransaction({ 43 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 44 | value: ethers.utils.parseEther("0.001") 45 | }) 46 | */ 47 | 48 | /* 49 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 50 | const yourContract = await deploy("YourContract", [], { 51 | value: ethers.utils.parseEther("0.05") 52 | }); 53 | */ 54 | 55 | /* 56 | //If you want to link a library into your contract: 57 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 58 | const yourContract = await deploy("YourContract", [], {}, { 59 | LibraryName: **LibraryAddress** 60 | }); 61 | */ 62 | 63 | // Verify from the command line by running `yarn verify` 64 | 65 | // You can also Verify your contracts with Etherscan here... 66 | // You don't want to verify on localhost 67 | // try { 68 | // if (chainId !== localChainId) { 69 | // await run("verify:verify", { 70 | // address: YourContract.address, 71 | // contract: "contracts/YourContract.sol:YourContract", 72 | // constructorArguments: [], 73 | // }); 74 | // } 75 | // } catch (error) { 76 | // console.error(error); 77 | // } 78 | }; 79 | module.exports.tags = ["YourContract"]; 80 | -------------------------------------------------------------------------------- /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/react-app/scripts/ipfs.js: -------------------------------------------------------------------------------- 1 | const {create, globSource} = require("ipfs-http-client"); 2 | const chalk = require("chalk"); 3 | const { clearLine } = require("readline"); 4 | 5 | const infura = { host: "ipfs.infura.io", port: "5001", protocol: "https" }; 6 | // run your own ipfs daemon: https://docs.ipfs.io/how-to/command-line-quick-start/#install-ipfs 7 | // const localhost = { host: "localhost", port: "5001", protocol: "http" }; 8 | 9 | const ipfs = create(infura); 10 | 11 | const ipfsGateway = "https://ipfs.io/ipfs/"; 12 | const ipnsGateway = "https://ipfs.io/ipns/"; 13 | 14 | const addOptions = { 15 | pin: true, 16 | wrapWithDirectory: true 17 | }; 18 | 19 | const pushDirectoryToIPFS = async path => { 20 | try { 21 | const file = ipfs.addAll(globSource(path, '**/*'), addOptions) 22 | let lastRes; 23 | for await (const f of file) { 24 | lastRes = f 25 | } 26 | return lastRes 27 | } catch (e) { 28 | return {}; 29 | } 30 | }; 31 | 32 | const publishHashToIPNS = async ipfsHash => { 33 | try { 34 | const response = await ipfs.name.publish(`/ipfs/${ipfsHash}`); 35 | return response; 36 | } catch (e) { 37 | return {}; 38 | } 39 | }; 40 | 41 | const nodeMayAllowPublish = ipfsClient => { 42 | // You must have your own IPFS node in order to publish an IPNS name 43 | // This contains a blacklist of known nodes which do not allow users to publish IPNS names. 44 | const nonPublishingNodes = ["ipfs.infura.io"]; 45 | const { host } = ipfsClient.getEndpointConfig(); 46 | return !nonPublishingNodes.some(nodeUrl => host.includes(nodeUrl)); 47 | }; 48 | 49 | const deploy = async () => { 50 | console.log("🛰 Sending to IPFS..."); 51 | const { cid } = await pushDirectoryToIPFS("./build"); 52 | if (!cid) { 53 | console.log(`📡 App deployment failed`); 54 | return false; 55 | } 56 | console.log(`📡 App deployed to IPFS with hash: ${chalk.cyan(cid.toString())}`); 57 | 58 | console.log(); 59 | 60 | let ipnsName = ""; 61 | if (nodeMayAllowPublish(ipfs)) { 62 | console.log(`✍️ Publishing /ipfs/${cid.toString()} to IPNS...`); 63 | process.stdout.write(" Publishing to IPNS can take up to roughly two minutes.\r"); 64 | ipnsName = (await publishHashToIPNS(cid.toString())).name; 65 | clearLine(process.stdout, 0); 66 | if (!ipnsName) { 67 | console.log(" Publishing IPNS name on node failed."); 68 | } 69 | console.log(`🔖 App published to IPNS with name: ${chalk.cyan(ipnsName)}`); 70 | console.log(); 71 | } 72 | 73 | console.log("🚀 Deployment to IPFS complete!"); 74 | console.log(); 75 | 76 | console.log(`Use the link${ipnsName && "s"} below to access your app:`); 77 | console.log(` IPFS: ${chalk.cyan(`${ipfsGateway}${cid.toString()}`)}`); 78 | if (ipnsName) { 79 | console.log(` IPNS: ${chalk.cyan(`${ipnsGateway}${ipnsName}`)}`); 80 | console.log(); 81 | console.log( 82 | "Each new deployment will have a unique IPFS hash while the IPNS name will always point at the most recent deployment.", 83 | ); 84 | console.log( 85 | "It is recommended that you share the IPNS link so that people always see the newest version of your app.", 86 | ); 87 | } 88 | console.log(); 89 | return true; 90 | }; 91 | 92 | deploy(); 93 | -------------------------------------------------------------------------------- /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 | useEffect(() => { 39 | if (!currentValue) { 40 | setDisplay(""); 41 | } 42 | }, [currentValue]); 43 | 44 | return ( 45 | { 57 | if (mode === "USD") { 58 | setMode("ETH"); 59 | setDisplay(currentValue); 60 | } else { 61 | setMode("USD"); 62 | if (currentValue) { 63 | const usdValue = "" + (parseFloat(currentValue) * props.price).toFixed(2); 64 | setDisplay(usdValue); 65 | } else { 66 | setDisplay(currentValue); 67 | } 68 | } 69 | }} 70 | > 71 | {mode === "USD" ? "USD 🔀" : "ETH 🔀"} 72 | 73 | ) 74 | } 75 | onChange={async e => { 76 | const newValue = e.target.value; 77 | if (mode === "USD") { 78 | const possibleNewValue = parseFloat(newValue); 79 | if (possibleNewValue) { 80 | const ethValue = possibleNewValue / props.price; 81 | setValue(ethValue); 82 | if (typeof props.onChange === "function") { 83 | props.onChange(ethValue); 84 | } 85 | setDisplay(newValue); 86 | } else { 87 | setDisplay(newValue); 88 | } 89 | } else { 90 | setValue(newValue); 91 | if (typeof props.onChange === "function") { 92 | props.onChange(newValue); 93 | } 94 | setDisplay(newValue); 95 | } 96 | }} 97 | /> 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/Web3ModalSetup.js: -------------------------------------------------------------------------------- 1 | import Portis from "@portis/web3"; 2 | import WalletConnectProvider from "@walletconnect/web3-provider"; 3 | import Authereum from "authereum"; 4 | import Fortmatic from "fortmatic"; 5 | import WalletLink from "walletlink"; 6 | import Web3Modal from "web3modal"; 7 | import { ALCHEMY_KEY, INFURA_ID } from "../constants"; 8 | 9 | // Coinbase walletLink init 10 | const walletLink = new WalletLink({ 11 | appName: "coinbase", 12 | }); 13 | 14 | // WalletLink provider 15 | const walletLinkProvider = walletLink.makeWeb3Provider(`https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`, 1); 16 | 17 | // Portis ID: 6255fb2b-58c8-433b-a2c9-62098c05ddc9 18 | /** 19 | Web3 modal helps us "connect" external wallets: 20 | **/ 21 | const web3ModalSetup = () => 22 | new Web3Modal({ 23 | network: "mainnet", // Optional. If using WalletConnect on xDai, change network to "xdai" and add RPC info below for xDai chain. 24 | cacheProvider: true, // optional 25 | theme: "light", // optional. Change to "dark" for a dark theme. 26 | providerOptions: { 27 | walletconnect: { 28 | package: WalletConnectProvider, // required 29 | options: { 30 | bridge: "https://polygon.bridge.walletconnect.org", 31 | infuraId: INFURA_ID, 32 | rpc: { 33 | 10: "https://mainnet.optimism.io", // xDai 34 | 100: "https://rpc.gnosischain.com", // xDai 35 | 137: "https://polygon-rpc.com", 36 | 31337: "http://localhost:8545", 37 | 42161: "https://arb1.arbitrum.io/rpc", 38 | 80001: "https://rpc-mumbai.maticvigil.com", 39 | }, 40 | }, 41 | }, 42 | portis: { 43 | display: { 44 | logo: "https://user-images.githubusercontent.com/9419140/128913641-d025bc0c-e059-42de-a57b-422f196867ce.png", 45 | name: "Portis", 46 | description: "Connect to Portis App", 47 | }, 48 | package: Portis, 49 | options: { 50 | id: "6255fb2b-58c8-433b-a2c9-62098c05ddc9", 51 | }, 52 | }, 53 | fortmatic: { 54 | package: Fortmatic, // required 55 | options: { 56 | key: "pk_live_5A7C91B2FC585A17", // required 57 | }, 58 | }, 59 | // torus: { 60 | // package: Torus, 61 | // options: { 62 | // networkParams: { 63 | // host: "https://localhost:8545", // optional 64 | // chainId: 1337, // optional 65 | // networkId: 1337 // optional 66 | // }, 67 | // config: { 68 | // buildEnv: "development" // optional 69 | // }, 70 | // }, 71 | // }, 72 | "custom-walletlink": { 73 | display: { 74 | logo: "https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0", 75 | name: "Coinbase", 76 | description: "Connect to Coinbase Wallet (not Coinbase App)", 77 | }, 78 | package: walletLinkProvider, 79 | connector: async (provider, _options) => { 80 | await provider.enable(); 81 | return provider; 82 | }, 83 | }, 84 | authereum: { 85 | package: Authereum, // required 86 | }, 87 | }, 88 | }); 89 | 90 | export default web3ModalSetup; 91 | -------------------------------------------------------------------------------- /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/react-app/src/components/Faucet.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Tooltip } from "antd"; 2 | import React, { useState, useEffect } from "react"; 3 | import Blockies from "react-blockies"; 4 | import { SendOutlined } from "@ant-design/icons"; 5 | import { Transactor } from "../helpers"; 6 | import Wallet from "./Wallet"; 7 | 8 | const { utils } = require("ethers"); 9 | 10 | // improved a bit by converting address to ens if it exists 11 | // added option to directly input ens name 12 | // added placeholder option 13 | 14 | /** 15 | ~ What it does? ~ 16 | 17 | Displays a local faucet to send ETH to given address, also wallet is provided 18 | 19 | ~ How can I use? ~ 20 | 21 | 27 | 28 | ~ Features ~ 29 | 30 | - Provide price={price} of ether and convert between USD and ETH in a wallet 31 | - Provide localProvider={localProvider} to be able to send ETH to given address 32 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 33 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 34 | works both in input field & wallet 35 | - Provide placeholder="Send local faucet" value for the input 36 | **/ 37 | 38 | export default function Faucet(props) { 39 | const [address, setAddress] = useState(); 40 | const [faucetAddress, setFaucetAddress] = useState(); 41 | 42 | const { price, placeholder, localProvider, ensProvider } = props; 43 | 44 | useEffect(() => { 45 | const getFaucetAddress = async () => { 46 | if (localProvider) { 47 | const _faucetAddress = await localProvider.listAccounts(); 48 | setFaucetAddress(_faucetAddress[0]); 49 | } 50 | }; 51 | getFaucetAddress(); 52 | }, [localProvider]); 53 | 54 | let blockie; 55 | if (address && typeof address.toLowerCase === "function") { 56 | blockie = ; 57 | } else { 58 | blockie =
; 59 | } 60 | 61 | const updateAddress = newValue => { 62 | if (typeof newValue !== "undefined" && utils.isAddress(newValue)) { 63 | setAddress(newValue); 64 | } 65 | }; 66 | 67 | const tx = Transactor(localProvider); 68 | 69 | return ( 70 | 71 | updateAddress(e.target.value)} 77 | suffix={ 78 | 79 |
34 | ); 35 | } else { 36 | networkDisplay = ( 37 |
38 | 42 | You have {networkSelected && networkSelected.name} selected and you need to be on{" "} 43 | 83 |
84 | } 85 | type="error" 86 | closable={false} 87 | /> 88 | 89 | ); 90 | } 91 | } else { 92 | networkDisplay = USE_NETWORK_SELECTOR ? null : ( 93 |
94 | {targetNetwork.name} 95 |
96 | ); 97 | } 98 | 99 | console.log({ networkDisplay }); 100 | 101 | return networkDisplay; 102 | } 103 | 104 | export default NetworkDisplay; 105 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Ramp.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Divider, Modal } from "antd"; 2 | import React, { useState } from "react"; 3 | import { DollarCircleOutlined } from "@ant-design/icons"; 4 | import { RampInstantSDK } from "@ramp-network/ramp-instant-sdk"; 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/Timeline.jsx: -------------------------------------------------------------------------------- 1 | import { Timeline, Typography } from "antd"; 2 | import React from "react"; 3 | import Blockies from "react-blockies"; 4 | import { DownloadOutlined, EditOutlined, SendOutlined } from "@ant-design/icons"; 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/Contract/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from "antd"; 2 | import { useContractExistsAtAddress, useContractLoader } from "eth-hooks"; 3 | import React, { useMemo, useState } from "react"; 4 | import Address from "../Address"; 5 | import Balance from "../Balance"; 6 | import DisplayVariable from "./DisplayVariable"; 7 | import FunctionForm from "./FunctionForm"; 8 | 9 | const noContractDisplay = ( 10 |
11 | Loading...{" "} 12 |
13 | You need to run{" "} 14 | 18 | yarn run chain 19 | {" "} 20 | and{" "} 21 | 25 | yarn run deploy 26 | {" "} 27 | to see your contract here. 28 |
29 |
30 | 31 | ☢️ 32 | 33 | Warning: You might need to run 34 | 38 | yarn run deploy 39 | {" "} 40 | again after the frontend comes up! 41 |
42 |
43 | ); 44 | 45 | const isQueryable = fn => (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; 46 | 47 | export default function Contract({ 48 | customContract, 49 | account, 50 | gasPrice, 51 | signer, 52 | provider, 53 | name, 54 | show, 55 | price, 56 | blockExplorer, 57 | chainId, 58 | contractConfig, 59 | }) { 60 | const contracts = useContractLoader(provider, contractConfig, chainId); 61 | let contract; 62 | if (!customContract) { 63 | contract = contracts ? contracts[name] : ""; 64 | } else { 65 | contract = customContract; 66 | } 67 | 68 | const address = contract ? contract.address : ""; 69 | const contractIsDeployed = useContractExistsAtAddress(provider, address); 70 | 71 | const displayedContractFunctions = useMemo(() => { 72 | const results = contract 73 | ? Object.entries(contract.interface.functions).filter( 74 | fn => fn[1]["type"] === "function" && !(show && show.indexOf(fn[1]["name"]) < 0), 75 | ) 76 | : []; 77 | return results; 78 | }, [contract, show]); 79 | 80 | const [refreshRequired, triggerRefresh] = useState(false); 81 | const contractDisplay = displayedContractFunctions.map(contractFuncInfo => { 82 | const contractFunc = 83 | contractFuncInfo[1].stateMutability === "view" || contractFuncInfo[1].stateMutability === "pure" 84 | ? contract[contractFuncInfo[0]] 85 | : contract.connect(signer)[contractFuncInfo[0]]; 86 | 87 | if (typeof contractFunc === "function") { 88 | if (isQueryable(contractFuncInfo[1])) { 89 | // If there are no inputs, just display return value 90 | return ( 91 | 99 | ); 100 | } 101 | 102 | // If there are inputs, display a form to allow users to provide these 103 | return ( 104 | 112 | ); 113 | } 114 | return null; 115 | }); 116 | 117 | return ( 118 |
119 | 122 | {name} 123 |
124 |
125 | 126 |
127 |
128 | } 129 | size="large" 130 | style={{ marginTop: 25, width: "100%" }} 131 | loading={contractDisplay && contractDisplay.length <= 0} 132 | > 133 | {contractIsDeployed ? contractDisplay : noContractDisplay} 134 | 135 | 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /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 | 5 | import Address from "./Address"; 6 | import Balance from "./Balance"; 7 | import Wallet from "./Wallet"; 8 | 9 | /** 10 | ~ What it does? ~ 11 | 12 | Displays an Address, Balance, and Wallet as one Account component, 13 | also allows users to log in to existing accounts and log out 14 | 15 | ~ How can I use? ~ 16 | 17 | 30 | 31 | ~ Features ~ 32 | 33 | - Provide address={address} and get balance corresponding to the given address 34 | - Provide localProvider={localProvider} to access balance on local network 35 | - Provide userProvider={userProvider} to display a wallet 36 | - Provide mainnetProvider={mainnetProvider} and your address will be replaced by ENS name 37 | (ex. "0xa870" => "user.eth") 38 | - Provide price={price} of ether and get your balance converted to dollars 39 | - Provide web3Modal={web3Modal}, loadWeb3Modal={loadWeb3Modal}, logoutOfWeb3Modal={logoutOfWeb3Modal} 40 | to be able to log in/log out to/from existing accounts 41 | - Provide blockExplorer={blockExplorer}, click on address and get the link 42 | (ex. by default "https://etherscan.io/" or for xdai "https://blockscout.com/poa/xdai/") 43 | **/ 44 | 45 | export default function Account({ 46 | useBurner, 47 | address, 48 | userSigner, 49 | localProvider, 50 | mainnetProvider, 51 | price, 52 | minimized, 53 | web3Modal, 54 | loadWeb3Modal, 55 | logoutOfWeb3Modal, 56 | blockExplorer, 57 | isContract, 58 | }) { 59 | const { currentTheme } = useThemeSwitcher(); 60 | 61 | const modalButtons = []; 62 | if (web3Modal) { 63 | if (web3Modal.cachedProvider) { 64 | modalButtons.push( 65 | , 74 | ); 75 | } else { 76 | modalButtons.push( 77 | , 87 | ); 88 | } 89 | } 90 | const display = minimized ? ( 91 | "" 92 | ) : ( 93 | 94 | {web3Modal && web3Modal.cachedProvider ? ( 95 | <> 96 | {address &&
} 97 | 98 | 106 | 107 | ) : useBurner ? ( 108 | "" 109 | ) : isContract ? ( 110 | <> 111 | {address &&
} 112 | 113 | 114 | ) : ( 115 | "" 116 | )} 117 | {useBurner && web3Modal && !web3Modal.cachedProvider ? ( 118 | <> 119 |
120 | 121 | 129 | 130 | ) : ( 131 | <> 132 | )} 133 | 134 | ); 135 | 136 | return ( 137 |
138 | {display} 139 | {modalButtons} 140 |
141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /packages/react-app/src/components/AddressInput.jsx: -------------------------------------------------------------------------------- 1 | import { Badge, Input } from "antd"; 2 | import React, { useCallback, useState } from "react"; 3 | import { ethers } from "ethers"; 4 | import { CameraOutlined, QrcodeOutlined } from "@ant-design/icons"; 5 | import { useLookupAddress } from "eth-hooks/dapps/ens"; 6 | import QrReader from "react-qr-reader"; 7 | 8 | import Blockie from "./Blockie"; 9 | 10 | const isENS = (address = "") => address.endsWith(".eth") || address.endsWith(".xyz"); 11 | 12 | // probably we need to change value={toAddress} to address={toAddress} 13 | 14 | /** 15 | ~ What it does? ~ 16 | 17 | Displays an address input with QR scan option 18 | 19 | ~ How can I use? ~ 20 | 21 | 28 | 29 | ~ Features ~ 30 | 31 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 32 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 33 | - Provide placeholder="Enter address" value for the input 34 | - Value of the address input is stored in value={toAddress} 35 | - Control input change by onChange={setToAddress} 36 | or onChange={address => { setToAddress(address);}} 37 | **/ 38 | export default function AddressInput(props) { 39 | const { ensProvider, onChange } = props; 40 | const [value, setValue] = useState(props.value); 41 | const [scan, setScan] = useState(false); 42 | 43 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 44 | const ens = useLookupAddress(props.ensProvider, currentValue); 45 | 46 | const updateAddress = useCallback( 47 | async newValue => { 48 | if (typeof newValue !== "undefined") { 49 | let address = newValue; 50 | if (isENS(address)) { 51 | try { 52 | const possibleAddress = await ensProvider.resolveName(address); 53 | if (possibleAddress) { 54 | address = possibleAddress; 55 | } 56 | // eslint-disable-next-line no-empty 57 | } catch (e) {} 58 | } 59 | setValue(address); 60 | if (typeof onChange === "function") { 61 | onChange(address); 62 | } 63 | } 64 | }, 65 | [ensProvider, onChange], 66 | ); 67 | 68 | return ( 69 |
70 | {scan ? ( 71 |
{ 80 | setScan(false); 81 | }} 82 | > 83 | { 87 | console.log("SCAN ERROR", e); 88 | setScan(false); 89 | }} 90 | onScan={newValue => { 91 | if (newValue) { 92 | console.log("SCAN VALUE", newValue); 93 | let possibleNewValue = newValue; 94 | if (possibleNewValue.indexOf("/") >= 0) { 95 | possibleNewValue = possibleNewValue.substr(possibleNewValue.lastIndexOf("0x")); 96 | console.log("CLEANED VALUE", possibleNewValue); 97 | } 98 | setScan(false); 99 | updateAddress(possibleNewValue); 100 | } 101 | }} 102 | style={{ width: "100%" }} 103 | /> 104 |
105 | ) : ( 106 | "" 107 | )} 108 | } 115 | value={ethers.utils.isAddress(currentValue) && !isENS(currentValue) && isENS(ens) ? ens : currentValue} 116 | addonAfter={ 117 |
{ 120 | setScan(!scan); 121 | }} 122 | > 123 | }> 124 | 125 | {" "} 126 | Scan 127 |
128 | } 129 | onChange={e => { 130 | updateAddress(e.target.value); 131 | }} 132 | /> 133 |
134 | ); 135 | } 136 | -------------------------------------------------------------------------------- /packages/react-app/src/views/Home.jsx: -------------------------------------------------------------------------------- 1 | import { useContractReader } from "eth-hooks"; 2 | import { ethers } from "ethers"; 3 | import React from "react"; 4 | import { Link } from "react-router-dom"; 5 | 6 | /** 7 | * web3 props can be passed from '../App.jsx' into your local view component for use 8 | * @param {*} yourLocalBalance balance on current network 9 | * @param {*} readContracts contracts from current chain already pre-loaded using ethers contract module. More here https://docs.ethers.io/v5/api/contract/contract/ 10 | * @returns react component 11 | **/ 12 | function Home({ yourLocalBalance, readContracts }) { 13 | // you can also use hooks locally in your component of choice 14 | // in this case, let's keep track of 'purpose' variable from our contract 15 | const purpose = useContractReader(readContracts, "YourContract", "purpose"); 16 | 17 | return ( 18 |
19 |
20 | 📝 21 | This Is Your App Home. You can start editing it in{" "} 22 | 26 | packages/react-app/src/views/Home.jsx 27 | 28 |
29 |
30 | ✏️ 31 | Edit your smart contract{" "} 32 | 36 | YourContract.sol 37 | {" "} 38 | in{" "} 39 | 43 | packages/hardhat/contracts 44 | 45 |
46 | {!purpose ? ( 47 |
48 | 👷‍♀️ 49 | You haven't deployed your contract yet, run 50 | 59 | yarn chain 60 | {" "} 61 | and{" "} 62 | 71 | yarn deploy 72 | {" "} 73 | to deploy your first contract! 74 |
75 | ) : ( 76 |
77 | 🤓 78 | The "purpose" variable from your contract is{" "} 79 | 88 | {purpose} 89 | 90 |
91 | )} 92 | 93 |
94 | 🤖 95 | An example prop of your balance{" "} 96 | ({ethers.utils.formatEther(yourLocalBalance)}) was 97 | passed into the 98 | 102 | Home.jsx 103 | {" "} 104 | component from 105 | 109 | App.jsx 110 | 111 |
112 |
113 | 💭 114 | Check out the "Hints" tab for more tips. 115 |
116 |
117 | 🛠 118 | Tinker with your smart contract using the "Debug Contract" tab. 119 |
120 |
121 | ); 122 | } 123 | 124 | export default Home; 125 | -------------------------------------------------------------------------------- /packages/react-app/src/components/MultiAddressInput.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { Select } from "antd"; 3 | import { ethers } from "ethers"; 4 | import React, { useCallback, useMemo, useState } from "react"; 5 | import Blockie from "./Blockie"; 6 | 7 | // probably we need to change value={toAddress} to address={toAddress} 8 | 9 | /* 10 | ~ What it does? ~ 11 | 12 | Displays an address input with QR scan option 13 | 14 | ~ How can I use? ~ 15 | 16 | 23 | 24 | ~ Features ~ 25 | 26 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 27 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 28 | - Provide placeholder="Enter address" value for the input 29 | - Value of the address input is stored in value={toAddress} 30 | - Control input change by onChange={setToAddress} 31 | or onChange={address => { setToAddress(address);}} 32 | */ 33 | 34 | const isENS = (address = "") => address.endsWith(".eth") || address.endsWith(".xyz"); 35 | 36 | export default function MultiAddressInput(props) { 37 | const { ensProvider, onChange } = props; 38 | const [value, setValue] = useState(props.value || []); 39 | const [searchResults, setSearchResults] = useState([]); 40 | 41 | const children = useMemo(() => { 42 | if (searchResults.length < 1) { 43 | return []; 44 | } 45 | 46 | // use search result to format children 47 | return searchResults.map(i => ( 48 | 49 |
50 |
51 | 52 |
53 | {i.ens ? i.ens : i.address?.substr(0, 5) + "..." + i.address?.substr(-4)} 54 |
55 |
56 | )); 57 | }, [searchResults.length]); 58 | 59 | // const currentValue = typeof props.value !== "undefined" ? props.value : value; 60 | // const ens = useLookupAddress(props.ensProvider, currentValue); 61 | 62 | const manageSearch = useCallback( 63 | async newValue => { 64 | if (typeof newValue !== "undefined") { 65 | let address = newValue; 66 | let isResolvedAddress = true; 67 | if (isENS(address)) { 68 | try { 69 | const possibleAddress = await ensProvider.resolveName(address); 70 | 71 | if (possibleAddress) { 72 | isResolvedAddress = true; 73 | address = possibleAddress; 74 | } 75 | // eslint-disable-next-line no-empty 76 | } catch (e) {} 77 | } else if (ethers.utils.isAddress(address)) { 78 | try { 79 | const possibleENS = await ensProvider.lookupAddress(address); 80 | 81 | address = possibleENS; 82 | isResolvedAddress = false; 83 | } catch (e) {} 84 | } 85 | return { resolvedTo: address, isResolvedAddress }; 86 | } 87 | }, 88 | [ensProvider, onChange], 89 | ); 90 | 91 | const handleSearch = async val => { 92 | console.log(`Searching: `, val); 93 | const formattedVal = val.toLowerCase(); 94 | const resolution = await manageSearch(formattedVal); 95 | 96 | console.log(resolution); 97 | 98 | const [address, ens] = resolution.isResolvedAddress ? [resolution.resolvedTo, val] : [val, resolution.resolvedTo]; 99 | 100 | if (resolution.resolvedTo !== val) { 101 | setSearchResults([{ address, isResolvedAddress: resolution.isResolvedAddress, ens }]); 102 | } 103 | }; 104 | 105 | const handleOnChange = e => { 106 | console.log(e); 107 | setSearchResults([]); 108 | setValue(e); 109 | 110 | if (typeof onChange === "function") { 111 | onChange(e.map(i => i.value)); 112 | } 113 | }; 114 | 115 | // 116 | 117 | return ( 118 |
119 | 137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /packages/react-app/src/views/ExampleUI.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Card, DatePicker, Divider, Input, Progress, Slider, Spin, Switch } from "antd"; 2 | import React, { useState } from "react"; 3 | import { utils } from "ethers"; 4 | import logo from '../moonbeamlogo.png'; 5 | import { SyncOutlined } from "@ant-design/icons"; 6 | 7 | import { Address, Balance, Events } from "../components"; 8 | 9 | export default function ExampleUI({ 10 | purpose, 11 | target, 12 | totalStake, 13 | currentState, 14 | memberStake, 15 | address, 16 | mainnetProvider, 17 | localProvider, 18 | yourLocalBalance, 19 | price, 20 | tx, 21 | readContracts, 22 | writeContracts, 23 | }) { 24 | const [newStake, setNewStake] = useState("loading..."); 25 | const [withdrawalAddress, setWithdrawalAddress] = useState("loading..."); 26 | 27 | const chainState = ["Collecting", "Staking", "Revoking", "Revoked"]; 28 | 29 | return ( 30 |
31 | {/* 32 | ⚙️ Here is an example UI that displays and sets the purpose in your smart contract: 33 | */} 34 |
35 | Logo; 36 | 37 | 38 |

Delegation DAO Example UI:

39 |

Target Collator: {target}

40 |

Total Stake: {totalStake ? utils.formatEther(totalStake) : "..."}

41 |

Member Stake: {memberStake ? utils.formatEther(memberStake) : "..."}

42 |

Current DAO State: {chainState[currentState]}

43 | 44 |
45 |

Add Stake Amount:

46 | { 48 | setNewStake(e.target.value); 49 | }} 50 | /> 51 | 80 |
81 | 82 |
83 | 95 |
96 | 97 |
98 |

Withdrawl Address:

99 | { 101 | setWithdrawalAddress(e.target.value); 102 | }} 103 | /> 104 | 132 |
133 |
134 |
135 | ); 136 | 137 | } 138 | -------------------------------------------------------------------------------- /packages/react-app/src/components/TokenSelect.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { Select } from "antd"; 3 | import { useState, useMemo, useEffect } from "react"; 4 | import { ethers } from "ethers"; 5 | import axios from "axios"; 6 | import searchico from "searchico"; 7 | 8 | // helpers to load token name and symbol for unlisted tokens 9 | const ERC20ABI = ["function symbol() view returns (string)", "function name() view returns (string)"]; 10 | 11 | const loadERC20 = async (address, p) => { 12 | try { 13 | // load token information here 14 | const r = new ethers.Contract(address, ERC20ABI, p); 15 | const name = await r.name?.(); 16 | const symbol = await r.symbol?.(); 17 | 18 | return { name, symbol }; 19 | } catch (error) { 20 | return {}; 21 | } 22 | }; 23 | 24 | /* 25 | 31 | */ 32 | export default function TokenSelect({ onChange, chainId = 1, nativeToken = {}, localProvider, ...props }) { 33 | const [value, setValue] = useState(null); 34 | const [list, setList] = useState([]); 35 | const [searchResults, setSearchResults] = useState([]); 36 | 37 | const listCollection = useMemo(() => { 38 | return searchico(list, { keys: ["address", "name", "symbol"] }); 39 | }, [list.length]); 40 | 41 | const children = useMemo(() => { 42 | if (searchResults.length < 1) { 43 | return []; 44 | } 45 | 46 | // use search result to format children 47 | return searchResults.map(i => ( 48 | 49 |
50 | {i.logoURI && ( 51 |
52 | {`${i.name} 53 |
54 | )} 55 | {i.name} - {i.symbol} {i.address?.substr(0, 5) + "..." + i.address?.substr(-4)}{" "} 56 | {i.unlisted && (unlisted) } 57 |
58 |
59 | )); 60 | }, [JSON.stringify(searchResults)]); 61 | 62 | const handleSearch = async val => { 63 | let collectionResult = []; 64 | 65 | if (val.length > 0) { 66 | // TODO : Do all search & filtering here 67 | collectionResult = (listCollection?.find(val) || []).filter(i => i.chainId === chainId); 68 | 69 | if (collectionResult.length < 1) { 70 | const nativeTokenObj = { 71 | chainId: chainId, 72 | decimals: 18, 73 | name: "Native Token", 74 | symbol: "ETH", 75 | address: "0x0000000000000000000000000000000000000000", 76 | logoURI: "https://assets.coingecko.com/coins/images/279/thumb/ethereum.png?1595348880", 77 | ...nativeToken, 78 | }; 79 | 80 | collectionResult.push(nativeTokenObj); 81 | 82 | try { 83 | const checksumAddress = ethers.utils.getAddress(val); 84 | // load contract and try to get name and symbol if there's a provider given 85 | const tokenInfo = localProvider ? await loadERC20(checksumAddress, localProvider) : {}; 86 | collectionResult = [ 87 | { 88 | chainId: chainId, 89 | name: null, 90 | unlisted: true, 91 | symbol: null, 92 | address: checksumAddress, 93 | logoURI: "", 94 | ...tokenInfo, 95 | }, 96 | ]; 97 | } catch (error) { 98 | console.log(`Could not identify this token`); 99 | } 100 | } 101 | } 102 | 103 | setSearchResults(collectionResult); 104 | }; 105 | 106 | const handleOnChange = async e => { 107 | setSearchResults([]); 108 | 109 | // TODO : check if it's an address that's not on list & Add as unlisted 110 | 111 | setValue(e); 112 | 113 | if (typeof onChange === "function") { 114 | onChange(e.value); 115 | } 116 | }; 117 | 118 | const loadList = async () => { 119 | // https://tokens.coingecko.com/uniswap/all.json 120 | const res = await axios.get("https://tokens.coingecko.com/uniswap/all.json"); 121 | const { tokens } = res.data; 122 | 123 | setList(tokens); 124 | }; 125 | 126 | useEffect(() => { 127 | loadList(); 128 | }, []); 129 | 130 | return ( 131 |
132 | 150 |
151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /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/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 | Make sure your local chain is running first: 96 | 97 | yarn chain 98 | 99 |
100 | 101 |
102 | 🚮 103 | Clean up previous data, if there is any: 104 | 105 | yarn clean-graph-node 106 | 107 |
108 | 109 |
110 | 📡 111 | Spin up a local graph node by running 112 | 113 | yarn run-graph-node 114 | 115 | 116 | {" "} 117 | (requires{" "} 118 | 119 | {" "} 120 | Docker 121 | 122 | ){" "} 123 | 124 |
125 | 126 |
127 | 📝 128 | Create your local subgraph by running 129 | 130 | yarn graph-create-local 131 | 132 | (only required once!) 133 |
134 | 135 |
136 | 🚢 137 | Deploy your local subgraph by running 138 | 139 | yarn graph-ship-local 140 | 141 |
142 | 143 |
144 | 🖍️ 145 | Edit your local subgraph in 146 | 147 | packages/subgraph/src 148 | 149 | (learn more about subgraph definition{" "} 150 | 151 | here 152 | 153 | ) 154 |
155 | 156 |
157 | 🤩 158 | Deploy your contracts and your subgraph in one go by running 159 | 160 | yarn deploy-and-graph 161 | 162 |
163 | 164 |
165 |
166 | { 168 | setNewPurpose(e.target.value); 169 | }} 170 | /> 171 | 180 |
181 | 182 | {data ? ( 183 | 184 | ) : ( 185 | {loading ? "Loading..." : deployWarning} 186 | )} 187 | 188 |
189 | 190 |
191 | 192 | 193 |
...
194 | 195 | ); 196 | } 197 | 198 | export default Subgraph; 199 | -------------------------------------------------------------------------------- /packages/react-app/src/constants.js: -------------------------------------------------------------------------------- 1 | // MY INFURA_ID, SWAP IN YOURS FROM https://infura.io/dashboard/ethereum 2 | export const INFURA_ID = "460f40a260564ac4a4f4b3fffb032dad"; 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://mainnet.infura.io/v3/${INFURA_ID}`, 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 | polygon: { 70 | name: "polygon", 71 | color: "#2bbdf7", 72 | chainId: 137, 73 | price: 1, 74 | gasPrice: 1000000000, 75 | rpcUrl: "https://polygon-rpc.com/", 76 | blockExplorer: "https://polygonscan.com/", 77 | }, 78 | mumbai: { 79 | name: "mumbai", 80 | color: "#92D9FA", 81 | chainId: 80001, 82 | price: 1, 83 | gasPrice: 1000000000, 84 | rpcUrl: "https://rpc-mumbai.maticvigil.com", 85 | faucet: "https://faucet.polygon.technology/", 86 | blockExplorer: "https://mumbai.polygonscan.com/", 87 | }, 88 | localOptimismL1: { 89 | name: "localOptimismL1", 90 | color: "#f01a37", 91 | chainId: 31337, 92 | blockExplorer: "", 93 | rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":9545", 94 | }, 95 | localOptimism: { 96 | name: "localOptimism", 97 | color: "#f01a37", 98 | chainId: 420, 99 | blockExplorer: "", 100 | rpcUrl: "http://" + (global.window ? window.location.hostname : "localhost") + ":8545", 101 | gasPrice: 0, 102 | }, 103 | kovanOptimism: { 104 | name: "kovanOptimism", 105 | color: "#f01a37", 106 | chainId: 69, 107 | blockExplorer: "https://kovan-optimistic.etherscan.io/", 108 | rpcUrl: `https://kovan.optimism.io`, 109 | gasPrice: 0, 110 | }, 111 | optimism: { 112 | name: "optimism", 113 | color: "#f01a37", 114 | chainId: 10, 115 | blockExplorer: "https://optimistic.etherscan.io/", 116 | rpcUrl: `https://mainnet.optimism.io`, 117 | }, 118 | localAvalanche: { 119 | name: "localAvalanche", 120 | color: "#666666", 121 | chainId: 43112, 122 | blockExplorer: "", 123 | rpcUrl: `http://localhost:9650/ext/bc/C/rpc`, 124 | gasPrice: 225000000000, 125 | }, 126 | fujiAvalanche: { 127 | name: "fujiAvalanche", 128 | color: "#666666", 129 | chainId: 43113, 130 | blockExplorer: "https://cchain.explorer.avax-test.network/", 131 | rpcUrl: `https://api.avax-test.network/ext/bc/C/rpc`, 132 | gasPrice: 225000000000, 133 | }, 134 | mainnetAvalanche: { 135 | name: "mainnetAvalanche", 136 | color: "#666666", 137 | chainId: 43114, 138 | blockExplorer: "https://cchain.explorer.avax.network/", 139 | rpcUrl: `https://api.avax.network/ext/bc/C/rpc`, 140 | gasPrice: 225000000000, 141 | }, 142 | testnetHarmony: { 143 | name: "testnetHarmony", 144 | color: "#00b0ef", 145 | chainId: 1666700000, 146 | blockExplorer: "https://explorer.pops.one/", 147 | rpcUrl: `https://api.s0.b.hmny.io`, 148 | gasPrice: 1000000000, 149 | }, 150 | mainnetHarmony: { 151 | name: "mainnetHarmony", 152 | color: "#00b0ef", 153 | chainId: 1666600000, 154 | blockExplorer: "https://explorer.harmony.one/", 155 | rpcUrl: `https://api.harmony.one`, 156 | gasPrice: 1000000000, 157 | }, 158 | fantom: { 159 | name: "fantom", 160 | color: "#1969ff", 161 | chainId: 250, 162 | blockExplorer: "https://ftmscan.com/", 163 | rpcUrl: `https://rpcapi.fantom.network`, 164 | gasPrice: 1000000000, 165 | }, 166 | testnetFantom: { 167 | name: "testnetFantom", 168 | color: "#1969ff", 169 | chainId: 4002, 170 | blockExplorer: "https://testnet.ftmscan.com/", 171 | rpcUrl: `https://rpc.testnet.fantom.network`, 172 | gasPrice: 1000000000, 173 | faucet: "https://faucet.fantom.network/", 174 | }, 175 | moonbeam: { 176 | name: "moonbeam", 177 | color: "#53CBC9", 178 | chainId: 1284, 179 | blockExplorer: "https://moonscan.io", 180 | rpcUrl: "https://rpc.api.moonbeam.network", 181 | }, 182 | moonriver: { 183 | name: "moonriver", 184 | color: "#53CBC9", 185 | chainId: 1285, 186 | blockExplorer: "https://moonriver.moonscan.io/", 187 | rpcUrl: "https://rpc.api.moonriver.moonbeam.network", 188 | }, 189 | moonbaseAlpha: { 190 | name: "moonbaseAlpha", 191 | color: "#53CBC9", 192 | chainId: 1287, 193 | blockExplorer: "https://moonbase.moonscan.io/", 194 | rpcUrl: "https://rpc.api.moonbase.moonbeam.network", 195 | faucet: "https://discord.gg/SZNP8bWHZq", 196 | }, 197 | moonbeamDevNode: { 198 | name: "moonbeamDevNode", 199 | color: "#53CBC9", 200 | chainId: 1281, 201 | blockExplorer: "https://moonbeam-explorer.netlify.app/", 202 | rpcUrl: "http://127.0.0.1:9933", 203 | } 204 | }; 205 | 206 | export const NETWORK = chainId => { 207 | for (const n in NETWORKS) { 208 | if (NETWORKS[n].chainId === chainId) { 209 | return NETWORKS[n]; 210 | } 211 | } 212 | }; 213 | -------------------------------------------------------------------------------- /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/contracts/DelegationDAO.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-only 2 | // This is a PoC to use the staking precompile wrapper as a Solidity developer. 3 | pragma solidity >=0.8.0; 4 | 5 | import "./StakingInterface.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 8 | import "@openzeppelin/contracts/utils/Address.sol"; 9 | 10 | contract DelegationDAO is AccessControl { 11 | 12 | using SafeMath for uint256; 13 | 14 | // Role definition for contract members 15 | bytes32 public constant MEMBER = keccak256("MEMBER"); 16 | 17 | // Possible states for the DAO to be in: 18 | // COLLECTING: the DAO is collecting funds before creating a delegation once the minimum delegation stake has been reached 19 | // STAKING: the DAO has an active delegation 20 | // REVOKING: the DAO has scheduled a delegation revoke 21 | // REVOKED: the scheduled revoke has been executed 22 | enum daoState{ COLLECTING, STAKING, REVOKING, REVOKED } 23 | 24 | // Current state that the DAO is in 25 | daoState public currentState; 26 | 27 | // Member stakes (doesnt include rewards, represents member shares) 28 | mapping(address => uint256) public memberStakes; 29 | 30 | // Total Staking Pool (doesnt include rewards, represents total shares) 31 | uint256 public totalStake; 32 | 33 | // The ParachainStaking wrapper at the known pre-compile address. This will be used to make 34 | // all calls to the underlying staking solution 35 | ParachainStaking public staking; 36 | 37 | // Minimum Delegation Amount 38 | uint256 public constant minDelegationStk = 5 ether; 39 | 40 | // Moonbeam Staking Precompile address 41 | address public constant stakingPrecompileAddress = 0x0000000000000000000000000000000000000800; 42 | 43 | // The collator that this DAO is currently nominating 44 | address public target; 45 | 46 | // Event for a member deposit 47 | event deposit(address indexed _from, uint _value); 48 | 49 | // Event for a member withdrawal 50 | event withdrawal(address indexed _from, address indexed _to, uint _value); 51 | 52 | // Initialize a new DelegationDao dedicated to delegating to the given collator target. 53 | constructor(address _target, address admin) { 54 | 55 | //Sets the collator that this DAO nominating 56 | target = _target; 57 | 58 | // Initializes Moonbeam's parachain staking precompile 59 | staking = ParachainStaking(stakingPrecompileAddress); 60 | 61 | //Initializes Roles 62 | _setupRole(DEFAULT_ADMIN_ROLE, admin); 63 | _setupRole(MEMBER, admin); 64 | 65 | //Initialize the DAO state 66 | currentState = daoState.COLLECTING; 67 | 68 | } 69 | 70 | // Grant a user the role of admin 71 | function grant_admin(address newAdmin) 72 | public 73 | onlyRole(DEFAULT_ADMIN_ROLE) 74 | onlyRole(MEMBER) 75 | { 76 | grantRole(DEFAULT_ADMIN_ROLE, newAdmin); 77 | grantRole(MEMBER, newAdmin); 78 | } 79 | 80 | // Grant a user membership 81 | function grant_member(address newMember) 82 | public 83 | onlyRole(DEFAULT_ADMIN_ROLE) 84 | { 85 | grantRole(MEMBER, newMember); 86 | } 87 | 88 | // Revoke a user membership 89 | function remove_member(address payable exMember) 90 | public 91 | onlyRole(DEFAULT_ADMIN_ROLE) 92 | { 93 | revokeRole(MEMBER, exMember); 94 | } 95 | 96 | // Increase member stake via a payable function and automatically stake the added amount if possible 97 | function add_stake() external payable onlyRole(MEMBER) { 98 | if (currentState == daoState.STAKING ) { 99 | // Sanity check 100 | if(!staking.is_delegator(address(this))){ 101 | revert("The DAO is in an inconsistent state."); 102 | } 103 | memberStakes[msg.sender] = memberStakes[msg.sender].add(msg.value); 104 | totalStake = totalStake.add(msg.value); 105 | emit deposit(msg.sender, msg.value); 106 | staking.delegator_bond_more(target, msg.value); 107 | } 108 | else if (currentState == daoState.COLLECTING ){ 109 | memberStakes[msg.sender] = memberStakes[msg.sender].add(msg.value); 110 | totalStake = totalStake.add(msg.value); 111 | emit deposit(msg.sender, msg.value); 112 | if(totalStake < minDelegationStk){ 113 | return; 114 | } else { 115 | //initialiate the delegation and change the state 116 | staking.delegate(target, address(this).balance, staking.candidate_delegation_count(target), staking.delegator_delegation_count(address(this))); 117 | currentState = daoState.STAKING; 118 | } 119 | } 120 | else { 121 | revert("The DAO is not accepting new stakes in the current state."); 122 | } 123 | } 124 | 125 | // Function for a user to withdraw their stake 126 | function withdraw(address payable account) public onlyRole(MEMBER) { 127 | require(currentState != daoState.STAKING, "The DAO is not in the correct state to withdraw."); 128 | if (currentState == daoState.REVOKING) { 129 | bool result = execute_revoke(); 130 | require(result, "Schedule revoke delay is not finished yet."); 131 | } 132 | if (currentState == daoState.REVOKED || currentState == daoState.COLLECTING) { 133 | //Sanity checks 134 | if(staking.is_delegator(address(this))){ 135 | revert("The DAO is in an inconsistent state."); 136 | } 137 | require(totalStake!=0, "Cannot divide by zero."); 138 | //Calculate the withdrawal amount including staking rewards 139 | uint amount = address(this) 140 | .balance 141 | .mul(memberStakes[msg.sender]) 142 | .div(totalStake); 143 | require(check_free_balance() >= amount, "Not enough free balance for withdrawal."); 144 | Address.sendValue(account, amount); 145 | totalStake = totalStake.sub(memberStakes[msg.sender]); 146 | memberStakes[msg.sender] = 0; 147 | emit withdrawal(msg.sender, account, amount); 148 | } 149 | } 150 | 151 | // Schedule revoke, admin only 152 | function schedule_revoke() public onlyRole(DEFAULT_ADMIN_ROLE){ 153 | require(currentState == daoState.STAKING, "The DAO is not in the correct state to schedule a revoke."); 154 | staking.schedule_revoke_delegation(target); 155 | currentState = daoState.REVOKING; 156 | } 157 | 158 | // Try to execute the revoke, returns true if it succeeds, false if it doesn't 159 | function execute_revoke() internal onlyRole(MEMBER) returns(bool) { 160 | require(currentState == daoState.REVOKING, "The DAO is not in the correct state to execute a revoke."); 161 | staking.execute_delegation_request(address(this), target); 162 | if (staking.is_delegator(address(this))){ 163 | return false; 164 | } else { 165 | currentState = daoState.REVOKED; 166 | return true; 167 | } 168 | } 169 | 170 | // Check how much free balance the DAO currently has. It should be the staking rewards if the DAO state is anything other than REVOKED or COLLECTING. 171 | function check_free_balance() public view onlyRole(MEMBER) returns(uint256) { 172 | return address(this).balance; 173 | } 174 | 175 | // Change the collator target, admin only 176 | function change_target(address newCollator) public onlyRole(DEFAULT_ADMIN_ROLE) { 177 | require(currentState == daoState.REVOKED || currentState == daoState.COLLECTING, "The DAO is not in the correct state to change staking target."); 178 | target = newCollator; 179 | } 180 | 181 | // Reset the DAO state back to COLLECTING, admin only 182 | function reset_dao() public onlyRole(DEFAULT_ADMIN_ROLE) { 183 | currentState = daoState.COLLECTING; 184 | } 185 | 186 | 187 | } -------------------------------------------------------------------------------- /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 | 5 | import { Transactor } from "../../helpers"; 6 | import { tryToDisplay, tryToDisplayAsText } from "./utils"; 7 | 8 | const { utils, BigNumber } = require("ethers"); 9 | 10 | const getFunctionInputKey = (functionInfo, input, inputIndex) => { 11 | const name = input?.name ? input.name : "input_" + inputIndex + "_"; 12 | return functionInfo.name + "_" + name + "_" + input.type; 13 | }; 14 | 15 | export default function FunctionForm({ contractFunction, functionInfo, provider, gasPrice, triggerRefresh }) { 16 | const [form, setForm] = useState({}); 17 | const [txValue, setTxValue] = useState(); 18 | const [returnValue, setReturnValue] = useState(); 19 | 20 | const tx = Transactor(provider, gasPrice); 21 | 22 | const inputs = functionInfo.inputs.map((input, inputIndex) => { 23 | const key = getFunctionInputKey(functionInfo, input, inputIndex); 24 | 25 | let buttons = ""; 26 | if (input.type === "bytes32") { 27 | buttons = ( 28 | 29 |
{ 33 | if (utils.isHexString(form[key])) { 34 | const formUpdate = { ...form }; 35 | formUpdate[key] = utils.parseBytes32String(form[key]); 36 | setForm(formUpdate); 37 | } else { 38 | const formUpdate = { ...form }; 39 | formUpdate[key] = utils.formatBytes32String(form[key]); 40 | setForm(formUpdate); 41 | } 42 | }} 43 | > 44 | #️⃣ 45 |
46 |
47 | ); 48 | } else if (input.type === "bytes") { 49 | buttons = ( 50 | 51 |
{ 55 | if (utils.isHexString(form[key])) { 56 | const formUpdate = { ...form }; 57 | formUpdate[key] = utils.toUtf8String(form[key]); 58 | setForm(formUpdate); 59 | } else { 60 | const formUpdate = { ...form }; 61 | formUpdate[key] = utils.hexlify(utils.toUtf8Bytes(form[key])); 62 | setForm(formUpdate); 63 | } 64 | }} 65 | > 66 | #️⃣ 67 |
68 |
69 | ); 70 | } else if (input.type === "uint256") { 71 | buttons = ( 72 | 73 |
{ 77 | const formUpdate = { ...form }; 78 | formUpdate[key] = utils.parseEther(form[key]); 79 | setForm(formUpdate); 80 | }} 81 | > 82 | ✴️ 83 |
84 |
85 | ); 86 | } else if (input.type === "address") { 87 | const possibleAddress = form[key] && form[key].toLowerCase && form[key].toLowerCase().trim(); 88 | if (possibleAddress && possibleAddress.length === 42) { 89 | buttons = ( 90 | 91 | 92 | 93 | ); 94 | } 95 | } 96 | 97 | return ( 98 |
99 | { 106 | const formUpdate = { ...form }; 107 | formUpdate[event.target.name] = event.target.value; 108 | setForm(formUpdate); 109 | }} 110 | suffix={buttons} 111 | /> 112 |
113 | ); 114 | }); 115 | 116 | const txValueInput = ( 117 |
118 | setTxValue(e.target.value)} 121 | value={txValue} 122 | addonAfter={ 123 |
124 | 125 |
126 | 127 |
{ 131 | const floatValue = parseFloat(txValue); 132 | if (floatValue) setTxValue("" + floatValue * 10 ** 18); 133 | }} 134 | > 135 | ✳️ 136 |
137 |
138 | 139 | 140 | 141 |
{ 145 | setTxValue(BigNumber.from(txValue).toHexString()); 146 | }} 147 | > 148 | #️⃣ 149 |
150 |
151 | 152 | 153 | 154 | } 155 | /> 156 | 157 | ); 158 | 159 | if (functionInfo.payable) { 160 | inputs.push(txValueInput); 161 | } 162 | 163 | const handleForm = returned => { 164 | if (returned) { 165 | setForm({}); 166 | } 167 | }; 168 | 169 | const buttonIcon = 170 | functionInfo.type === "call" ? ( 171 | 172 | ) : ( 173 | 174 | ); 175 | inputs.push( 176 |
177 | setReturnValue(e.target.value)} 179 | defaultValue="" 180 | bordered={false} 181 | disabled 182 | value={returnValue} 183 | suffix={ 184 |
{ 188 | const args = functionInfo.inputs.map((input, inputIndex) => { 189 | const key = getFunctionInputKey(functionInfo, input, inputIndex); 190 | let value = form[key]; 191 | if (input.baseType === "array") { 192 | value = JSON.parse(value); 193 | } else if (input.type === "bool") { 194 | if (value === "true" || value === "1" || value === "0x1" || value === "0x01" || value === "0x0001") { 195 | value = 1; 196 | } else { 197 | value = 0; 198 | } 199 | } 200 | return value; 201 | }); 202 | 203 | let result; 204 | if (functionInfo.stateMutability === "view" || functionInfo.stateMutability === "pure") { 205 | try { 206 | const returned = await contractFunction(...args); 207 | handleForm(returned); 208 | result = tryToDisplayAsText(returned); 209 | } catch (err) { 210 | console.error(err); 211 | } 212 | } else { 213 | const overrides = {}; 214 | if (txValue) { 215 | overrides.value = txValue; // ethers.utils.parseEther() 216 | } 217 | if (gasPrice) { 218 | overrides.gasPrice = gasPrice; 219 | } 220 | // Uncomment this if you want to skip the gas estimation for each transaction 221 | // overrides.gasLimit = hexlify(1200000); 222 | 223 | // console.log("Running with extras",extras) 224 | const returned = await tx(contractFunction(...args, overrides)); 225 | handleForm(returned); 226 | result = tryToDisplay(returned); 227 | } 228 | 229 | console.log("SETTING RESULT:", result); 230 | setReturnValue(result); 231 | triggerRefresh(true); 232 | }} 233 | > 234 | {buttonIcon} 235 |
236 | } 237 | /> 238 |
, 239 | ); 240 | 241 | return ( 242 |
243 | 244 |
253 | {functionInfo.name} 254 | 255 | {inputs} 256 | 257 | 258 | 259 | ); 260 | } 261 | -------------------------------------------------------------------------------- /packages/react-app/src/views/Hints.jsx: -------------------------------------------------------------------------------- 1 | import { Select } from "antd"; 2 | import React, { useState } from "react"; 3 | import { utils } from "ethers"; 4 | 5 | import { useTokenList } from "eth-hooks/dapps/dex"; 6 | import { Address, AddressInput } from "../components"; 7 | 8 | const { Option } = Select; 9 | 10 | export default function Hints({ yourLocalBalance, mainnetProvider, price, address }) { 11 | // Get a list of tokens from a tokenlist -> see tokenlists.org! 12 | const [selectedToken, setSelectedToken] = useState("Pick a token!"); 13 | const listOfTokens = useTokenList( 14 | "https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json", 15 | ); 16 | 17 | return ( 18 |
19 |
20 | 👷 21 | Edit your contract in 22 | 26 | packages/hardhat/contracts 27 | 28 |
29 | 30 |
31 | 🛰 32 | compile/deploy with 33 | 37 | yarn run deploy 38 | 39 |
40 | 41 |
42 | 🚀 43 | Your contract artifacts are automatically injected into your frontend at 44 | 48 | packages/react-app/src/contracts/ 49 | 50 |
51 | 52 |
53 | 🎛 54 | Edit your frontend in 55 | 59 | packages/reactapp/src/App.js 60 | 61 |
62 | 63 |
64 | 🔭 65 | explore the 66 | 77 | 🖇 hooks 78 | 79 | and 80 | 84 | 📦 components 85 | 86 |
87 | 88 |
89 | for example, the 90 | 94 | useBalance() 95 | {" "} 96 | hook keeps track of your balance: {utils.formatEther(yourLocalBalance || 0)} 97 |
98 | 99 |
100 |
101 | useTokenList() can get you an array of tokens from{" "} 102 | 103 | tokenlists.org! 104 | 105 |
106 | 122 |
123 | 124 |
125 | as you build your app you'll need web3 specific components like an 126 | 130 | {""} 131 | 132 | component: 133 |
134 | 135 |
136 |
(try putting in your address, an ens address, or scanning a QR code)
137 |
138 | 139 |
140 | this balance could be multiplied by 141 | 145 | price 146 | {" "} 147 | that is loaded with the 148 | 152 | usePrice 153 | {" "} 154 | hook with the current value: ${price} 155 |
156 | 157 |
158 | 💧 159 | use the faucet to send funds to 160 | 164 |
{address} 165 | 166 |
167 | 168 |
169 | 📡 170 | deploy to a testnet or mainnet by editing 171 | 175 | packages/hardhat/hardhat.config.js 176 | 177 | and running 178 | 182 | yarn run deploy 183 | 184 |
185 | 186 |
187 | 🔑 188 | 192 | yarn run generate 193 | 194 | will create a deployer account in 195 | 199 | packages/hardhat 200 | 201 |
202 | (use{" "} 203 | 212 | yarn run account 213 | {" "} 214 | to display deployer address and balance) 215 |
216 |
217 | 218 |
219 | ⚙️ 220 | build your app with 221 | 225 | yarn run build 226 | 227 |
228 | 229 |
230 | 🚢 231 | ship it! 232 | 236 | yarn run surge 237 | 238 | or 239 | 243 | yarn run s3 244 | 245 | or 246 | 250 | yarn run ipfs 251 | 252 |
253 | 254 |
255 | 💬 256 | for support, join this 257 | 261 | 262 | Telegram Chat 263 | 264 | 265 |
266 |
267 | 🛠 Check out your browser's developer console for more... (inspect console) 🚀 268 |
269 |
270 | ); 271 | } 272 | --------------------------------------------------------------------------------