├── .gitignore ├── .gitpod.yml ├── LICENSE ├── README.md ├── docker └── graph-node │ ├── Dockerfile │ ├── README.md │ ├── bin │ ├── create │ ├── debug │ ├── deploy │ ├── reassign │ └── remove │ ├── build.sh │ ├── cloudbuild.yaml │ ├── docker-compose.yml │ ├── hooks │ └── post_checkout │ ├── setup.sh │ ├── start │ ├── tag.sh │ └── wait_for ├── package.json ├── packages ├── hardhat │ ├── .eslintrc.js │ ├── contracts │ │ ├── Arbitrage.sol │ │ ├── Migrations.sol │ │ ├── UniswapV2Library.sol │ │ └── interfaces │ │ │ ├── IERC20.sol │ │ │ ├── IUniswapV2Callee.sol │ │ │ ├── IUniswapV2Factory.sol │ │ │ ├── IUniswapV2Pair.sol │ │ │ ├── IUniswapV2Router01.sol │ │ │ └── IUniswapV2Router02.sol │ ├── hardhat.config.js │ ├── package.json │ ├── scripts │ │ ├── deploy.js │ │ ├── publish.js │ │ └── watch.js │ └── test │ │ └── myTest.js ├── react-app │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .prettierrc │ ├── .sample.env │ ├── gulpfile.js │ ├── package.json │ ├── public │ │ ├── dark-theme.css │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── light-theme.css │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── robots.txt │ │ └── scaffold-eth.png │ ├── scripts │ │ ├── ipfs.js │ │ ├── s3.js │ │ └── watch.js │ └── src │ │ ├── App.css │ │ ├── App.jsx │ │ ├── App.test.js │ │ ├── components │ │ ├── Account.jsx │ │ ├── Address.jsx │ │ ├── AddressInput.jsx │ │ ├── Balance.jsx │ │ ├── Blockie.jsx │ │ ├── BytesStringInput.jsx │ │ ├── Contract │ │ │ ├── DisplayVariable.jsx │ │ │ ├── FunctionForm.jsx │ │ │ ├── index.jsx │ │ │ └── utils.js │ │ ├── EtherInput.jsx │ │ ├── Faucet.jsx │ │ ├── GasGauge.jsx │ │ ├── Header.jsx │ │ ├── Provider.jsx │ │ ├── Ramp.jsx │ │ ├── Swap.jsx │ │ ├── ThemeSwitch.jsx │ │ ├── Timeline.jsx │ │ ├── TokenBalance.jsx │ │ ├── Wallet.jsx │ │ └── index.js │ │ ├── constants.js │ │ ├── contracts │ │ └── contracts.js │ │ ├── ethereumLogo.png │ │ ├── helpers │ │ ├── Transactor.js │ │ └── index.js │ │ ├── hooks │ │ ├── Balance.js │ │ ├── ContractExistsAtAddress.js │ │ ├── ContractLoader.js │ │ ├── ContractReader.js │ │ ├── CustomContractLoader.js │ │ ├── Debounce.js │ │ ├── EventListener.js │ │ ├── ExchangePrice.js │ │ ├── ExternalContractLoader.js │ │ ├── GasPrice.js │ │ ├── LocalStorage.js │ │ ├── LookupAddress.js │ │ ├── Nonce.js │ │ ├── OnBlock.js │ │ ├── Poller.js │ │ ├── ResolveName.js │ │ ├── TokenList.js │ │ ├── UserProvider.js │ │ └── index.js │ │ ├── index.css │ │ ├── index.jsx │ │ ├── setupTests.js │ │ ├── themes │ │ ├── dark-theme.less │ │ └── light-theme.less │ │ └── views │ │ ├── ExampleUI.jsx │ │ ├── Hints.jsx │ │ ├── Subgraph.jsx │ │ └── index.js └── subgraph │ ├── abis │ └── YourContract.json │ ├── package.json │ └── src │ ├── mapping.ts │ ├── schema.graphql │ └── subgraph.template.yaml └── yarn.lock /.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 | packages/hardhat/artifacts 10 | packages/hardhat/deployments 11 | packages/react-app/src/contracts/* 12 | !packages/react-app/src/contracts/contracts.js 13 | packages/hardhat/cache 14 | 15 | docker/**/data 16 | 17 | packages/subgraph/config/config.json 18 | tenderly.yaml 19 | 20 | # dependencies 21 | /node_modules 22 | /.pnp 23 | .pnp.js 24 | 25 | # testing 26 | coverage 27 | 28 | # production 29 | build 30 | 31 | # misc 32 | .DS_Store 33 | .env* 34 | 35 | # debug 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | .idea 41 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: App 3 | init: > 4 | yarn && 5 | gp sync-done install 6 | command: REACT_APP_PROVIDER=$(gp url 8545) yarn start 7 | - name: Chain 8 | init: gp sync-await install 9 | command: yarn chain 10 | openMode: split-right 11 | - name: Deployment 12 | init: gp sync-await install 13 | command: yarn deploy 14 | openMode: split-right 15 | ports: 16 | - port: 3000 17 | onOpen: open-preview 18 | - port: 8545 19 | onOpen: ignore 20 | github: 21 | prebuilds: 22 | pullRequestsFromForks: true 23 | addComment: true 24 | vscode: 25 | extensions: 26 | - dbaeumer.vscode-eslint 27 | - esbenp.prettier-vscode 28 | - juanblanco.solidity -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/graph-node/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | graph-node: 4 | image: graphprotocol/graph-node 5 | ports: 6 | - '8000:8000' 7 | - '8001:8001' 8 | - '8020:8020' 9 | - '8030:8030' 10 | - '8040:8040' 11 | depends_on: 12 | - ipfs 13 | - postgres 14 | environment: 15 | postgres_host: postgres 16 | postgres_user: graph-node 17 | postgres_pass: let-me-in 18 | postgres_db: graph-node 19 | ipfs: 'ipfs:5001' 20 | ethereum: 'localhost:http://host.docker.internal:8545' 21 | RUST_LOG: info 22 | ipfs: 23 | image: ipfs/go-ipfs:v0.4.23 24 | ports: 25 | - '5001:5001' 26 | volumes: 27 | - ./data/ipfs:/data/ipfs 28 | postgres: 29 | image: postgres 30 | ports: 31 | - '5432:5432' 32 | command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] 33 | environment: 34 | POSTGRES_USER: graph-node 35 | POSTGRES_PASSWORD: let-me-in 36 | POSTGRES_DB: graph-node 37 | volumes: 38 | - ./data/postgres:/var/lib/postgresql/data 39 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/monorepo", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "ethereum", 6 | "react", 7 | "uniswap", 8 | "workspaces", 9 | "yarn" 10 | ], 11 | "private": true, 12 | "scripts": { 13 | "react-app:build": "yarn workspace @scaffold-eth/react-app build --max-old-space-size=12288", 14 | "react-app:eject": "yarn workspace @scaffold-eth/react-app eject", 15 | "react-app:start": "yarn workspace @scaffold-eth/react-app start", 16 | "react-app:test": "yarn workspace @scaffold-eth/react-app test", 17 | "build": "yarn workspace @scaffold-eth/react-app build --max-old-space-size=12288", 18 | "chain": "yarn workspace @scaffold-eth/hardhat chain", 19 | "fork": "yarn workspace @scaffold-eth/hardhat fork", 20 | "node": "yarn workspace @scaffold-eth/hardhat chain", 21 | "test": "yarn workspace @scaffold-eth/hardhat test", 22 | "start": "yarn workspace @scaffold-eth/react-app start", 23 | "compile": "yarn workspace @scaffold-eth/hardhat compile", 24 | "deploy": "yarn workspace @scaffold-eth/hardhat deploy", 25 | "watch": "yarn workspace @scaffold-eth/hardhat watch", 26 | "accounts": "yarn workspace @scaffold-eth/hardhat accounts", 27 | "balance": "yarn workspace @scaffold-eth/hardhat balance", 28 | "send": "yarn workspace @scaffold-eth/hardhat send", 29 | "ipfs": "yarn workspace @scaffold-eth/react-app ipfs", 30 | "surge": "yarn workspace @scaffold-eth/react-app surge", 31 | "s3": "yarn workspace @scaffold-eth/react-app s3", 32 | "ship": "yarn workspace @scaffold-eth/react-app ship", 33 | "generate": "yarn workspace @scaffold-eth/hardhat generate", 34 | "account": "yarn workspace @scaffold-eth/hardhat account", 35 | "mineContractAddress": "cd packages/hardhat && npx hardhat mineContractAddress", 36 | "wallet": "cd packages/hardhat && npx hardhat wallet", 37 | "fundedwallet": "cd packages/hardhat && npx hardhat fundedwallet", 38 | "flatten": "cd packages/hardhat && npx hardhat flatten", 39 | "clean": "cd packages/hardhat && npx hardhat clean", 40 | "graph-run-node": "cd docker/graph-node && docker-compose up", 41 | "graph-remove-node": "cd docker/graph-node && docker-compose down", 42 | "graph-prepare": "mustache packages/subgraph/config/config.json packages/subgraph/src/subgraph.template.yaml > packages/subgraph/subgraph.yaml", 43 | "graph-codegen": "yarn workspace @scaffold-eth/subgraph graph codegen", 44 | "graph-build": "yarn workspace @scaffold-eth/subgraph graph build", 45 | "graph-create-local": "yarn workspace @scaffold-eth/subgraph graph create --node http://localhost:8020/ scaffold-eth/your-contract", 46 | "graph-remove-local": "yarn workspace @scaffold-eth/subgraph graph remove --node http://localhost:8020/ scaffold-eth/your-contract", 47 | "graph-deploy-local": "yarn workspace @scaffold-eth/subgraph graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract", 48 | "graph-ship-local": "yarn graph-prepare && yarn graph-codegen && yarn graph-deploy-local", 49 | "deploy-and-graph": "yarn deploy && yarn graph-ship-local", 50 | "theme": "yarn workspace @scaffold-eth/react-app theme", 51 | "watch-theme": "yarn workspace @scaffold-eth/react-app watch" 52 | }, 53 | "workspaces": { 54 | "packages": [ 55 | "packages/*" 56 | ], 57 | "nohoist": [ 58 | "**/@graphprotocol/graph-ts", 59 | "**/@graphprotocol/graph-ts/**", 60 | "**/hardhat", 61 | "**/hardhat/**" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | ts: "never", 15 | }, 16 | ], 17 | "import/prefer-default-export": "off", 18 | "prefer-destructuring": "off", 19 | "prefer-template": "off", 20 | "no-console": "off", 21 | "func-names": "off", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/Arbitrage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.0 <0.9.0; 2 | //SPDX-License-Identifier: MIT 3 | 4 | import './UniswapV2Library.sol'; 5 | import './interfaces/IUniswapV2Router02.sol'; 6 | import './interfaces/IUniswapV2Pair.sol'; 7 | import './interfaces/IUniswapV2Factory.sol'; 8 | import './interfaces/IERC20.sol'; 9 | 10 | contract Arbitrage { 11 | address public factory; 12 | uint constant deadline = 10 days; 13 | IUniswapV2Router02 public sushiRouter; 14 | 15 | constructor(address _factory, address _sushiRouter) public { 16 | factory = _factory; 17 | sushiRouter = IUniswapV2Router02(_sushiRouter); 18 | } 19 | 20 | function startArbitrage( 21 | address token0, 22 | address token1, 23 | uint amount0, 24 | uint amount1 25 | ) external { 26 | address pairAddress = IUniswapV2Factory(factory).getPair(token0, token1); 27 | require(pairAddress != address(0), 'This pool does not exist'); 28 | IUniswapV2Pair(pairAddress).swap( 29 | amount0, 30 | amount1, 31 | address(this), 32 | bytes('not empty') 33 | ); 34 | } 35 | 36 | function uniswapV2Call( 37 | address _sender, 38 | uint _amount0, 39 | uint _amount1, 40 | bytes calldata _data 41 | ) external { 42 | address[] memory path = new address[](2); 43 | uint amountToken = _amount0 == 0 ? _amount1 : _amount0; 44 | 45 | address token0 = IUniswapV2Pair(msg.sender).token0(); 46 | address token1 = IUniswapV2Pair(msg.sender).token1(); 47 | 48 | require( 49 | msg.sender == UniswapV2Library.pairFor(factory, token0, token1), 50 | 'Unauthorized' 51 | ); 52 | require(_amount0 == 0 || _amount1 == 0); 53 | 54 | path[0] = _amount0 == 0 ? token1 : token0; 55 | path[1] = _amount0 == 0 ? token0 : token1; 56 | 57 | IERC20 token = IERC20(_amount0 == 0 ? token1 : token0); 58 | 59 | token.approve(address(sushiRouter), amountToken); 60 | 61 | uint amountRequired = UniswapV2Library.getAmountsIn( 62 | factory, 63 | amountToken, 64 | path 65 | )[0]; 66 | uint amountReceived = sushiRouter.swapExactTokensForTokens( 67 | amountToken, 68 | amountRequired, 69 | path, 70 | msg.sender, 71 | deadline 72 | )[1]; 73 | 74 | IERC20 otherToken = IERC20(_amount0 == 0 ? token0 : token1); 75 | otherToken.transfer(msg.sender, amountRequired); 76 | otherToken.transfer(tx.origin, amountReceived - amountRequired); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } -------------------------------------------------------------------------------- /packages/hardhat/contracts/UniswapV2Library.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | import './interfaces/IUniswapV2Pair.sol'; 4 | 5 | import "@openzeppelin/contracts/math/SafeMath.sol"; 6 | 7 | library UniswapV2Library { 8 | using SafeMath for uint; 9 | 10 | // returns sorted token addresses, used to handle return values from pairs sorted in this order 11 | function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { 12 | require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES'); 13 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 14 | require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS'); 15 | } 16 | 17 | // calculates the CREATE2 address for a pair without making any external calls 18 | function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { 19 | (address token0, address token1) = sortTokens(tokenA, tokenB); 20 | pair = address(uint(keccak256(abi.encodePacked( 21 | hex'ff', 22 | factory, 23 | keccak256(abi.encodePacked(token0, token1)), 24 | hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash 25 | )))); 26 | } 27 | 28 | // fetches and sorts the reserves for a pair 29 | function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) { 30 | (address token0,) = sortTokens(tokenA, tokenB); 31 | (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); 32 | (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); 33 | } 34 | 35 | // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset 36 | function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) { 37 | require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT'); 38 | require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); 39 | amountB = amountA.mul(reserveB) / reserveA; 40 | } 41 | 42 | // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset 43 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { 44 | require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT'); 45 | require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); 46 | uint amountInWithFee = amountIn.mul(997); 47 | uint numerator = amountInWithFee.mul(reserveOut); 48 | uint denominator = reserveIn.mul(1000).add(amountInWithFee); 49 | amountOut = numerator / denominator; 50 | } 51 | 52 | // given an output amount of an asset and pair reserves, returns a required input amount of the other asset 53 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { 54 | require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT'); 55 | require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); 56 | uint numerator = reserveIn.mul(amountOut).mul(1000); 57 | uint denominator = reserveOut.sub(amountOut).mul(997); 58 | amountIn = (numerator / denominator).add(1); 59 | } 60 | 61 | // performs chained getAmountOut calculations on any number of pairs 62 | function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) { 63 | require(path.length >= 2, 'UniswapV2Library: INVALID_PATH'); 64 | amounts = new uint[](path.length); 65 | amounts[0] = amountIn; 66 | for (uint i; i < path.length - 1; i++) { 67 | (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]); 68 | amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); 69 | } 70 | } 71 | 72 | // performs chained getAmountIn calculations on any number of pairs 73 | function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) { 74 | require(path.length >= 2, 'UniswapV2Library: INVALID_PATH'); 75 | amounts = new uint[](path.length); 76 | amounts[amounts.length - 1] = amountOut; 77 | for (uint i = path.length - 1; i > 0; i--) { 78 | (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]); 79 | amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /packages/hardhat/contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IERC20 { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external view returns (string memory); 8 | function symbol() external view returns (string memory); 9 | function decimals() external view returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | } 18 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/interfaces/IUniswapV2Callee.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Callee { 4 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; 5 | } 6 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Factory { 4 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 5 | function getPair(address tokenA, address tokenB) external view returns (address pair); 6 | function allPairs(uint) external view returns (address pair); 7 | function allPairsLength() external view returns (uint); 8 | function feeTo() external view returns (address); 9 | function feeToSetter() external view returns (address); 10 | function createPair(address tokenA, address tokenB) external returns (address pair); 11 | } 12 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Pair { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | function symbol() external pure returns (string memory); 9 | function decimals() external pure returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | 18 | function DOMAIN_SEPARATOR() external view returns (bytes32); 19 | function PERMIT_TYPEHASH() external pure returns (bytes32); 20 | function nonces(address owner) external view returns (uint); 21 | 22 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 23 | 24 | event Mint(address indexed sender, uint amount0, uint amount1); 25 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 26 | event Swap( 27 | address indexed sender, 28 | uint amount0In, 29 | uint amount1In, 30 | uint amount0Out, 31 | uint amount1Out, 32 | address indexed to 33 | ); 34 | event Sync(uint112 reserve0, uint112 reserve1); 35 | 36 | function MINIMUM_LIQUIDITY() external pure returns (uint); 37 | function factory() external view returns (address); 38 | function token0() external view returns (address); 39 | function token1() external view returns (address); 40 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 41 | function price0CumulativeLast() external view returns (uint); 42 | function price1CumulativeLast() external view returns (uint); 43 | function kLast() external view returns (uint); 44 | 45 | function mint(address to) external returns (uint liquidity); 46 | function burn(address to) external returns (uint amount0, uint amount1); 47 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 48 | function skim(address to) external; 49 | function sync() external; 50 | 51 | function initialize(address, address) external; 52 | } 53 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/interfaces/IUniswapV2Router01.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.2; 2 | 3 | interface IUniswapV2Router01 { 4 | function factory() external pure returns (address); 5 | function WETH() external pure returns (address); 6 | 7 | function addLiquidity( 8 | address tokenA, 9 | address tokenB, 10 | uint amountADesired, 11 | uint amountBDesired, 12 | uint amountAMin, 13 | uint amountBMin, 14 | address to, 15 | uint deadline 16 | ) external returns (uint amountA, uint amountB, uint liquidity); 17 | function addLiquidityETH( 18 | address token, 19 | uint amountTokenDesired, 20 | uint amountTokenMin, 21 | uint amountETHMin, 22 | address to, 23 | uint deadline 24 | ) external payable returns (uint amountToken, uint amountETH, uint liquidity); 25 | function removeLiquidity( 26 | address tokenA, 27 | address tokenB, 28 | uint liquidity, 29 | uint amountAMin, 30 | uint amountBMin, 31 | address to, 32 | uint deadline 33 | ) external returns (uint amountA, uint amountB); 34 | function removeLiquidityETH( 35 | address token, 36 | uint liquidity, 37 | uint amountTokenMin, 38 | uint amountETHMin, 39 | address to, 40 | uint deadline 41 | ) external returns (uint amountToken, uint amountETH); 42 | function removeLiquidityWithPermit( 43 | address tokenA, 44 | address tokenB, 45 | uint liquidity, 46 | uint amountAMin, 47 | uint amountBMin, 48 | address to, 49 | uint deadline, 50 | bool approveMax, uint8 v, bytes32 r, bytes32 s 51 | ) external returns (uint amountA, uint amountB); 52 | function removeLiquidityETHWithPermit( 53 | address token, 54 | uint liquidity, 55 | uint amountTokenMin, 56 | uint amountETHMin, 57 | address to, 58 | uint deadline, 59 | bool approveMax, uint8 v, bytes32 r, bytes32 s 60 | ) external returns (uint amountToken, uint amountETH); 61 | function swapExactTokensForTokens( 62 | uint amountIn, 63 | uint amountOutMin, 64 | address[] calldata path, 65 | address to, 66 | uint deadline 67 | ) external returns (uint[] memory amounts); 68 | function swapTokensForExactTokens( 69 | uint amountOut, 70 | uint amountInMax, 71 | address[] calldata path, 72 | address to, 73 | uint deadline 74 | ) external returns (uint[] memory amounts); 75 | function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) 76 | external 77 | payable 78 | returns (uint[] memory amounts); 79 | function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) 80 | external 81 | returns (uint[] memory amounts); 82 | function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) 83 | external 84 | returns (uint[] memory amounts); 85 | function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) 86 | external 87 | payable 88 | returns (uint[] memory amounts); 89 | 90 | function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); 91 | function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); 92 | function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); 93 | function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); 94 | function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); 95 | } 96 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/interfaces/IUniswapV2Router02.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.2; 2 | 3 | import './IUniswapV2Router01.sol'; 4 | 5 | interface IUniswapV2Router02 is IUniswapV2Router01 { 6 | function removeLiquidityETHSupportingFeeOnTransferTokens( 7 | address token, 8 | uint liquidity, 9 | uint amountTokenMin, 10 | uint amountETHMin, 11 | address to, 12 | uint deadline 13 | ) external returns (uint amountETH); 14 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 15 | address token, 16 | uint liquidity, 17 | uint amountTokenMin, 18 | uint amountETHMin, 19 | address to, 20 | uint deadline, 21 | bool approveMax, uint8 v, bytes32 r, bytes32 s 22 | ) external returns (uint amountETH); 23 | 24 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 25 | uint amountIn, 26 | uint amountOutMin, 27 | address[] calldata path, 28 | address to, 29 | uint deadline 30 | ) external; 31 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 32 | uint amountOutMin, 33 | address[] calldata path, 34 | address to, 35 | uint deadline 36 | ) external payable; 37 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 38 | uint amountIn, 39 | uint amountOutMin, 40 | address[] calldata path, 41 | address to, 42 | uint deadline 43 | ) external; 44 | } -------------------------------------------------------------------------------- /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.1.4" 12 | }, 13 | "dependencies": { 14 | "@nomiclabs/hardhat-ethers": "^2.0.0", 15 | "@nomiclabs/hardhat-etherscan": "^2.1.1", 16 | "@nomiclabs/hardhat-waffle": "^2.0.0", 17 | "@openzeppelin/contracts": "^3.2.0", 18 | "@tenderly/hardhat-tenderly": "^1.0.10", 19 | "chai": "^4.2.0", 20 | "chalk": "^4.1.0", 21 | "ethereum-waffle": "^3.1.1", 22 | "ethers": "^5.0.17", 23 | "hardhat": "^2.0.11", 24 | "node-watch": "^0.7.0", 25 | "qrcode-terminal": "^0.12.0", 26 | "ramda": "^0.27.1" 27 | }, 28 | "scripts": { 29 | "chain": "hardhat node", 30 | "fork": "hardhat node --fork https://mainnet.infura.io/v3/460f40a260564ac4a4f4b3fffb032dad", 31 | "test": "hardhat test --network hardhat", 32 | "compile": "hardhat compile", 33 | "deploy": "hardhat run scripts/deploy.js", 34 | "postdeploy": "hardhat run scripts/publish.js", 35 | "watch": "node scripts/watch.js", 36 | "accounts": "hardhat accounts", 37 | "balance": "hardhat balance", 38 | "send": "hardhat send", 39 | "generate": "hardhat generate", 40 | "account": "hardhat account" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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 | const main = async () => { 9 | 10 | console.log("\n\n 📡 Deploying...\n"); 11 | 12 | const yourContract = await deploy("Arbitrage", ["0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32", "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"]) // <-- add in constructor args like line 19 vvvv 13 | const migrations = await deploy("Migrations") 14 | const UniswapV2Library = await deploy("UniswapV2Library") 15 | 16 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 17 | //const secondContract = await deploy("SecondContract") 18 | 19 | // const exampleToken = await deploy("ExampleToken") 20 | // const examplePriceOracle = await deploy("ExamplePriceOracle") 21 | // const smartContractWallet = await deploy("SmartContractWallet",[exampleToken.address,examplePriceOracle.address]) 22 | 23 | /* 24 | //If you want to send value to an address from the deployer 25 | const deployerWallet = ethers.provider.getSigner() 26 | await deployerWallet.sendTransaction({ 27 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 28 | value: ethers.utils.parseEther("0.001") 29 | }) 30 | */ 31 | 32 | 33 | /* 34 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 35 | const yourContract = await deploy("YourContract", [], { 36 | value: ethers.utils.parseEther("0.05") 37 | }); 38 | */ 39 | 40 | 41 | /* 42 | //If you want to link a library into your contract: 43 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 44 | const yourContract = await deploy("YourContract", [], {}, { 45 | LibraryName: **LibraryAddress** 46 | }); 47 | */ 48 | 49 | 50 | //If you want to verify your contract on tenderly.co (see setup details in the scaffold-eth README!) 51 | /* 52 | await tenderlyVerify( 53 | {contractName: "YourContract", 54 | contractAddress: yourContract.address 55 | }) 56 | */ 57 | 58 | // If you want to verify your contract on etherscan 59 | /* 60 | console.log(chalk.blue('verifying on etherscan')) 61 | await run("verify:verify", { 62 | address: yourContract.address, 63 | // contract: "contracts/Example.sol:ExampleContract" // If you are inheriting from multiple contracts in yourContract.sol, you can specify which to verify 64 | // constructorArguments: args // If your contract has constructor arguments, you can pass them as an array 65 | }) 66 | */ 67 | 68 | console.log( 69 | " 💾 Artifacts (address, abi, and args) saved to: ", 70 | chalk.blue("packages/hardhat/artifacts/"), 71 | "\n\n" 72 | ); 73 | }; 74 | 75 | const deploy = async (contractName, _args = [], overrides = {}, libraries = {}) => { 76 | console.log(` 🛰 Deploying: ${contractName}`); 77 | 78 | const contractArgs = _args || []; 79 | const contractArtifacts = await ethers.getContractFactory(contractName,{libraries: libraries}); 80 | const deployed = await contractArtifacts.deploy(...contractArgs, overrides); 81 | const encoded = abiEncodeArgs(deployed, contractArgs); 82 | fs.writeFileSync(`artifacts/${contractName}.address`, deployed.address); 83 | 84 | let extraGasInfo = "" 85 | if(deployed&&deployed.deployTransaction){ 86 | const gasUsed = deployed.deployTransaction.gasLimit.mul(deployed.deployTransaction.gasPrice) 87 | extraGasInfo = `${utils.formatEther(gasUsed)} ETH, tx hash ${deployed.deployTransaction.hash}` 88 | } 89 | 90 | console.log( 91 | " 📄", 92 | chalk.cyan(contractName), 93 | "deployed to:", 94 | chalk.magenta(deployed.address) 95 | ); 96 | console.log( 97 | " ⛽", 98 | chalk.grey(extraGasInfo) 99 | ); 100 | 101 | await tenderly.persistArtifacts({ 102 | name: contractName, 103 | address: deployed.address 104 | }); 105 | 106 | if (!encoded || encoded.length <= 2) return deployed; 107 | fs.writeFileSync(`artifacts/${contractName}.args`, encoded.slice(2)); 108 | 109 | return deployed; 110 | }; 111 | 112 | 113 | // ------ utils ------- 114 | 115 | // abi encodes contract arguments 116 | // useful when you want to manually verify the contracts 117 | // for example, on Etherscan 118 | const abiEncodeArgs = (deployed, contractArgs) => { 119 | // not writing abi encoded args if this does not pass 120 | if ( 121 | !contractArgs || 122 | !deployed || 123 | !R.hasPath(["interface", "deploy"], deployed) 124 | ) { 125 | return ""; 126 | } 127 | const encoded = utils.defaultAbiCoder.encode( 128 | deployed.interface.deploy.inputs, 129 | contractArgs 130 | ); 131 | return encoded; 132 | }; 133 | 134 | // checks if it is a Solidity file 135 | const isSolidity = (fileName) => 136 | fileName.indexOf(".sol") >= 0 && fileName.indexOf(".swp") < 0 && fileName.indexOf(".swap") < 0; 137 | 138 | const readArgsFile = (contractName) => { 139 | let args = []; 140 | try { 141 | const argsFile = `./contracts/${contractName}.args`; 142 | if (!fs.existsSync(argsFile)) return args; 143 | args = JSON.parse(fs.readFileSync(argsFile)); 144 | } catch (e) { 145 | console.log(e); 146 | } 147 | return args; 148 | }; 149 | 150 | function sleep(ms) { 151 | return new Promise(resolve => setTimeout(resolve, ms)); 152 | } 153 | 154 | // If you want to verify on https://tenderly.co/ 155 | const tenderlyVerify = async ({contractName, contractAddress}) => { 156 | 157 | let tenderlyNetworks = ["kovan","goerli","mainnet","rinkeby","ropsten","matic","mumbai","xDai","POA"] 158 | let targetNetwork = process.env.HARDHAT_NETWORK || config.defaultNetwork 159 | 160 | if(tenderlyNetworks.includes(targetNetwork)) { 161 | console.log(chalk.blue(` 📁 Attempting tenderly verification of ${contractName} on ${targetNetwork}`)) 162 | 163 | await tenderly.persistArtifacts({ 164 | name: contractName, 165 | address: contractAddress 166 | }); 167 | 168 | let verification = await tenderly.verify({ 169 | name: contractName, 170 | address: contractAddress, 171 | network: targetNetwork 172 | }) 173 | 174 | return verification 175 | } else { 176 | console.log(chalk.grey(` 🧐 Contract verification not supported on ${targetNetwork}`)) 177 | } 178 | } 179 | 180 | main() 181 | .then(() => process.exit(0)) 182 | .catch((error) => { 183 | console.error(error); 184 | process.exit(1); 185 | }); 186 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/publish.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const chalk = require("chalk"); 3 | const bre = require("hardhat"); 4 | 5 | const publishDir = "../react-app/src/contracts"; 6 | const graphDir = "../subgraph" 7 | 8 | function publishContract(contractName) { 9 | console.log( 10 | " 💽 Publishing", 11 | chalk.cyan(contractName), 12 | "to", 13 | chalk.gray(publishDir) 14 | ); 15 | try { 16 | let contract = fs 17 | .readFileSync(`${bre.config.paths.artifacts}/contracts/${contractName}.sol/${contractName}.json`) 18 | .toString(); 19 | const address = fs 20 | .readFileSync(`${bre.config.paths.artifacts}/${contractName}.address`) 21 | .toString(); 22 | contract = JSON.parse(contract); 23 | let graphConfigPath = `${graphDir}/config/config.json` 24 | let graphConfig 25 | try { 26 | if (fs.existsSync(graphConfigPath)) { 27 | graphConfig = fs 28 | .readFileSync(graphConfigPath) 29 | .toString(); 30 | } else { 31 | graphConfig = '{}' 32 | } 33 | } catch (e) { 34 | console.log(e) 35 | } 36 | 37 | graphConfig = JSON.parse(graphConfig) 38 | graphConfig[contractName + "Address"] = address 39 | fs.writeFileSync( 40 | `${publishDir}/${contractName}.address.js`, 41 | `module.exports = "${address}";` 42 | ); 43 | fs.writeFileSync( 44 | `${publishDir}/${contractName}.abi.js`, 45 | `module.exports = ${JSON.stringify(contract.abi, null, 2)};` 46 | ); 47 | fs.writeFileSync( 48 | `${publishDir}/${contractName}.bytecode.js`, 49 | `module.exports = "${contract.bytecode}";` 50 | ); 51 | 52 | const folderPath = graphConfigPath.replace("/config.json","") 53 | if (!fs.existsSync(folderPath)){ 54 | fs.mkdirSync(folderPath); 55 | } 56 | fs.writeFileSync( 57 | graphConfigPath, 58 | JSON.stringify(graphConfig, null, 2) 59 | ); 60 | fs.writeFileSync( 61 | `${graphDir}/abis/${contractName}.json`, 62 | JSON.stringify(contract.abi, null, 2) 63 | ); 64 | 65 | console.log(" 📠 Published "+chalk.green(contractName)+" to the frontend.") 66 | 67 | return true; 68 | } catch (e) { 69 | if(e.toString().indexOf("no such file or directory")>=0){ 70 | console.log(chalk.yellow(" ⚠️ Can't publish "+contractName+" yet (make sure it getting deployed).")) 71 | }else{ 72 | console.log(e); 73 | return false; 74 | } 75 | } 76 | } 77 | 78 | async function main() { 79 | if (!fs.existsSync(publishDir)) { 80 | fs.mkdirSync(publishDir); 81 | } 82 | const finalContractList = []; 83 | fs.readdirSync(bre.config.paths.sources).forEach((file) => { 84 | if (file.indexOf(".sol") >= 0) { 85 | const contractName = file.replace(".sol", ""); 86 | // Add contract to list if publishing is successful 87 | if (publishContract(contractName)) { 88 | finalContractList.push(contractName); 89 | } 90 | } 91 | }); 92 | fs.writeFileSync( 93 | `${publishDir}/contracts.js`, 94 | `module.exports = ${JSON.stringify(finalContractList)};` 95 | ); 96 | } 97 | main() 98 | .then(() => process.exit(0)) 99 | .catch((error) => { 100 | console.error(error); 101 | process.exit(1); 102 | }); 103 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("🛠 Compiling & Deploying..."); 6 | exec("yarn deploy", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Contracts..."); 14 | watch("./contracts", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/hardhat/test/myTest.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | const { use, expect } = require("chai"); 3 | const { solidity } = require("ethereum-waffle"); 4 | 5 | use(solidity); 6 | 7 | describe("My Dapp", function () { 8 | let myContract; 9 | 10 | describe("YourContract", function () { 11 | it("Should deploy YourContract", async function () { 12 | const YourContract = await ethers.getContractFactory("YourContract"); 13 | 14 | myContract = await YourContract.deploy(); 15 | }); 16 | 17 | describe("setPurpose()", function () { 18 | it("Should be able to set a new purpose", async function () { 19 | const newPurpose = "Test Purpose"; 20 | 21 | await myContract.setPurpose(newPurpose); 22 | expect(await myContract.purpose()).to.equal(newPurpose); 23 | }); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/react-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | build/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /packages/react-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended", "prettier/react"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | jsx: "never", 15 | ts: "never", 16 | tsx: "never", 17 | }, 18 | ], 19 | "import/prefer-default-export": "off", 20 | "prefer-destructuring": "off", 21 | "prefer-template": "off", 22 | "react/prop-types": "off", 23 | "react/destructuring-assignment": "off", 24 | "no-console": "off", 25 | "jsx-a11y/accessible-emoji": ["off"], 26 | "jsx-a11y/click-events-have-key-events": ["off"], 27 | "jsx-a11y/no-static-element-interactions": ["off"], 28 | "no-underscore-dangle": "off", 29 | "no-nested-ternary": "off", 30 | "no-restricted-syntax": "off", 31 | "no-plusplus": "off", 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/react-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "printWidth": 120, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-app/.sample.env: -------------------------------------------------------------------------------- 1 | REACT_APP_PROVIDER=https://rinkeby.infura.io/v3/2717afb6bf164045b5d5468031b93f87 2 | -------------------------------------------------------------------------------- /packages/react-app/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const gulpless = require('gulp-less') 3 | const postcss = require('gulp-postcss') 4 | const debug = require('gulp-debug') 5 | var csso = require('gulp-csso') 6 | const autoprefixer = require('autoprefixer') 7 | const NpmImportPlugin = require('less-plugin-npm-import') 8 | 9 | gulp.task('less', function () { 10 | const plugins = [autoprefixer()] 11 | 12 | return gulp 13 | .src('src/themes/*-theme.less') 14 | .pipe(debug({title: 'Less files:'})) 15 | .pipe( 16 | gulpless({ 17 | javascriptEnabled: true, 18 | plugins: [new NpmImportPlugin({prefix: '~'})], 19 | }), 20 | ) 21 | .pipe(postcss(plugins)) 22 | .pipe( 23 | csso({ 24 | debug: true, 25 | }), 26 | ) 27 | .pipe(gulp.dest('./public')) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/react-app", 3 | "version": "1.0.0", 4 | "homepage": ".", 5 | "browserslist": { 6 | "production": [ 7 | ">0.2%", 8 | "not dead", 9 | "not op_mini all" 10 | ], 11 | "development": [ 12 | "last 1 chrome version", 13 | "last 1 firefox version", 14 | "last 1 safari version" 15 | ] 16 | }, 17 | "dependencies": { 18 | "@ant-design/icons": "^4.2.2", 19 | "@apollo/react-hooks": "^4.0.0", 20 | "@ethersproject/address": "^5.0.5", 21 | "@ethersproject/bignumber": "^5.0.8", 22 | "@ethersproject/bytes": "^5.0.5", 23 | "@ethersproject/contracts": "^5.0.5", 24 | "@ethersproject/providers": "^5.0.12", 25 | "@ethersproject/solidity": "^5.0.9", 26 | "@ethersproject/units": "^5.0.6", 27 | "@ramp-network/ramp-instant-sdk": "^2.2.0", 28 | "@testing-library/jest-dom": "^5.11.4", 29 | "@testing-library/react": "^11.1.0", 30 | "@testing-library/user-event": "^12.1.8", 31 | "@uniswap/sdk": "^3.0.3", 32 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 33 | "@walletconnect/web3-provider": "^1.3.1", 34 | "antd": "^4.7.0", 35 | "apollo-boost": "^0.4.9", 36 | "apollo-client": "^2.6.10", 37 | "apollo-utilities": "^1.3.4", 38 | "axios": "^0.20.0", 39 | "bnc-notify": "^1.5.0", 40 | "burner-provider": "^1.0.38", 41 | "dotenv": "^8.2.0", 42 | "eth-hooks": "^1.1.2", 43 | "ethers": "^5.0.31", 44 | "graphiql": "^1.0.5", 45 | "graphql": "^15.3.0", 46 | "isomorphic-fetch": "^3.0.0", 47 | "node-watch": "^0.7.1", 48 | "postcss": "^8.2.6", 49 | "qrcode.react": "^1.0.0", 50 | "react": "^16.14.0", 51 | "react-blockies": "^1.4.1", 52 | "react-css-theme-switcher": "^0.2.2", 53 | "react-dom": "^16.14.0", 54 | "react-qr-reader": "^2.2.1", 55 | "react-router-dom": "^5.2.0", 56 | "react-scripts": "^3.4.3", 57 | "web3modal": "^1.9.1" 58 | }, 59 | "devDependencies": { 60 | "@testing-library/dom": "^6.12.2", 61 | "@types/react": "^16.9.19", 62 | "autoprefixer": "^10.2.4", 63 | "chalk": "^4.1.0", 64 | "eslint": "^7.5.0", 65 | "eslint-config-airbnb": "^18.2.0", 66 | "eslint-config-prettier": "^6.11.0", 67 | "eslint-plugin-babel": "^5.3.1", 68 | "eslint-plugin-import": "^2.22.1", 69 | "eslint-plugin-jsx-a11y": "^6.4.1", 70 | "eslint-plugin-prettier": "^3.1.4", 71 | "eslint-plugin-react": "^7.22.0", 72 | "eslint-plugin-react-hooks": "^4.2.0", 73 | "gulp": "^4.0.2", 74 | "gulp-csso": "^4.0.1", 75 | "gulp-debug": "^4.0.0", 76 | "gulp-less": "^4.0.1", 77 | "gulp-postcss": "^9.0.0", 78 | "ipfs-http-client": "^45.0.0", 79 | "less-plugin-npm-import": "^2.1.0", 80 | "prettier": "^2.0.5", 81 | "s3-folder-upload": "^2.3.1", 82 | "surge": "^0.21.5" 83 | }, 84 | "eslintConfig": { 85 | "extends": "react-app" 86 | }, 87 | "scripts": { 88 | "build": "react-scripts build", 89 | "eject": "react-scripts eject", 90 | "start": "react-scripts start", 91 | "test": "react-scripts test", 92 | "lint": "eslint --config ./.eslintrc.js --ignore-path ./.eslintignore ./src/**/*", 93 | "ipfs": "node ./scripts/ipfs.js", 94 | "surge": "cp build/index.html build/200.html && surge ./build", 95 | "s3": "node ./scripts/s3.js", 96 | "ship": "yarn surge", 97 | "theme": "npx gulp less", 98 | "watch": "node ./scripts/watch.js" 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wojtekwtf/polygon-arbitrage-bot/9f13cc1e9f08edaa9be31a2fded2e3535d89f370/packages/react-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Ethereum App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wojtekwtf/polygon-arbitrage-bot/9f13cc1e9f08edaa9be31a2fded2e3535d89f370/packages/react-app/public/logo192.png -------------------------------------------------------------------------------- /packages/react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wojtekwtf/polygon-arbitrage-bot/9f13cc1e9f08edaa9be31a2fded2e3535d89f370/packages/react-app/public/logo512.png -------------------------------------------------------------------------------- /packages/react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "🏗 Scaffold-Eth App", 3 | "start_url": ".", 4 | "name": "🏗 Scaffold-Eth App", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | }, 11 | { 12 | "src": "logo192.png", 13 | "type": "image/png", 14 | "sizes": "192x192" 15 | }, 16 | { 17 | "src": "logo512.png", 18 | "type": "image/png", 19 | "sizes": "512x512" 20 | } 21 | ], 22 | "start_url": ".", 23 | "display": "standalone", 24 | "theme_color": "#000000", 25 | "background_color": "#ffffff" 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /packages/react-app/public/scaffold-eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wojtekwtf/polygon-arbitrage-bot/9f13cc1e9f08edaa9be31a2fded2e3535d89f370/packages/react-app/public/scaffold-eth.png -------------------------------------------------------------------------------- /packages/react-app/scripts/ipfs.js: -------------------------------------------------------------------------------- 1 | const ipfsAPI = require("ipfs-http-client"); 2 | const chalk = require("chalk"); 3 | const { clearLine } = require("readline"); 4 | 5 | const { globSource } = ipfsAPI; 6 | 7 | const infura = { host: "ipfs.infura.io", port: "5001", protocol: "https" }; 8 | // run your own ipfs daemon: https://docs.ipfs.io/how-to/command-line-quick-start/#install-ipfs 9 | // const localhost = { host: "localhost", port: "5001", protocol: "http" }; 10 | 11 | const ipfs = ipfsAPI(infura); 12 | 13 | const ipfsGateway = "https://ipfs.io/ipfs/"; 14 | 15 | const addOptions = { 16 | pin: true, 17 | }; 18 | 19 | const pushDirectoryToIPFS = async path => { 20 | try { 21 | const response = await ipfs.add(globSource(path, { recursive: true }), addOptions); 22 | return response; 23 | } catch (e) { 24 | return {}; 25 | } 26 | }; 27 | 28 | const publishHashToIPNS = async ipfsHash => { 29 | try { 30 | const response = await ipfs.name.publish(`/ipfs/${ipfsHash}`); 31 | return response; 32 | } catch (e) { 33 | return {}; 34 | } 35 | }; 36 | 37 | const nodeMayAllowPublish = ipfsClient => { 38 | // You must have your own IPFS node in order to publish an IPNS name 39 | // This contains a blacklist of known nodes which do not allow users to publish IPNS names. 40 | const nonPublishingNodes = ["ipfs.infura.io"]; 41 | const { host } = ipfsClient.getEndpointConfig(); 42 | return !nonPublishingNodes.some(nodeUrl => host.includes(nodeUrl)); 43 | }; 44 | 45 | const deploy = async () => { 46 | console.log("🛰 Sending to IPFS..."); 47 | const { cid } = await pushDirectoryToIPFS("./build"); 48 | if (!cid) { 49 | console.log(`📡 App deployment failed`); 50 | return false; 51 | } 52 | console.log(`📡 App deployed to IPFS with hash: ${chalk.cyan(cid.toString())}`); 53 | 54 | console.log(); 55 | 56 | let ipnsName = ""; 57 | if (nodeMayAllowPublish(ipfs)) { 58 | console.log(`✍️ Publishing /ipfs/${cid.toString()} to IPNS...`); 59 | process.stdout.write(" Publishing to IPNS can take up to roughly two minutes.\r"); 60 | ipnsName = (await publishHashToIPNS(cid.toString())).name; 61 | clearLine(process.stdout, 0); 62 | if (!ipnsName) { 63 | console.log(" Publishing IPNS name on node failed."); 64 | } 65 | console.log(`🔖 App published to IPNS with name: ${chalk.cyan(ipnsName)}`); 66 | console.log(); 67 | } 68 | 69 | console.log("🚀 Deployment to IPFS complete!"); 70 | console.log(); 71 | 72 | console.log(`Use the link${ipnsName && "s"} below to access your app:`); 73 | console.log(` IPFS: ${chalk.cyan(`${ipfsGateway}${cid.toString()}`)}`); 74 | if (ipnsName) { 75 | console.log(` IPNS: ${chalk.cyan(`${ipfsGateway}${ipnsName}`)}`); 76 | console.log(); 77 | console.log( 78 | "Each new deployment will have a unique IPFS hash while the IPNS name will always point at the most recent deployment.", 79 | ); 80 | console.log( 81 | "It is recommended that you share the IPNS link so that people always see the newest version of your app.", 82 | ); 83 | } 84 | console.log(); 85 | return true; 86 | }; 87 | 88 | deploy(); 89 | -------------------------------------------------------------------------------- /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 = ""; // <<---- SET YOUR BUCKET NAME AND CREATE aws.json ** see below vvvvvvvvvv 7 | 8 | if (!BUCKETNAME) { 9 | console.log("☢️ Enter a bucket name in packages/react-app/scripts/s3.js "); 10 | process.exit(1); 11 | } 12 | 13 | let credentials = {}; 14 | try { 15 | credentials = JSON.parse(fs.readFileSync("aws.json")); 16 | } catch (e) { 17 | console.log(e); 18 | console.log( 19 | '☢️ Create an aws.json credentials file in packages/react-app/ like { "accessKeyId": "xxx", "secretAccessKey": "xxx", "region": "xxx" } ', 20 | ); 21 | process.exit(1); 22 | } 23 | console.log("credentials", credentials); 24 | 25 | credentials.bucket = BUCKETNAME; 26 | 27 | // optional options to be passed as parameter to the method 28 | const options = { 29 | useFoldersForFileTypes: false, 30 | useIAMRoleCredentials: false, 31 | }; 32 | 33 | // optional cloudfront invalidation rule 34 | // const invalidation = { 35 | // awsDistributionId: "", 36 | // awsInvalidationPath: "/*" 37 | // } 38 | 39 | s3FolderUpload(directoryName, credentials, options /* , invalidation */); 40 | -------------------------------------------------------------------------------- /packages/react-app/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("Compiling & Generating..."); 6 | exec("npx gulp less", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Themes..."); 14 | watch("./src/themes", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import App from "./App"; 4 | 5 | test("renders learn react link", () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Account.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd"; 2 | import React from "react"; 3 | import { useThemeSwitcher } from "react-css-theme-switcher"; 4 | import Address from "./Address"; 5 | import Balance from "./Balance"; 6 | import Wallet from "./Wallet"; 7 | 8 | /* 9 | ~ What it does? ~ 10 | 11 | Displays an Address, Balance, and Wallet as one Account component, 12 | also allows users to log in to existing accounts and log out 13 | 14 | ~ How can I use? ~ 15 | 16 | 27 | 28 | ~ Features ~ 29 | 30 | - Provide address={address} and get balance corresponding to the given address 31 | - Provide localProvider={localProvider} to access balance on local network 32 | - Provide userProvider={userProvider} to display a wallet 33 | - Provide mainnetProvider={mainnetProvider} and your address will be replaced by ENS name 34 | (ex. "0xa870" => "user.eth") 35 | - Provide price={price} of ether and get your balance converted to dollars 36 | - Provide web3Modal={web3Modal}, loadWeb3Modal={loadWeb3Modal}, logoutOfWeb3Modal={logoutOfWeb3Modal} 37 | to be able to log in/log out to/from existing accounts 38 | - Provide blockExplorer={blockExplorer}, click on address and get the link 39 | (ex. by default "https://etherscan.io/" or for xdai "https://blockscout.com/poa/xdai/") 40 | */ 41 | 42 | export default function Account({ 43 | address, 44 | userProvider, 45 | localProvider, 46 | mainnetProvider, 47 | price, 48 | minimized, 49 | web3Modal, 50 | loadWeb3Modal, 51 | logoutOfWeb3Modal, 52 | blockExplorer, 53 | }) { 54 | const modalButtons = []; 55 | if (web3Modal) { 56 | if (web3Modal.cachedProvider) { 57 | modalButtons.push( 58 | , 67 | ); 68 | } else { 69 | modalButtons.push( 70 | , 80 | ); 81 | } 82 | } 83 | 84 | const { currentTheme } = useThemeSwitcher(); 85 | 86 | const display = minimized ? ( 87 | "" 88 | ) : ( 89 | 90 | {address ? ( 91 |
92 | ) : ( 93 | "Connecting..." 94 | )} 95 | 96 | 103 | 104 | ); 105 | 106 | return ( 107 |
108 | {display} 109 | {modalButtons} 110 |
111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Address.jsx: -------------------------------------------------------------------------------- 1 | import { Skeleton, Typography } from "antd"; 2 | import React from "react"; 3 | import Blockies from "react-blockies"; 4 | import { useThemeSwitcher } from "react-css-theme-switcher"; 5 | import { useLookupAddress } from "../hooks"; 6 | 7 | // changed value={address} to address={address} 8 | 9 | /* 10 | ~ What it does? ~ 11 | 12 | Displays an address with a blockie image and option to copy address 13 | 14 | ~ How can I use? ~ 15 | 16 |
22 | 23 | ~ Features ~ 24 | 25 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 26 | (ex. "0xa870" => "user.eth") 27 | - Provide blockExplorer={blockExplorer}, click on address and get the link 28 | (ex. by default "https://etherscan.io/" or for xdai "https://blockscout.com/poa/xdai/") 29 | - Provide fontSize={fontSize} to change the size of address text 30 | */ 31 | 32 | const { Text } = Typography; 33 | 34 | const blockExplorerLink = (address, blockExplorer) => 35 | `${blockExplorer || "https://etherscan.io/"}${"address/"}${address}`; 36 | 37 | export default function Address(props) { 38 | const address = props.value || props.address; 39 | 40 | const ens = useLookupAddress(props.ensProvider, address); 41 | 42 | const { currentTheme } = useThemeSwitcher(); 43 | 44 | if (!address) { 45 | return ( 46 | 47 | 48 | 49 | ); 50 | } 51 | 52 | let displayAddress = address.substr(0, 6); 53 | 54 | if (ens && ens.indexOf("0x") < 0) { 55 | displayAddress = ens; 56 | } else if (props.size === "short") { 57 | displayAddress += "..." + address.substr(-4); 58 | } else if (props.size === "long") { 59 | displayAddress = address; 60 | } 61 | 62 | const etherscanLink = blockExplorerLink(address, props.blockExplorer); 63 | if (props.minimized) { 64 | return ( 65 | 66 | 72 | 73 | 74 | 75 | ); 76 | } 77 | 78 | let text; 79 | if (props.onChange) { 80 | text = ( 81 | 82 | 88 | {displayAddress} 89 | 90 | 91 | ); 92 | } else { 93 | text = ( 94 | 95 | 101 | {displayAddress} 102 | 103 | 104 | ); 105 | } 106 | 107 | return ( 108 | 109 | 110 | 111 | 112 | 113 | {text} 114 | 115 | 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /packages/react-app/src/components/AddressInput.jsx: -------------------------------------------------------------------------------- 1 | import { CameraOutlined, QrcodeOutlined } from "@ant-design/icons"; 2 | import { Badge, Input } from "antd"; 3 | import { useLookupAddress } from "eth-hooks"; 4 | import React, { useCallback, useState } from "react"; 5 | import QrReader from "react-qr-reader"; 6 | import Blockie from "./Blockie"; 7 | 8 | // probably we need to change value={toAddress} to address={toAddress} 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Displays an address input with QR scan option 14 | 15 | ~ How can I use? ~ 16 | 17 | 24 | 25 | ~ Features ~ 26 | 27 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 28 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 29 | - Provide placeholder="Enter address" value for the input 30 | - Value of the address input is stored in value={toAddress} 31 | - Control input change by onChange={setToAddress} 32 | or onChange={address => { setToAddress(address);}} 33 | */ 34 | 35 | export default function AddressInput(props) { 36 | const [value, setValue] = useState(props.value); 37 | const [scan, setScan] = useState(false); 38 | 39 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 40 | const ens = useLookupAddress(props.ensProvider, currentValue); 41 | 42 | const scannerButton = ( 43 |
{ 46 | setScan(!scan); 47 | }} 48 | > 49 | }> 50 | 51 | {" "} 52 | Scan 53 |
54 | ); 55 | 56 | const { ensProvider, onChange } = props; 57 | const updateAddress = useCallback( 58 | async newValue => { 59 | if (typeof newValue !== "undefined") { 60 | let address = newValue; 61 | if (address.indexOf(".eth") > 0 || address.indexOf(".xyz") > 0) { 62 | try { 63 | const possibleAddress = await ensProvider.resolveName(address); 64 | if (possibleAddress) { 65 | address = possibleAddress; 66 | } 67 | // eslint-disable-next-line no-empty 68 | } catch (e) {} 69 | } 70 | setValue(address); 71 | if (typeof onChange === "function") { 72 | onChange(address); 73 | } 74 | } 75 | }, 76 | [ensProvider, onChange], 77 | ); 78 | 79 | const scanner = scan ? ( 80 |
{ 89 | setScan(false); 90 | }} 91 | > 92 | { 96 | console.log("SCAN ERROR", e); 97 | setScan(false); 98 | }} 99 | onScan={newValue => { 100 | if (newValue) { 101 | console.log("SCAN VALUE", newValue); 102 | let possibleNewValue = newValue; 103 | if (possibleNewValue.indexOf("/") >= 0) { 104 | possibleNewValue = possibleNewValue.substr(possibleNewValue.lastIndexOf("0x")); 105 | console.log("CLEANED VALUE", possibleNewValue); 106 | } 107 | setScan(false); 108 | updateAddress(possibleNewValue); 109 | } 110 | }} 111 | style={{ width: "100%" }} 112 | /> 113 |
114 | ) : ( 115 | "" 116 | ); 117 | 118 | return ( 119 |
120 | {scanner} 121 | } 128 | value={ens || currentValue} 129 | addonAfter={scannerButton} 130 | onChange={e => { 131 | updateAddress(e.target.value); 132 | }} 133 | /> 134 |
135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Balance.jsx: -------------------------------------------------------------------------------- 1 | import { formatEther } from "@ethersproject/units"; 2 | import React, { useState } from "react"; 3 | import { useBalance } from "../hooks"; 4 | 5 | /* 6 | ~ What it does? ~ 7 | 8 | Displays a balance of given address in ether & dollar 9 | 10 | ~ How can I use? ~ 11 | 12 | 17 | 18 | ~ If you already have the balance as a bignumber ~ 19 | 23 | 24 | ~ Features ~ 25 | 26 | - Provide address={address} and get balance corresponding to given address 27 | - Provide provider={mainnetProvider} to access balance on mainnet or any other network (ex. localProvider) 28 | - Provide price={price} of ether and get your balance converted to dollars 29 | */ 30 | 31 | export default function Balance(props) { 32 | const [dollarMode, setDollarMode] = useState(true); 33 | 34 | // const [listening, setListening] = useState(false); 35 | 36 | const balance = useBalance(props.provider, props.address); 37 | 38 | let floatBalance = parseFloat("0.00"); 39 | 40 | let usingBalance = balance; 41 | 42 | if (typeof props.balance !== "undefined") { 43 | usingBalance = props.balance; 44 | } 45 | if (typeof props.value !== "undefined") { 46 | usingBalance = props.value; 47 | } 48 | 49 | if (usingBalance) { 50 | const etherBalance = formatEther(usingBalance); 51 | parseFloat(etherBalance).toFixed(2); 52 | floatBalance = parseFloat(etherBalance); 53 | } 54 | 55 | let displayBalance = floatBalance.toFixed(4); 56 | 57 | const price = props.price || props.dollarMultiplier; 58 | 59 | if (price && dollarMode) { 60 | displayBalance = "$" + (floatBalance * price).toFixed(2); 61 | } 62 | 63 | return ( 64 | { 72 | setDollarMode(!dollarMode); 73 | }} 74 | > 75 | {displayBalance} 76 | 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Blockie.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blockies from "react-blockies"; 3 | 4 | // provides a blockie image for the address using "react-blockies" library 5 | 6 | export default function Blockie(props) { 7 | if (!props.address || typeof props.address.toLowerCase !== "function") { 8 | return ; 9 | } 10 | // eslint-disable-next-line react/jsx-props-no-spreading 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-app/src/components/BytesStringInput.jsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | const { utils, constants } = require("ethers"); 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Displays input field with options to convert between STRING and BYTES32 10 | 11 | ~ How can I use? ~ 12 | 13 | { 18 | setValue(value); 19 | }} 20 | /> 21 | 22 | ~ Features ~ 23 | 24 | - Provide value={value} to specify initial string 25 | - Provide placeholder="Enter value..." value for the input 26 | - Control input change by onChange={value => { setValue(value);}} 27 | 28 | */ 29 | 30 | export default function BytesStringInput(props) { 31 | const [mode, setMode] = useState("STRING"); 32 | const [display, setDisplay] = useState(); 33 | const [value, setValue] = useState(constants.HashZero); 34 | 35 | // current value is the value in bytes32 36 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 37 | 38 | const option = title => { 39 | return ( 40 |
{ 43 | if (mode === "STRING") { 44 | setMode("BYTES32"); 45 | if (!utils.isHexString(currentValue)) { 46 | /* in case user enters invalid bytes32 number, 47 | it considers it as string and converts to bytes32 */ 48 | const changedValue = utils.formatBytes32String(currentValue); 49 | setDisplay(changedValue); 50 | } else { 51 | setDisplay(currentValue); 52 | } 53 | } else { 54 | setMode("STRING"); 55 | if (currentValue && utils.isHexString(currentValue)) { 56 | setDisplay(utils.parseBytes32String(currentValue)); 57 | } else { 58 | setDisplay(currentValue); 59 | } 60 | } 61 | }} 62 | > 63 | {title} 64 |
65 | ); 66 | }; 67 | 68 | let addonAfter; 69 | if (mode === "STRING") { 70 | addonAfter = option("STRING 🔀"); 71 | } else { 72 | addonAfter = option("BYTES32 🔀"); 73 | } 74 | 75 | useEffect(() => { 76 | if (!currentValue) { 77 | setDisplay(""); 78 | } 79 | }, [currentValue]); 80 | 81 | return ( 82 | { 88 | const newValue = e.target.value; 89 | if (mode === "STRING") { 90 | // const ethValue = parseFloat(newValue) / props.price; 91 | // setValue(ethValue); 92 | if (typeof props.onChange === "function") { 93 | props.onChange(utils.formatBytes32String(newValue)); 94 | } 95 | setValue(utils.formatBytes32String(newValue)); 96 | setDisplay(newValue); 97 | } else { 98 | if (typeof props.onChange === "function") { 99 | props.onChange(newValue); 100 | } 101 | setValue(newValue); 102 | setDisplay(newValue); 103 | } 104 | }} 105 | /> 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/DisplayVariable.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/accessible-emoji */ 2 | /* eslint-disable jsx-a11y/anchor-is-valid */ 3 | import { Col, Divider, Row } from "antd"; 4 | import React, { useCallback, useEffect, useState } from "react"; 5 | import tryToDisplay from "./utils"; 6 | 7 | const DisplayVariable = ({ contractFunction, functionInfo, refreshRequired, triggerRefresh }) => { 8 | const [variable, setVariable] = useState(""); 9 | 10 | const refresh = useCallback(async () => { 11 | try { 12 | const funcResponse = await contractFunction(); 13 | setVariable(funcResponse); 14 | triggerRefresh(false); 15 | } catch (e) { 16 | console.log(e); 17 | } 18 | }, [setVariable, contractFunction, triggerRefresh]); 19 | 20 | useEffect(() => { 21 | refresh(); 22 | }, [refresh, refreshRequired, contractFunction]); 23 | 24 | return ( 25 |
26 | 27 | 36 | {functionInfo.name} 37 | 38 | 39 |

{tryToDisplay(variable)}

40 | 41 | 42 |

43 | 44 | 🔄 45 | 46 |

47 | 48 |
49 | 50 |
51 | ); 52 | }; 53 | 54 | export default DisplayVariable; 55 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/FunctionForm.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/click-events-have-key-events */ 2 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 3 | /* eslint-disable jsx-a11y/accessible-emoji */ 4 | import { BigNumber } from "@ethersproject/bignumber"; 5 | import { Button, Col, Divider, Input, Row, Tooltip } from "antd"; 6 | import React, { useState } from "react"; 7 | import Blockies from "react-blockies"; 8 | import { Transactor } from "../../helpers"; 9 | import tryToDisplay from "./utils"; 10 | 11 | const { utils } = require("ethers"); 12 | 13 | export default function FunctionForm({ contractFunction, functionInfo, provider, gasPrice, triggerRefresh }) { 14 | const [form, setForm] = useState({}); 15 | const [txValue, setTxValue] = useState(); 16 | const [returnValue, setReturnValue] = useState(); 17 | 18 | const tx = Transactor(provider, gasPrice); 19 | 20 | let inputIndex = 0; 21 | const inputs = functionInfo.inputs.map(input => { 22 | const key = functionInfo.name + "_" + input.name + "_" + input.type + "_" + inputIndex++; 23 | 24 | let buttons = ""; 25 | if (input.type === "bytes32") { 26 | buttons = ( 27 | 28 |
{ 32 | if (utils.isHexString(form[key])) { 33 | const formUpdate = { ...form }; 34 | formUpdate[key] = utils.parseBytes32String(form[key]); 35 | setForm(formUpdate); 36 | } else { 37 | const formUpdate = { ...form }; 38 | formUpdate[key] = utils.formatBytes32String(form[key]); 39 | setForm(formUpdate); 40 | } 41 | }} 42 | > 43 | #️⃣ 44 |
45 |
46 | ); 47 | } else if (input.type === "bytes") { 48 | buttons = ( 49 | 50 |
{ 54 | if (utils.isHexString(form[key])) { 55 | const formUpdate = { ...form }; 56 | formUpdate[key] = utils.toUtf8String(form[key]); 57 | setForm(formUpdate); 58 | } else { 59 | const formUpdate = { ...form }; 60 | formUpdate[key] = utils.hexlify(utils.toUtf8Bytes(form[key])); 61 | setForm(formUpdate); 62 | } 63 | }} 64 | > 65 | #️⃣ 66 |
67 |
68 | ); 69 | } else if (input.type === "uint256") { 70 | buttons = ( 71 | 72 |
{ 76 | const formUpdate = { ...form }; 77 | formUpdate[key] = utils.parseEther(form[key]); 78 | setForm(formUpdate); 79 | }} 80 | > 81 | ✴️ 82 |
83 |
84 | ); 85 | } else if (input.type === "address") { 86 | const possibleAddress = form[key] && form[key].toLowerCase && form[key].toLowerCase().trim(); 87 | if (possibleAddress && possibleAddress.length === 42) { 88 | buttons = ( 89 | 90 | 91 | 92 | ); 93 | } 94 | } 95 | 96 | return ( 97 |
98 | { 105 | const formUpdate = { ...form }; 106 | formUpdate[event.target.name] = event.target.value; 107 | setForm(formUpdate); 108 | }} 109 | suffix={buttons} 110 | /> 111 |
112 | ); 113 | }); 114 | 115 | const txValueInput = ( 116 |
117 | setTxValue(e.target.value)} 120 | value={txValue} 121 | addonAfter={ 122 |
123 | 124 | 125 | 126 |
{ 130 | const floatValue = parseFloat(txValue); 131 | if (floatValue) setTxValue("" + floatValue * 10 ** 18); 132 | }} 133 | > 134 | ✳️ 135 |
136 |
137 | 138 | 139 | 140 |
{ 144 | setTxValue(BigNumber.from(txValue).toHexString()); 145 | }} 146 | > 147 | #️⃣ 148 |
149 |
150 | 151 |
152 |
153 | } 154 | /> 155 |
156 | ); 157 | 158 | if (functionInfo.payable) { 159 | inputs.push(txValueInput); 160 | } 161 | 162 | const buttonIcon = 163 | functionInfo.type === "call" ? ( 164 | 165 | ) : ( 166 | 167 | ); 168 | inputs.push( 169 |
170 | setReturnValue(e.target.value)} 172 | defaultValue="" 173 | bordered={false} 174 | disabled 175 | value={returnValue} 176 | suffix={ 177 |
{ 181 | let innerIndex = 0; 182 | const args = functionInfo.inputs.map(input => { 183 | const key = functionInfo.name + "_" + input.name + "_" + input.type + "_" + innerIndex++; 184 | let value = form[key]; 185 | if (input.baseType === "array") { 186 | value = JSON.parse(value); 187 | } else if (input.type === "bool") { 188 | if (value === "true" || value === "1" || value === "0x1" || value === "0x01" || value === "0x0001") { 189 | value = 1; 190 | } else { 191 | value = 0; 192 | } 193 | } 194 | return value; 195 | }); 196 | 197 | let result; 198 | if (functionInfo.stateMutability === "view" || functionInfo.stateMutability === "pure") { 199 | const returned = await contractFunction(...args); 200 | result = tryToDisplay(returned); 201 | } else { 202 | const overrides = {}; 203 | if (txValue) { 204 | overrides.value = txValue; // ethers.utils.parseEther() 205 | } 206 | // Uncomment this if you want to skip the gas estimation for each transaction 207 | // overrides.gasLimit = hexlify(1200000); 208 | 209 | // console.log("Running with extras",extras) 210 | const returned = await tx(contractFunction(...args, overrides)); 211 | result = tryToDisplay(returned); 212 | } 213 | 214 | console.log("SETTING RESULT:", result); 215 | setReturnValue(result); 216 | triggerRefresh(true); 217 | }} 218 | > 219 | {buttonIcon} 220 |
221 | } 222 | /> 223 |
, 224 | ); 225 | 226 | return ( 227 |
228 | 229 | 238 | {functionInfo.name} 239 | 240 | {inputs} 241 | 242 | 243 |
244 | ); 245 | } 246 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/index.jsx: -------------------------------------------------------------------------------- 1 | import { Card } from "antd"; 2 | import React, { useMemo, useState } from "react"; 3 | import { useContractExistsAtAddress, useContractLoader } from "../../hooks"; 4 | import Account from "../Account"; 5 | import DisplayVariable from "./DisplayVariable"; 6 | import FunctionForm from "./FunctionForm"; 7 | 8 | const noContractDisplay = ( 9 |
10 | Loading...{" "} 11 |
12 | You need to run{" "} 13 | 17 | yarn run chain 18 | {" "} 19 | and{" "} 20 | 24 | yarn run deploy 25 | {" "} 26 | to see your contract here. 27 |
28 |
29 | 30 | ☢️ 31 | 32 | Warning: You might need to run 33 | 37 | yarn run deploy 38 | {" "} 39 | again after the frontend comes up! 40 |
41 |
42 | ); 43 | 44 | const isQueryable = fn => (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0; 45 | 46 | export default function Contract({ 47 | customContract, 48 | account, 49 | gasPrice, 50 | signer, 51 | provider, 52 | name, 53 | show, 54 | price, 55 | blockExplorer, 56 | }) { 57 | const contracts = useContractLoader(provider); 58 | let contract; 59 | if (!customContract) { 60 | contract = contracts ? contracts[name] : ""; 61 | } else { 62 | contract = customContract; 63 | } 64 | 65 | const address = contract ? contract.address : ""; 66 | const contractIsDeployed = useContractExistsAtAddress(provider, address); 67 | 68 | const displayedContractFunctions = useMemo( 69 | () => 70 | contract 71 | ? Object.values(contract.interface.functions).filter( 72 | fn => fn.type === "function" && !(show && show.indexOf(fn.name) < 0), 73 | ) 74 | : [], 75 | [contract, show], 76 | ); 77 | 78 | const [refreshRequired, triggerRefresh] = useState(false); 79 | const contractDisplay = displayedContractFunctions.map(fn => { 80 | if (isQueryable(fn)) { 81 | // If there are no inputs, just display return value 82 | return ( 83 | 90 | ); 91 | } 92 | // If there are inputs, display a form to allow users to provide these 93 | return ( 94 | 106 | ); 107 | }); 108 | 109 | return ( 110 |
111 | 114 | {name} 115 |
116 | 124 | {account} 125 |
126 |
127 | } 128 | size="large" 129 | style={{ marginTop: 25, width: "100%" }} 130 | loading={contractDisplay && contractDisplay.length <= 0} 131 | > 132 | {contractIsDeployed ? contractDisplay : noContractDisplay} 133 | 134 | 135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/utils.js: -------------------------------------------------------------------------------- 1 | import { formatUnits } from "@ethersproject/units"; 2 | import React from "react"; 3 | import { Address } from ".."; 4 | 5 | const tryToDisplay = thing => { 6 | if (thing && thing.toNumber) { 7 | try { 8 | return thing.toNumber(); 9 | } catch (e) { 10 | return "Ξ" + formatUnits(thing, "ether"); 11 | } 12 | } 13 | if (thing && thing.indexOf && thing.indexOf("0x") === 0 && thing.length === 42) { 14 | return
; 15 | } 16 | return JSON.stringify(thing); 17 | }; 18 | 19 | export default tryToDisplay; 20 | -------------------------------------------------------------------------------- /packages/react-app/src/components/EtherInput.jsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | // small change in useEffect, display currentValue if it's provided by user 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Displays input field for ETH/USD amount, with an option to convert between ETH and USD 10 | 11 | ~ How can I use? ~ 12 | 13 | { 19 | setAmount(value); 20 | }} 21 | /> 22 | 23 | ~ Features ~ 24 | 25 | - Provide price={price} of ether and easily convert between USD and ETH 26 | - Provide value={value} to specify initial amount of ether 27 | - Provide placeholder="Enter amount" value for the input 28 | - Control input change by onChange={value => { setAmount(value);}} 29 | */ 30 | 31 | export default function EtherInput(props) { 32 | const [mode, setMode] = useState(props.price ? "USD" : "ETH"); 33 | const [display, setDisplay] = useState(); 34 | const [value, setValue] = useState(); 35 | 36 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 37 | 38 | const option = title => { 39 | if (!props.price) return ""; 40 | return ( 41 |
{ 44 | if (mode === "USD") { 45 | setMode("ETH"); 46 | setDisplay(currentValue); 47 | } else { 48 | setMode("USD"); 49 | if (currentValue) { 50 | const usdValue = "" + (parseFloat(currentValue) * props.price).toFixed(2); 51 | setDisplay(usdValue); 52 | } else { 53 | setDisplay(currentValue); 54 | } 55 | } 56 | }} 57 | > 58 | {title} 59 |
60 | ); 61 | }; 62 | 63 | let prefix; 64 | let addonAfter; 65 | if (mode === "USD") { 66 | prefix = "$"; 67 | addonAfter = option("USD 🔀"); 68 | } else { 69 | prefix = "Ξ"; 70 | addonAfter = option("ETH 🔀"); 71 | } 72 | 73 | useEffect(() => { 74 | if (!currentValue) { 75 | setDisplay(""); 76 | } 77 | }, [currentValue]); 78 | 79 | return ( 80 | { 87 | const newValue = e.target.value; 88 | if (mode === "USD") { 89 | const possibleNewValue = parseFloat(newValue); 90 | if (possibleNewValue) { 91 | const ethValue = possibleNewValue / props.price; 92 | setValue(ethValue); 93 | if (typeof props.onChange === "function") { 94 | props.onChange(ethValue); 95 | } 96 | setDisplay(newValue); 97 | } else { 98 | setDisplay(newValue); 99 | } 100 | } else { 101 | setValue(newValue); 102 | if (typeof props.onChange === "function") { 103 | props.onChange(newValue); 104 | } 105 | setDisplay(newValue); 106 | } 107 | }} 108 | /> 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Faucet.jsx: -------------------------------------------------------------------------------- 1 | import { SendOutlined } from "@ant-design/icons"; 2 | import { parseEther } from "@ethersproject/units"; 3 | import { Button, Input, Tooltip } from "antd"; 4 | import { useLookupAddress } from "eth-hooks"; 5 | import React, { useCallback, useState } from "react"; 6 | import Blockies from "react-blockies"; 7 | import { Transactor } from "../helpers"; 8 | import Wallet from "./Wallet"; 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 | 41 | let blockie; 42 | if (address && typeof address.toLowerCase === "function") { 43 | blockie = ; 44 | } else { 45 | blockie =
; 46 | } 47 | 48 | const ens = useLookupAddress(props.ensProvider, address); 49 | 50 | const updateAddress = useCallback( 51 | async newValue => { 52 | if (typeof newValue !== "undefined") { 53 | let address = newValue; 54 | if (address.indexOf(".eth") > 0 || address.indexOf(".xyz") > 0) { 55 | try { 56 | const possibleAddress = await props.ensProvider.resolveName(address); 57 | if (possibleAddress) { 58 | address = possibleAddress; 59 | } 60 | // eslint-disable-next-line no-empty 61 | } catch (e) {} 62 | } 63 | setAddress(address); 64 | } 65 | }, 66 | [props.ensProvider, props.onChange], 67 | ); 68 | 69 | const tx = Transactor(props.localProvider); 70 | 71 | return ( 72 | 73 | { 80 | // setAddress(e.target.value); 81 | updateAddress(e.target.value); 82 | }} 83 | suffix={ 84 | 85 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from "antd"; 2 | import React from "react"; 3 | 4 | // displays a page header 5 | 6 | export default function Header() { 7 | return ( 8 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Provider.jsx: -------------------------------------------------------------------------------- 1 | import { Badge, Button } from "antd"; 2 | import { useBlockNumber, usePoller } from "eth-hooks"; 3 | import React, { useState } from "react"; 4 | // import { WalletOutlined } from '@ant-design/icons'; 5 | import Address from "./Address"; 6 | 7 | export default function Provider(props) { 8 | const [showMore, setShowMore] = useState(false); 9 | const [status, setStatus] = useState("processing"); 10 | const [network, setNetwork] = useState(); 11 | const [signer, setSigner] = useState(); 12 | const [address, setAddress] = useState(); 13 | 14 | const blockNumber = useBlockNumber(props.provider); 15 | 16 | usePoller(async () => { 17 | if (props.provider && typeof props.provider.getNetwork === "function") { 18 | try { 19 | const newNetwork = await props.provider.getNetwork(); 20 | setNetwork(newNetwork); 21 | if (newNetwork.chainId > 0) { 22 | setStatus("success"); 23 | } else { 24 | setStatus("warning"); 25 | } 26 | } catch (e) { 27 | console.log(e); 28 | setStatus("processing"); 29 | } 30 | try { 31 | const newSigner = await props.provider.getSigner(); 32 | setSigner(newSigner); 33 | const newAddress = await newSigner.getAddress(); 34 | setAddress(newAddress); 35 | // eslint-disable-next-line no-empty 36 | } catch (e) {} 37 | } 38 | }, 1377); 39 | 40 | if ( 41 | typeof props.provider === "undefined" || 42 | typeof props.provider.getNetwork !== "function" || 43 | !network || 44 | !network.chainId 45 | ) { 46 | return ( 47 | 56 | ); 57 | } 58 | 59 | let showExtra = ""; 60 | if (showMore) { 61 | showExtra = ( 62 | 63 | 64 | id: 65 | {network ? network.chainId : ""} 66 | 67 | 68 | name: 69 | {network ? network.name : ""} 70 | 71 | 72 | ); 73 | } 74 | 75 | let showWallet = ""; 76 | if (typeof signer !== "undefined" && address) { 77 | showWallet = ( 78 | 79 | 80 |
81 | 82 | 83 | ); 84 | } 85 | 86 | return ( 87 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Ramp.jsx: -------------------------------------------------------------------------------- 1 | import { DollarCircleOutlined } from "@ant-design/icons"; 2 | import { RampInstantSDK } from "@ramp-network/ramp-instant-sdk"; 3 | import { Button, Divider, Modal } from "antd"; 4 | import React, { useState } from "react"; 5 | 6 | // added display of 0 if price={price} is not provided 7 | 8 | /* 9 | ~ What it does? ~ 10 | 11 | Displays current ETH price and gives options to buy ETH through Wyre/Ramp/Coinbase 12 | or get through Rinkeby/Ropsten/Kovan/Goerli 13 | 14 | ~ How can I use? ~ 15 | 16 | 20 | 21 | ~ Features ~ 22 | 23 | - Ramp opens directly in the application, component uses RampInstantSDK 24 | - Provide price={price} and current ETH price will be displayed 25 | - Provide address={address} and your address will be pasted into Wyre/Ramp instantly 26 | */ 27 | 28 | export default function Ramp(props) { 29 | const [modalUp, setModalUp] = useState("down"); 30 | 31 | const type = "default"; 32 | 33 | const allFaucets = []; 34 | for (const n in props.networks) { 35 | if (props.networks[n].chainId !== 31337 && props.networks[n].chainId !== 1) { 36 | allFaucets.push( 37 |

38 | 49 |

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

84 | 99 |

100 |

101 | {" "} 102 | 125 |

126 | 127 |

128 | 141 |

142 | 143 | 144 | 145 |

Testnet ETH

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

Known Private Keys:

187 | {extraPkDisplay} 188 | 202 |
203 | ) : ( 204 | "" 205 | )} 206 |
207 | ); 208 | } 209 | 210 | receiveButton = ( 211 | 220 | ); 221 | privateKeyButton = ( 222 | 231 | ); 232 | } else { 233 | const inputStyle = { 234 | padding: 10, 235 | }; 236 | 237 | display = ( 238 |
239 |
240 | 247 |
248 |
249 | { 253 | setAmount(value); 254 | }} 255 | /> 256 |
257 |
258 | ); 259 | receiveButton = ( 260 | 269 | ); 270 | privateKeyButton = ( 271 | 280 | ); 281 | } 282 | 283 | return ( 284 | 285 | {providerSend} 286 | 290 | {selectedAddress ?
: } 291 |
292 | 293 |
294 |
295 | } 296 | onOk={() => { 297 | setQr(); 298 | setPK(); 299 | setOpen(!open); 300 | }} 301 | onCancel={() => { 302 | setQr(); 303 | setPK(); 304 | setOpen(!open); 305 | }} 306 | footer={[ 307 | privateKeyButton, 308 | receiveButton, 309 | , 335 | ]} 336 | > 337 | {display} 338 | 339 | 340 | ); 341 | } 342 | -------------------------------------------------------------------------------- /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 Faucet } from "./Faucet"; 10 | export { default as GasGauge } from "./GasGauge"; 11 | export { default as Header } from "./Header"; 12 | export { default as Provider } from "./Provider"; 13 | export { default as Ramp } from "./Ramp"; 14 | export { default as Swap } from "./Swap"; 15 | export { default as ThemeSwitch } from "./ThemeSwitch"; 16 | export { default as Timeline } from "./Timeline"; 17 | export { default as TokenBalance } from "./TokenBalance"; 18 | export { default as Wallet } from "./Wallet"; 19 | -------------------------------------------------------------------------------- /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 = "PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8"; 6 | 7 | // BLOCKNATIVE ID FOR Notify.js: 8 | export const BLOCKNATIVE_DAPPID = "0b58206a-f3c0-4701-a62f-73c7243e8c77"; 9 | 10 | // EXTERNAL CONTRACTS 11 | 12 | export const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; 13 | 14 | export const DAI_ABI = [ 15 | { 16 | inputs: [{ internalType: "uint256", name: "chainId_", type: "uint256" }], 17 | payable: false, 18 | stateMutability: "nonpayable", 19 | type: "constructor", 20 | }, 21 | { 22 | anonymous: false, 23 | inputs: [ 24 | { indexed: true, internalType: "address", name: "src", type: "address" }, 25 | { indexed: true, internalType: "address", name: "guy", type: "address" }, 26 | { indexed: false, internalType: "uint256", name: "wad", type: "uint256" }, 27 | ], 28 | name: "Approval", 29 | type: "event", 30 | }, 31 | { 32 | anonymous: true, 33 | inputs: [ 34 | { indexed: true, internalType: "bytes4", name: "sig", type: "bytes4" }, 35 | { indexed: true, internalType: "address", name: "usr", type: "address" }, 36 | { indexed: true, internalType: "bytes32", name: "arg1", type: "bytes32" }, 37 | { indexed: true, internalType: "bytes32", name: "arg2", type: "bytes32" }, 38 | { indexed: false, internalType: "bytes", name: "data", type: "bytes" }, 39 | ], 40 | name: "LogNote", 41 | type: "event", 42 | }, 43 | { 44 | anonymous: false, 45 | inputs: [ 46 | { indexed: true, internalType: "address", name: "src", type: "address" }, 47 | { indexed: true, internalType: "address", name: "dst", type: "address" }, 48 | { indexed: false, internalType: "uint256", name: "wad", type: "uint256" }, 49 | ], 50 | name: "Transfer", 51 | type: "event", 52 | }, 53 | { 54 | constant: true, 55 | inputs: [], 56 | name: "DOMAIN_SEPARATOR", 57 | outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], 58 | payable: false, 59 | stateMutability: "view", 60 | type: "function", 61 | }, 62 | { 63 | constant: true, 64 | inputs: [], 65 | name: "PERMIT_TYPEHASH", 66 | outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], 67 | payable: false, 68 | stateMutability: "view", 69 | type: "function", 70 | }, 71 | { 72 | constant: true, 73 | inputs: [ 74 | { internalType: "address", name: "", type: "address" }, 75 | { internalType: "address", name: "", type: "address" }, 76 | ], 77 | name: "allowance", 78 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 79 | payable: false, 80 | stateMutability: "view", 81 | type: "function", 82 | }, 83 | { 84 | constant: false, 85 | inputs: [ 86 | { internalType: "address", name: "usr", type: "address" }, 87 | { internalType: "uint256", name: "wad", type: "uint256" }, 88 | ], 89 | name: "approve", 90 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 91 | payable: false, 92 | stateMutability: "nonpayable", 93 | type: "function", 94 | }, 95 | { 96 | constant: true, 97 | inputs: [{ internalType: "address", name: "", type: "address" }], 98 | name: "balanceOf", 99 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 100 | payable: false, 101 | stateMutability: "view", 102 | type: "function", 103 | }, 104 | { 105 | constant: false, 106 | inputs: [ 107 | { internalType: "address", name: "usr", type: "address" }, 108 | { internalType: "uint256", name: "wad", type: "uint256" }, 109 | ], 110 | name: "burn", 111 | outputs: [], 112 | payable: false, 113 | stateMutability: "nonpayable", 114 | type: "function", 115 | }, 116 | { 117 | constant: true, 118 | inputs: [], 119 | name: "decimals", 120 | outputs: [{ internalType: "uint8", name: "", type: "uint8" }], 121 | payable: false, 122 | stateMutability: "view", 123 | type: "function", 124 | }, 125 | { 126 | constant: false, 127 | inputs: [{ internalType: "address", name: "guy", type: "address" }], 128 | name: "deny", 129 | outputs: [], 130 | payable: false, 131 | stateMutability: "nonpayable", 132 | type: "function", 133 | }, 134 | { 135 | constant: false, 136 | inputs: [ 137 | { internalType: "address", name: "usr", type: "address" }, 138 | { internalType: "uint256", name: "wad", type: "uint256" }, 139 | ], 140 | name: "mint", 141 | outputs: [], 142 | payable: false, 143 | stateMutability: "nonpayable", 144 | type: "function", 145 | }, 146 | { 147 | constant: false, 148 | inputs: [ 149 | { internalType: "address", name: "src", type: "address" }, 150 | { internalType: "address", name: "dst", type: "address" }, 151 | { internalType: "uint256", name: "wad", type: "uint256" }, 152 | ], 153 | name: "move", 154 | outputs: [], 155 | payable: false, 156 | stateMutability: "nonpayable", 157 | type: "function", 158 | }, 159 | { 160 | constant: true, 161 | inputs: [], 162 | name: "name", 163 | outputs: [{ internalType: "string", name: "", type: "string" }], 164 | payable: false, 165 | stateMutability: "view", 166 | type: "function", 167 | }, 168 | { 169 | constant: true, 170 | inputs: [{ internalType: "address", name: "", type: "address" }], 171 | name: "nonces", 172 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 173 | payable: false, 174 | stateMutability: "view", 175 | type: "function", 176 | }, 177 | { 178 | constant: false, 179 | inputs: [ 180 | { internalType: "address", name: "holder", type: "address" }, 181 | { internalType: "address", name: "spender", type: "address" }, 182 | { internalType: "uint256", name: "nonce", type: "uint256" }, 183 | { internalType: "uint256", name: "expiry", type: "uint256" }, 184 | { internalType: "bool", name: "allowed", type: "bool" }, 185 | { internalType: "uint8", name: "v", type: "uint8" }, 186 | { internalType: "bytes32", name: "r", type: "bytes32" }, 187 | { internalType: "bytes32", name: "s", type: "bytes32" }, 188 | ], 189 | name: "permit", 190 | outputs: [], 191 | payable: false, 192 | stateMutability: "nonpayable", 193 | type: "function", 194 | }, 195 | { 196 | constant: false, 197 | inputs: [ 198 | { internalType: "address", name: "usr", type: "address" }, 199 | { internalType: "uint256", name: "wad", type: "uint256" }, 200 | ], 201 | name: "pull", 202 | outputs: [], 203 | payable: false, 204 | stateMutability: "nonpayable", 205 | type: "function", 206 | }, 207 | { 208 | constant: false, 209 | inputs: [ 210 | { internalType: "address", name: "usr", type: "address" }, 211 | { internalType: "uint256", name: "wad", type: "uint256" }, 212 | ], 213 | name: "push", 214 | outputs: [], 215 | payable: false, 216 | stateMutability: "nonpayable", 217 | type: "function", 218 | }, 219 | { 220 | constant: false, 221 | inputs: [{ internalType: "address", name: "guy", type: "address" }], 222 | name: "rely", 223 | outputs: [], 224 | payable: false, 225 | stateMutability: "nonpayable", 226 | type: "function", 227 | }, 228 | { 229 | constant: true, 230 | inputs: [], 231 | name: "symbol", 232 | outputs: [{ internalType: "string", name: "", type: "string" }], 233 | payable: false, 234 | stateMutability: "view", 235 | type: "function", 236 | }, 237 | { 238 | constant: true, 239 | inputs: [], 240 | name: "totalSupply", 241 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 242 | payable: false, 243 | stateMutability: "view", 244 | type: "function", 245 | }, 246 | { 247 | constant: false, 248 | inputs: [ 249 | { internalType: "address", name: "dst", type: "address" }, 250 | { internalType: "uint256", name: "wad", type: "uint256" }, 251 | ], 252 | name: "transfer", 253 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 254 | payable: false, 255 | stateMutability: "nonpayable", 256 | type: "function", 257 | }, 258 | { 259 | constant: false, 260 | inputs: [ 261 | { internalType: "address", name: "src", type: "address" }, 262 | { internalType: "address", name: "dst", type: "address" }, 263 | { internalType: "uint256", name: "wad", type: "uint256" }, 264 | ], 265 | name: "transferFrom", 266 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 267 | payable: false, 268 | stateMutability: "nonpayable", 269 | type: "function", 270 | }, 271 | { 272 | constant: true, 273 | inputs: [], 274 | name: "version", 275 | outputs: [{ internalType: "string", name: "", type: "string" }], 276 | payable: false, 277 | stateMutability: "view", 278 | type: "function", 279 | }, 280 | { 281 | constant: true, 282 | inputs: [{ internalType: "address", name: "", type: "address" }], 283 | name: "wards", 284 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 285 | payable: false, 286 | stateMutability: "view", 287 | type: "function", 288 | }, 289 | ]; 290 | 291 | export const NETWORK = chainId => { 292 | for (const n in NETWORKS) { 293 | if (NETWORKS[n].chainId === chainId) { 294 | return NETWORKS[n]; 295 | } 296 | } 297 | }; 298 | 299 | export const NETWORKS = { 300 | localhost: { 301 | name: "localhost", 302 | color: "#666666", 303 | chainId: 31337, 304 | blockExplorer: "", 305 | rpcUrl: "http://" + window.location.hostname + ":8545", 306 | }, 307 | mainnet: { 308 | name: "mainnet", 309 | color: "#ff8b9e", 310 | chainId: 1, 311 | rpcUrl: `https://mainnet.infura.io/v3/${INFURA_ID}`, 312 | blockExplorer: "https://etherscan.io/", 313 | }, 314 | kovan: { 315 | name: "kovan", 316 | color: "#7003DD", 317 | chainId: 42, 318 | rpcUrl: `https://kovan.infura.io/v3/${INFURA_ID}`, 319 | blockExplorer: "https://kovan.etherscan.io/", 320 | faucet: "https://gitter.im/kovan-testnet/faucet", // https://faucet.kovan.network/ 321 | }, 322 | rinkeby: { 323 | name: "rinkeby", 324 | color: "#e0d068", 325 | chainId: 4, 326 | rpcUrl: `https://rinkeby.infura.io/v3/${INFURA_ID}`, 327 | faucet: "https://faucet.rinkeby.io/", 328 | blockExplorer: "https://rinkeby.etherscan.io/", 329 | }, 330 | ropsten: { 331 | name: "ropsten", 332 | color: "#F60D09", 333 | chainId: 3, 334 | faucet: "https://faucet.ropsten.be/", 335 | blockExplorer: "https://ropsten.etherscan.io/", 336 | rpcUrl: `https://ropsten.infura.io/v3/${INFURA_ID}`, 337 | }, 338 | goerli: { 339 | name: "goerli", 340 | color: "#0975F6", 341 | chainId: 5, 342 | faucet: "https://goerli-faucet.slock.it/", 343 | blockExplorer: "https://goerli.etherscan.io/", 344 | rpcUrl: `https://goerli.infura.io/v3/${INFURA_ID}`, 345 | }, 346 | xdai: { 347 | name: "xdai", 348 | color: "#48a9a6", 349 | chainId: 100, 350 | price: 1, 351 | gasPrice: 1000000000, 352 | rpcUrl: "https://dai.poa.network", 353 | faucet: "https://xdai-faucet.top/", 354 | blockExplorer: "https://blockscout.com/poa/xdai/", 355 | }, 356 | matic: { 357 | name: "matic", 358 | color: "#2bbdf7", 359 | chainId: 137, 360 | price: 1, 361 | gasPrice: 1000000000, 362 | rpcUrl: "https://rpc-mainnet.maticvigil.com", 363 | faucet: "https://faucet.matic.network/", 364 | blockExplorer: "https://explorer-mainnet.maticvigil.com//", 365 | }, 366 | mumbai: { 367 | name: "mumbai", 368 | color: "#92D9FA", 369 | chainId: 80001, 370 | price: 1, 371 | gasPrice: 1000000000, 372 | rpcUrl: "https://rpc-mumbai.maticvigil.com", 373 | faucet: "https://faucet.matic.network/", 374 | blockExplorer: "https://mumbai-explorer.matic.today/", 375 | }, 376 | }; 377 | -------------------------------------------------------------------------------- /packages/react-app/src/contracts/contracts.js: -------------------------------------------------------------------------------- 1 | module.exports = ["Arbitrage","Migrations","UniswapV2Library"]; -------------------------------------------------------------------------------- /packages/react-app/src/ethereumLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wojtekwtf/polygon-arbitrage-bot/9f13cc1e9f08edaa9be31a2fded2e3535d89f370/packages/react-app/src/ethereumLogo.png -------------------------------------------------------------------------------- /packages/react-app/src/helpers/Transactor.js: -------------------------------------------------------------------------------- 1 | import { hexlify } from "@ethersproject/bytes"; 2 | import { parseUnits } from "@ethersproject/units"; 3 | import { notification } from "antd"; 4 | import Notify from "bnc-notify"; 5 | import { BLOCKNATIVE_DAPPID } from "../constants"; 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(provider, gasPrice, etherscan) { 15 | if (typeof provider !== "undefined") { 16 | // eslint-disable-next-line consistent-return 17 | return async (tx, callback) => { 18 | const signer = provider.getSigner(); 19 | const network = await provider.getNetwork(); 20 | console.log("network", network); 21 | const options = { 22 | dappId: BLOCKNATIVE_DAPPID, // GET YOUR OWN KEY AT https://account.blocknative.com 23 | system: "ethereum", 24 | networkId: network.chainId, 25 | // darkMode: Boolean, // (default: false) 26 | transactionHandler: txInformation => { 27 | if(DEBUG) console.log("HANDLE TX", txInformation); 28 | const possibleFunction = callbacks[txInformation.transaction.hash]; 29 | if (typeof possibleFunction === "function") { 30 | possibleFunction(txInformation.transaction); 31 | } 32 | }, 33 | }; 34 | const notify = Notify(options); 35 | 36 | let etherscanNetwork = ""; 37 | if (network.name && network.chainId > 1) { 38 | etherscanNetwork = network.name + "."; 39 | } 40 | 41 | let etherscanTxUrl = "https://" + etherscanNetwork + "etherscan.io/tx/"; 42 | if (network.chainId === 100) { 43 | etherscanTxUrl = "https://blockscout.com/poa/xdai/tx/"; 44 | } 45 | 46 | try { 47 | let result; 48 | if (tx instanceof Promise) { 49 | if(DEBUG) console.log("AWAITING TX", tx); 50 | result = await tx; 51 | } else { 52 | if (!tx.gasPrice) { 53 | tx.gasPrice = gasPrice || parseUnits("4.1", "gwei"); 54 | } 55 | if (!tx.gasLimit) { 56 | tx.gasLimit = hexlify(120000); 57 | } 58 | if(DEBUG) console.log("RUNNING TX", tx); 59 | result = await signer.sendTransaction(tx); 60 | } 61 | if(DEBUG) console.log("RESULT:", result); 62 | // console.log("Notify", notify); 63 | 64 | if (callback) { 65 | callbacks[result.hash] = callback; 66 | } 67 | 68 | // if it is a valid Notify.js network, use that, if not, just send a default notification 69 | if ([1, 3, 4, 5, 42, 100].indexOf(network.chainId) >= 0) { 70 | const { emitter } = notify.hash(result.hash); 71 | emitter.on("all", transaction => { 72 | return { 73 | onclick: () => window.open((etherscan || etherscanTxUrl) + transaction.hash), 74 | }; 75 | }); 76 | } else { 77 | notification.info({ 78 | message: "Local Transaction Sent", 79 | description: result.hash, 80 | placement: "bottomRight", 81 | }); 82 | // on most networks BlockNative will update a transaction handler, 83 | // but locally we will set an interval to listen... 84 | if (callback) { 85 | const txResult = await tx; 86 | const listeningInterval = setInterval(async () => { 87 | console.log("CHECK IN ON THE TX", txResult, provider); 88 | const currentTransactionReceipt = await provider.getTransactionReceipt(txResult.hash); 89 | if (currentTransactionReceipt && currentTransactionReceipt.confirmations) { 90 | callback({ ...txResult, ...currentTransactionReceipt }); 91 | clearInterval(listeningInterval); 92 | } 93 | }, 500); 94 | } 95 | } 96 | 97 | return result; 98 | } catch (e) { 99 | console.log(e); 100 | console.log("Transaction Error:", e.message); 101 | notification.error({ 102 | message: "Transaction Error", 103 | description: e.message, 104 | }); 105 | if (callback && typeof callback === "function") { 106 | callback(e); 107 | } 108 | } 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as Transactor } from "./Transactor"; 2 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Balance.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | import useOnBlock from "./OnBlock"; 3 | import usePoller from "./Poller"; 4 | 5 | /* 6 | ~ What it does? ~ 7 | 8 | Gets your balance in ETH from given address and provider 9 | 10 | ~ How can I use? ~ 11 | 12 | const yourLocalBalance = useBalance(localProvider, address); 13 | 14 | ~ Features ~ 15 | 16 | - Provide address and get balance corresponding to given address 17 | - Change provider to access balance on different chains (ex. mainnetProvider) 18 | - If no pollTime is passed, the balance will update on every new block 19 | */ 20 | 21 | const DEBUG = false; 22 | 23 | export default function useBalance(provider, address, pollTime = 0) { 24 | const [balance, setBalance] = useState(); 25 | 26 | const pollBalance = useCallback( 27 | async (provider, address) => { 28 | if (provider && address) { 29 | const newBalance = await provider.getBalance(address); 30 | if (newBalance !== balance) { 31 | setBalance(newBalance); 32 | } 33 | } 34 | }, 35 | [provider, address], 36 | ); 37 | 38 | // Only pass a provider to watch on a block if there is no pollTime 39 | useOnBlock(pollTime === 0 && provider, () => { 40 | if (provider && address && pollTime === 0) { 41 | pollBalance(provider, address); 42 | } 43 | }); 44 | 45 | // Use a poller if a pollTime is provided 46 | usePoller( 47 | async () => { 48 | if (provider && address && pollTime > 0) { 49 | if (DEBUG) console.log("polling!", address); 50 | pollBalance(); 51 | } 52 | }, 53 | pollTime, 54 | provider && address, 55 | ); 56 | 57 | return balance; 58 | } 59 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractExistsAtAddress.js: -------------------------------------------------------------------------------- 1 | import { isAddress } from "@ethersproject/address"; 2 | import { useEffect, useState } from "react"; 3 | 4 | /* 5 | ~ What it does? ~ 6 | 7 | Checks whether a contract exists on the blockchain, returns true if it exists, otherwise false 8 | 9 | ~ How can I use? ~ 10 | 11 | const contractIsDeployed = useContractExistsAtAddress(localProvider, contractAddress); 12 | 13 | ~ Features ~ 14 | 15 | - Provide contractAddress to check if the contract is deployed 16 | - Change provider to check contract address on different chains (ex. mainnetProvider) 17 | */ 18 | 19 | const useContractExistsAtAddress = (provider, contractAddress) => { 20 | const [contractIsDeployed, setContractIsDeployed] = useState(false); 21 | 22 | // We can look at the blockchain and see what's stored at `contractAddress` 23 | // If we find code then we know that a contract exists there. 24 | // If we find nothing (0x0) then there is no contract deployed to that address 25 | useEffect(() => { 26 | // eslint-disable-next-line consistent-return 27 | const checkDeployment = async () => { 28 | if (!isAddress(contractAddress)) return false; 29 | const bytecode = await provider.getCode(contractAddress); 30 | setContractIsDeployed(bytecode !== "0x0"); 31 | }; 32 | if (provider) checkDeployment(); 33 | }, [provider, contractAddress]); 34 | 35 | return contractIsDeployed; 36 | }; 37 | 38 | export default useContractExistsAtAddress; 39 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Loads your local contracts and gives options to read values from contracts 10 | or write transactions into them 11 | 12 | ~ How can I use? ~ 13 | 14 | const readContracts = useContractLoader(localProvider) // or 15 | const writeContracts = useContractLoader(userProvider) 16 | 17 | ~ Features ~ 18 | 19 | - localProvider enables reading values from contracts 20 | - userProvider enables writing transactions into contracts 21 | - Example of keeping track of "purpose" variable by loading contracts into readContracts 22 | and using ContractReader.js hook: 23 | const purpose = useContractReader(readContracts,"YourContract", "purpose") 24 | - Example of using setPurpose function from our contract and writing transactions by Transactor.js helper: 25 | tx( writeContracts.YourContract.setPurpose(newPurpose) ) 26 | */ 27 | 28 | const loadContract = (contractName, signer) => { 29 | const newContract = new Contract( 30 | require(`../contracts/${contractName}.address.js`), 31 | require(`../contracts/${contractName}.abi.js`), 32 | signer, 33 | ); 34 | try { 35 | newContract.bytecode = require(`../contracts/${contractName}.bytecode.js`); 36 | } catch (e) { 37 | console.log(e); 38 | } 39 | return newContract; 40 | }; 41 | 42 | export default function useContractLoader(providerOrSigner) { 43 | const [contracts, setContracts] = useState(); 44 | useEffect(() => { 45 | async function loadContracts() { 46 | if (typeof providerOrSigner !== "undefined") { 47 | try { 48 | // we need to check to see if this providerOrSigner has a signer or not 49 | let signer; 50 | let accounts; 51 | if (providerOrSigner && typeof providerOrSigner.listAccounts === "function") { 52 | accounts = await providerOrSigner.listAccounts(); 53 | } 54 | 55 | if (accounts && accounts.length > 0) { 56 | signer = providerOrSigner.getSigner(); 57 | } else { 58 | signer = providerOrSigner; 59 | } 60 | 61 | const contractList = require("../contracts/contracts.js"); 62 | 63 | const newContracts = contractList.reduce((accumulator, contractName) => { 64 | accumulator[contractName] = loadContract(contractName, signer); 65 | return accumulator; 66 | }, {}); 67 | setContracts(newContracts); 68 | } catch (e) { 69 | console.log("ERROR LOADING CONTRACTS!!", e); 70 | } 71 | } 72 | } 73 | loadContracts(); 74 | }, [providerOrSigner]); 75 | return contracts; 76 | } 77 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractReader.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import useOnBlock from "./OnBlock"; 3 | import usePoller from "./Poller"; 4 | 5 | const DEBUG = false; 6 | 7 | /* 8 | ~ What it does? ~ 9 | 10 | Enables you to read values from contracts and keep track of them in the local React states 11 | 12 | ~ How can I use? ~ 13 | 14 | const purpose = useContractReader(readContracts,"YourContract", "purpose") 15 | 16 | ~ Features ~ 17 | 18 | - Provide readContracts by loading contracts (see more on ContractLoader.js) 19 | - Specify the name of the contract, in this case it is "YourContract" 20 | - Specify the name of the variable in the contract, in this case we keep track of "purpose" variable 21 | - Pass an args array if the function requires 22 | - Pass pollTime - if no pollTime is specified, the function will update on every new block 23 | */ 24 | 25 | export default function useContractReader(contracts, contractName, functionName, args, pollTime, formatter, onChange) { 26 | let adjustPollTime = 0; 27 | if (pollTime) { 28 | adjustPollTime = pollTime; 29 | } else if (!pollTime && typeof args === "number") { 30 | // it's okay to pass poll time as last argument without args for the call 31 | adjustPollTime = args; 32 | } 33 | 34 | const [value, setValue] = useState(); 35 | useEffect(() => { 36 | if (typeof onChange === "function") { 37 | setTimeout(onChange.bind(this, value), 1); 38 | } 39 | }, [value, onChange]); 40 | 41 | const updateValue = async () => { 42 | try { 43 | let newValue; 44 | if (DEBUG) console.log("CALLING ", contractName, functionName, "with args", args); 45 | if (args && args.length > 0) { 46 | newValue = await contracts[contractName][functionName](...args); 47 | if (DEBUG) 48 | console.log("contractName", contractName, "functionName", functionName, "args", args, "RESULT:", newValue); 49 | } else { 50 | newValue = await contracts[contractName][functionName](); 51 | } 52 | if (formatter && typeof formatter === "function") { 53 | newValue = formatter(newValue); 54 | } 55 | // console.log("GOT VALUE",newValue) 56 | if (newValue !== value) { 57 | setValue(newValue); 58 | } 59 | } catch (e) { 60 | console.log(e); 61 | } 62 | }; 63 | 64 | // Only pass a provider to watch on a block if we have a contract and no PollTime 65 | useOnBlock(contracts && contracts[contractName] && adjustPollTime === 0 && contracts[contractName].provider, () => { 66 | if (contracts && contracts[contractName] && adjustPollTime === 0) { 67 | updateValue(); 68 | } 69 | }); 70 | 71 | // Use a poller if a pollTime is provided 72 | usePoller( 73 | async () => { 74 | if (contracts && contracts[contractName] && adjustPollTime > 0) { 75 | if (DEBUG) console.log("polling!", contractName, functionName); 76 | updateValue(); 77 | } 78 | }, 79 | adjustPollTime, 80 | contracts && contracts[contractName], 81 | ); 82 | 83 | return value; 84 | } 85 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/CustomContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /* 7 | when you want to load a local contract's abi but supply a custom address 8 | */ 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Enables you to load a local contract with custom address 14 | 15 | ~ How can I use? ~ 16 | 17 | const customContract = useCustomContractLoader(localProvider, "YourContract", customAddress) 18 | 19 | ~ Features ~ 20 | 21 | - Specify the localProvider 22 | - Specify the name of the contract, in this case it is "YourContract" 23 | - Specify the customAddress of your contract 24 | */ 25 | 26 | export default function useCustomContractLoader(provider, contractName, address) { 27 | const [contract, setContract] = useState(); 28 | useEffect(() => { 29 | async function loadContract() { 30 | if (typeof provider !== "undefined" && contractName && address) { 31 | try { 32 | // we need to check to see if this provider has a signer or not 33 | let signer; 34 | const accounts = await provider.listAccounts(); 35 | if (accounts && accounts.length > 0) { 36 | signer = provider.getSigner(); 37 | } else { 38 | signer = provider; 39 | } 40 | 41 | const customContract = new Contract(address, require(`../contracts/${contractName}.abi.js`), signer); 42 | try { 43 | customContract.bytecode = require(`../contracts/${contractName}.bytecode.js`); 44 | } catch (e) { 45 | console.log(e); 46 | } 47 | 48 | setContract(customContract); 49 | } catch (e) { 50 | console.log("ERROR LOADING CONTRACTS!!", e); 51 | } 52 | } 53 | } 54 | loadContract(); 55 | }, [provider, contractName, address]); 56 | return contract; 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Debounce.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useDebounce(value, delay) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/EventListener.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Enables you to keep track of events 7 | 8 | ~ How can I use? ~ 9 | 10 | const setPurposeEvents = useEventListener(readContracts, "YourContract", "SetPurpose", localProvider, 1); 11 | 12 | ~ Features ~ 13 | 14 | - Provide readContracts by loading contracts (see more on ContractLoader.js) 15 | - Specify the name of the contract, in this case it is "YourContract" 16 | - Specify the name of the event in the contract, in this case we keep track of "SetPurpose" event 17 | - Specify the provider 18 | */ 19 | 20 | export default function useEventListener(contracts, contractName, eventName, provider, startBlock, args) { 21 | const [updates, setUpdates] = useState([]); 22 | 23 | useEffect(() => { 24 | if (typeof provider !== "undefined" && typeof startBlock !== "undefined") { 25 | // if you want to read _all_ events from your contracts, set this to the block number it is deployed 26 | provider.resetEventsBlock(startBlock); 27 | } 28 | if (contracts && contractName && contracts[contractName]) { 29 | try { 30 | contracts[contractName].on(eventName, (...args) => { 31 | const blockNumber = args[args.length - 1].blockNumber; 32 | setUpdates(messages => [{ blockNumber, ...args.pop().args }, ...messages]); 33 | }); 34 | return () => { 35 | contracts[contractName].removeListener(eventName); 36 | }; 37 | } catch (e) { 38 | console.log(e); 39 | } 40 | } 41 | }, [provider, startBlock, contracts, contractName, eventName]); 42 | 43 | return updates; 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ExchangePrice.js: -------------------------------------------------------------------------------- 1 | import { Fetcher, Route, Token, WETH } from "@uniswap/sdk"; 2 | import { usePoller } from "eth-hooks"; 3 | import { useState } from "react"; 4 | 5 | export default function useExchangePrice(targetNetwork, mainnetProvider, pollTime) { 6 | const [price, setPrice] = useState(0); 7 | 8 | const pollPrice = () => { 9 | async function getPrice() { 10 | if (targetNetwork.price) { 11 | setPrice(targetNetwork.price); 12 | } else { 13 | const DAI = new Token( 14 | mainnetProvider.network ? mainnetProvider.network.chainId : 1, 15 | "0x6B175474E89094C44Da98b954EedeAC495271d0F", 16 | 18, 17 | ); 18 | const pair = await Fetcher.fetchPairData(DAI, WETH[DAI.chainId], mainnetProvider); 19 | const route = new Route([pair], WETH[DAI.chainId]); 20 | setPrice(parseFloat(route.midPrice.toSignificant(6))); 21 | } 22 | } 23 | getPrice(); 24 | }; 25 | usePoller(pollPrice, pollTime || 9777); 26 | 27 | return price; 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ExternalContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /* 7 | when you want to load an existing contract using just the provider, address, and ABI 8 | */ 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Enables you to load an existing mainnet DAI contract using the provider, address and abi 14 | 15 | ~ How can I use? ~ 16 | 17 | const mainnetDAIContract = useExternalContractLoader(mainnetProvider, DAI_ADDRESS, DAI_ABI) 18 | 19 | ~ Features ~ 20 | 21 | - Specify mainnetProvider 22 | - Specify DAI_ADDRESS and DAI_ABI, you can load/write them using constants.js 23 | */ 24 | export default function useExternalContractLoader(provider, address, ABI, optionalBytecode) { 25 | const [contract, setContract] = useState(); 26 | useEffect(() => { 27 | async function loadContract() { 28 | if (typeof provider !== "undefined" && address && ABI) { 29 | try { 30 | // we need to check to see if this provider has a signer or not 31 | let signer; 32 | const accounts = await provider.listAccounts(); 33 | if (accounts && accounts.length > 0) { 34 | signer = provider.getSigner(); 35 | } else { 36 | signer = provider; 37 | } 38 | 39 | const customContract = new Contract(address, ABI, signer); 40 | if (optionalBytecode) customContract.bytecode = optionalBytecode; 41 | 42 | setContract(customContract); 43 | } catch (e) { 44 | console.log("ERROR LOADING EXTERNAL CONTRACT AT " + address + " (check provider, address, and ABI)!!", e); 45 | } 46 | } 47 | } 48 | loadContract(); 49 | }, [provider, address, ABI, optionalBytecode]); 50 | return contract; 51 | } 52 | -------------------------------------------------------------------------------- /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.gasPrice) { 9 | setGasPrice(targetNetwork.gasPrice); 10 | } else { 11 | axios 12 | .get("https://ethgasstation.info/json/ethgasAPI.json") 13 | .then(response => { 14 | const newGasPrice = response.data[speed || "fast"] * 100000000; 15 | if (newGasPrice !== gasPrice) { 16 | setGasPrice(newGasPrice); 17 | } 18 | }) 19 | .catch(error => console.log(error)); 20 | } 21 | }; 22 | 23 | usePoller(loadGasPrice, 39999); 24 | return gasPrice; 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | // Hook from useHooks! (https://usehooks.com/useLocalStorage/) 3 | export default function useLocalStorage(key, initialValue, ttl) { 4 | // State to store our value 5 | // Pass initial state function to useState so logic is only executed once 6 | const [storedValue, setStoredValue] = useState(() => { 7 | try { 8 | // Get from local storage by key 9 | const item = window.localStorage.getItem(key); 10 | const parsedItem = item ? JSON.parse(item) : initialValue; 11 | 12 | if (typeof parsedItem === "object" && parsedItem !== null && "expiry" in parsedItem && "value" in parsedItem) { 13 | const now = new Date(); 14 | if (ttl && now.getTime() > parsedItem.expiry) { 15 | // If the item is expired, delete the item from storage 16 | // and return null 17 | window.localStorage.removeItem(key); 18 | return initialValue; 19 | } 20 | return parsedItem.value; 21 | } 22 | // Parse stored json or if none return initialValue 23 | return parsedItem; 24 | } catch (error) { 25 | // If error also return initialValue 26 | console.log(error); 27 | return initialValue; 28 | } 29 | }); 30 | 31 | // Return a wrapped version of useState's setter function that ... 32 | // ... persists the new value to localStorage. 33 | const setValue = value => { 34 | try { 35 | // Allow value to be a function so we have same API as useState 36 | const valueToStore = value instanceof Function ? value(storedValue) : value; 37 | // Save state 38 | setStoredValue(valueToStore); 39 | // Save to local storage 40 | if (ttl) { 41 | const now = new Date(); 42 | 43 | // `item` is an object which contains the original value 44 | // as well as the time when it's supposed to expire 45 | const item = { 46 | value: valueToStore, 47 | expiry: now.getTime() + ttl, 48 | }; 49 | window.localStorage.setItem(key, JSON.stringify(item)); 50 | } else { 51 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 52 | } 53 | } catch (error) { 54 | // A more advanced implementation would handle the error case 55 | console.log(error); 56 | } 57 | }; 58 | 59 | return [storedValue, setValue]; 60 | } 61 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LookupAddress.js: -------------------------------------------------------------------------------- 1 | import { getAddress, isAddress } from "@ethersproject/address"; 2 | import { useEffect, useState } from "react"; 3 | 4 | // resolved if(name){} to not save "" into cache 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Gets ENS name from given address and provider 10 | 11 | ~ How can I use? ~ 12 | 13 | const ensName = useLookupAddress(mainnetProvider, address); 14 | 15 | ~ Features ~ 16 | 17 | - Provide address and get ENS name corresponding to given address 18 | */ 19 | 20 | const lookupAddress = async (provider, address) => { 21 | if (isAddress(address)) { 22 | // console.log(`looking up ${address}`) 23 | try { 24 | // Accuracy of reverse resolution is not enforced. 25 | // We then manually ensure that the reported ens name resolves to address 26 | const reportedName = await provider.lookupAddress(address); 27 | 28 | const resolvedAddress = await provider.resolveName(reportedName); 29 | 30 | if (getAddress(address) === getAddress(resolvedAddress)) { 31 | return reportedName; 32 | } 33 | return getAddress(address); 34 | } catch (e) { 35 | return getAddress(address); 36 | } 37 | } 38 | return 0; 39 | }; 40 | 41 | const useLookupAddress = (provider, address) => { 42 | const [ensName, setEnsName] = useState(address); 43 | // const [ensCache, setEnsCache] = useLocalStorage('ensCache_'+address); Writing directly due to sync issues 44 | 45 | useEffect(() => { 46 | let cache = window.localStorage.getItem("ensCache_" + address); 47 | cache = cache && JSON.parse(cache); 48 | 49 | if (cache && cache.timestamp > Date.now()) { 50 | setEnsName(cache.name); 51 | } else if (provider) { 52 | lookupAddress(provider, address).then(name => { 53 | if (name) { 54 | setEnsName(name); 55 | window.localStorage.setItem( 56 | "ensCache_" + address, 57 | JSON.stringify({ 58 | timestamp: Date.now() + 360000, 59 | name, 60 | }), 61 | ); 62 | } 63 | }); 64 | } 65 | }, [provider, address, setEnsName]); 66 | 67 | return ensName; 68 | }; 69 | 70 | export default useLookupAddress; 71 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Nonce.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function useNonce(mainnetProvider, address) { 4 | const [nonce, setNonce] = useState(0); 5 | 6 | const Nonce = () => { 7 | async function getNonce() { 8 | setNonce(await mainnetProvider.getTransactionCount(address)); 9 | } 10 | if (address) getNonce(); 11 | }; 12 | Nonce(); 13 | return nonce; 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/OnBlock.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | // helper hook to call a function regularly in time intervals 4 | const DEBUG = false; 5 | 6 | export default function useOnBlock(provider, fn, args) { 7 | const savedCallback = useRef(); 8 | // Remember the latest fn. 9 | useEffect(() => { 10 | savedCallback.current = fn; 11 | }, [fn]); 12 | 13 | // Turn on the listener if we have a function & a provider 14 | useEffect(() => { 15 | if (fn && provider) { 16 | const listener = blockNumber => { 17 | if (DEBUG) console.log(blockNumber, fn, args, provider.listeners()); 18 | 19 | if (args && args.length > 0) { 20 | savedCallback.current(...args); 21 | } else { 22 | savedCallback.current(); 23 | } 24 | }; 25 | 26 | provider.on("block", listener); 27 | 28 | return () => { 29 | provider.off("block", listener); 30 | }; 31 | } 32 | }, [provider]); 33 | } 34 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Poller.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | // helper hook to call a function regularly in time intervals 4 | 5 | export default function usePoller(fn, delay, extraWatch) { 6 | const savedCallback = useRef(); 7 | // Remember the latest fn. 8 | useEffect(() => { 9 | savedCallback.current = fn; 10 | }, [fn]); 11 | // Set up the interval. 12 | // eslint-disable-next-line consistent-return 13 | useEffect(() => { 14 | function tick() { 15 | savedCallback.current(); 16 | } 17 | if (delay !== null) { 18 | const id = setInterval(tick, delay); 19 | return () => clearInterval(id); 20 | } 21 | }, [delay]); 22 | // run at start too 23 | useEffect(() => { 24 | fn(); 25 | }, [extraWatch]); 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ResolveName.js: -------------------------------------------------------------------------------- 1 | import { AddressZero } from "@ethersproject/constants"; 2 | import { useEffect, useState } from "react"; 3 | 4 | /* 5 | ~ What it does? ~ 6 | 7 | Gets address from given ENS name and provider 8 | 9 | ~ How can I use? ~ 10 | 11 | const addressFromENS = useResolveName(mainnetProvider, "austingriffith.eth"); 12 | 13 | ~ Features ~ 14 | 15 | - Specify mainnetProvider 16 | - Provide ENS name and get address corresponding to given ENS name 17 | */ 18 | 19 | const useResolveName = (provider, ensName) => { 20 | const [address, setAddress] = useState(AddressZero); 21 | 22 | useEffect(() => { 23 | if (provider) { 24 | provider.resolveName(ensName).then(resolvedAddress => setAddress(resolvedAddress)); 25 | } 26 | }, [provider, ensName]); 27 | 28 | return address; 29 | }; 30 | 31 | export default useResolveName; 32 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/TokenList.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Gets a tokenlist (see more at https://tokenlists.org/), returning the .tokens only 7 | 8 | ~ How can I use? ~ 9 | 10 | const tokenList = useTokenList(); <- default returns the Unsiwap tokens 11 | const tokenList = useTokenList("https://gateway.ipfs.io/ipns/tokens.uniswap.org"); 12 | 13 | ~ Features ~ 14 | 15 | - Optional - specify chainId to filter by chainId 16 | */ 17 | 18 | const useTokenList = (tokenListUri, chainId) => { 19 | const [tokenList, setTokenList] = useState([]); 20 | 21 | const _tokenListUri = tokenListUri || "https://gateway.ipfs.io/ipns/tokens.uniswap.org"; 22 | 23 | useEffect(() => { 24 | const getTokenList = async () => { 25 | try { 26 | const tokenList = await fetch(_tokenListUri); 27 | const tokenListJson = await tokenList.json(); 28 | let _tokenList; 29 | 30 | if (chainId) { 31 | _tokenList = tokenListJson.tokens.filter(function (t) { 32 | return t.chainId === chainId; 33 | }); 34 | } else { 35 | _tokenList = tokenListJson; 36 | } 37 | 38 | setTokenList(_tokenList.tokens); 39 | } catch (e) { 40 | console.log(e); 41 | } 42 | }; 43 | getTokenList(); 44 | }, [tokenListUri]); 45 | 46 | return tokenList; 47 | }; 48 | 49 | export default useTokenList; 50 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/UserProvider.js: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from "@ethersproject/providers"; 2 | import BurnerProvider from "burner-provider"; 3 | import { useMemo } from "react"; 4 | import { INFURA_ID } from "../constants"; 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Gets user provider 10 | 11 | ~ How can I use? ~ 12 | 13 | const userProvider = useUserProvider(injectedProvider, localProvider); 14 | 15 | ~ Features ~ 16 | 17 | - Specify the injected provider from Metamask 18 | - Specify the local provider 19 | - Usage examples: 20 | const address = useUserAddress(userProvider); 21 | const tx = Transactor(userProvider, gasPrice) 22 | */ 23 | 24 | const useUserProvider = (injectedProvider, localProvider) => 25 | useMemo(() => { 26 | if (injectedProvider) { 27 | console.log("🦊 Using injected provider"); 28 | return injectedProvider; 29 | } 30 | if (!localProvider) return undefined; 31 | 32 | const burnerConfig = {}; 33 | 34 | if (window.location.pathname) { 35 | if (window.location.pathname.indexOf("/pk") >= 0) { 36 | const incomingPK = window.location.hash.replace("#", ""); 37 | let rawPK; 38 | if (incomingPK.length === 64 || incomingPK.length === 66) { 39 | console.log("🔑 Incoming Private Key..."); 40 | rawPK = incomingPK; 41 | burnerConfig.privateKey = rawPK; 42 | window.history.pushState({}, "", "/"); 43 | const currentPrivateKey = window.localStorage.getItem("metaPrivateKey"); 44 | if (currentPrivateKey && currentPrivateKey !== rawPK) { 45 | window.localStorage.setItem("metaPrivateKey_backup" + Date.now(), currentPrivateKey); 46 | } 47 | window.localStorage.setItem("metaPrivateKey", rawPK); 48 | } 49 | } 50 | } 51 | 52 | console.log("🔥 Using burner provider", burnerConfig); 53 | if (localProvider.connection && localProvider.connection.url) { 54 | burnerConfig.rpcUrl = localProvider.connection.url; 55 | return new Web3Provider(new BurnerProvider(burnerConfig)); 56 | } 57 | // eslint-disable-next-line no-underscore-dangle 58 | const networkName = localProvider._network && localProvider._network.name; 59 | burnerConfig.rpcUrl = `https://${networkName || "mainnet"}.infura.io/v3/${INFURA_ID}`; 60 | return new Web3Provider(new BurnerProvider(burnerConfig)); 61 | }, [injectedProvider, localProvider]); 62 | 63 | export default useUserProvider; 64 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useBalance } from "./Balance"; 2 | export { default as useContractExistsAtAddress } from "./ContractExistsAtAddress"; 3 | export { default as useContractLoader } from "./ContractLoader"; 4 | export { default as useContractReader } from "./ContractReader"; 5 | export { default as useCustomContractLoader } from "./CustomContractLoader"; 6 | export { default as useDebounce } from "./Debounce"; 7 | export { default as useEventListener } from "./EventListener"; 8 | export { default as useExchangePrice } from "./ExchangePrice"; 9 | export { default as useExternalContractLoader } from "./ExternalContractLoader"; 10 | export { default as useGasPrice } from "./GasPrice"; 11 | export { default as useLocalStorage } from "./LocalStorage"; 12 | export { default as useLookupAddress } from "./LookupAddress"; 13 | export { default as useNonce } from "./Nonce"; 14 | export { default as useOnBlock } from "./OnBlock"; 15 | export { default as usePoller } from "./Poller"; 16 | export { default as useResolveName } from "./ResolveName"; 17 | export { default as useTokenList } from "./TokenList"; 18 | export { default as useUserProvider } from "./UserProvider"; 19 | -------------------------------------------------------------------------------- /packages/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | code { 6 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 7 | monospace; 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-app/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"; 2 | import React from "react"; 3 | import { ThemeSwitcherProvider } from "react-css-theme-switcher"; 4 | import ReactDOM from "react-dom"; 5 | import App from "./App"; 6 | import "./index.css"; 7 | 8 | const themes = { 9 | dark: `${process.env.PUBLIC_URL}/dark-theme.css`, 10 | light: `${process.env.PUBLIC_URL}/light-theme.css`, 11 | }; 12 | 13 | const prevTheme = window.localStorage.getItem("theme"); 14 | 15 | const subgraphUri = "http://localhost:8000/subgraphs/name/scaffold-eth/your-contract"; 16 | 17 | const client = new ApolloClient({ 18 | uri: subgraphUri, 19 | cache: new InMemoryCache(), 20 | }); 21 | 22 | ReactDOM.render( 23 | 24 | 25 | 26 | 27 | , 28 | document.getElementById("root"), 29 | ); 30 | -------------------------------------------------------------------------------- /packages/react-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom/extend-expect"; 6 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/dark-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/dark.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | @primary-color: #2caad9; 7 | @border-radius-base: 4px; 8 | 9 | @component-background: #212121; 10 | @body-background: #212121; 11 | @popover-background: #212121; 12 | @border-color-base: #6f6c6c; 13 | @border-color-split: #424242; 14 | @table-header-sort-active-bg: #424242; 15 | @card-skeleton-bg: #424242; 16 | @skeleton-color: #424242; 17 | @table-header-sort-active-bg: #424242; 18 | 19 | 20 | .highlight { 21 | background-color: #3f3f3f; 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/light-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/default.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | // @primary-color: #00adb5; 7 | @border-radius-base: 4px; 8 | 9 | .highlight { 10 | background-color: #f9f9f9; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-app/src/views/ExampleUI.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/accessible-emoji */ 2 | 3 | import { SyncOutlined } from "@ant-design/icons"; 4 | import { formatEther, parseEther } from "@ethersproject/units"; 5 | import { Button, Card, DatePicker, Divider, Input, List, Progress, Slider, Spin, Switch } from "antd"; 6 | import React, { useState } from "react"; 7 | import { Address, Balance } from "../components"; 8 | 9 | export default function ExampleUI({ 10 | purpose, 11 | setPurposeEvents, 12 | address, 13 | mainnetProvider, 14 | localProvider, 15 | yourLocalBalance, 16 | price, 17 | tx, 18 | readContracts, 19 | writeContracts, 20 | }) { 21 | const [newPurpose, setNewPurpose] = useState("loading..."); 22 | 23 | return ( 24 |
25 | {/* 26 | ⚙️ Here is an example UI that displays and sets the purpose in your smart contract: 27 | */} 28 |
29 |

Example UI:

30 |

purpose: {purpose}

31 | 32 |
33 | { 35 | setNewPurpose(e.target.value); 36 | }} 37 | /> 38 | 64 |
65 | 66 | Your Address: 67 |
68 | 69 | ENS Address Example: 70 |
75 | 76 | {/* use formatEther to display a BigNumber: */} 77 |

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

78 |
OR
79 | 80 | 81 |
🐳 Example Whale Balance:
82 | 83 | 84 | {/* use formatEther to display a BigNumber: */} 85 |

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

86 | 87 | Your Contract Address: 88 |
93 | 94 |
95 | 103 |
104 |
105 | 120 |
121 |
122 | 135 |
136 |
137 | 152 |
153 |
154 | 155 | {/* 156 | 📑 Maybe display a list of events? 157 | (uncomment the event and emit line in YourContract.sol! ) 158 | */} 159 |
160 |

Events:

161 | { 165 | return ( 166 | 167 |
=> 168 | {item[1]} 169 | 170 | ); 171 | }} 172 | /> 173 |
174 | 175 |
176 | 177 | Check out all the{" "} 178 | 183 | 📦 components 184 | 185 | 186 | 187 | 188 |
189 | There are tons of generic components included from{" "} 190 | 191 | 🐜 ant.design 192 | {" "} 193 | too! 194 |
195 | 196 |
197 | 198 |
199 | 200 |
201 | Icons 202 |
203 | 204 |
205 | Date Pickers? 206 |
207 | {}} /> 208 |
209 |
210 | 211 |
212 | {}} /> 213 |
214 | 215 |
216 | {}} /> 217 |
218 | 219 |
220 | 221 |
222 | 223 |
224 | 225 |
226 |
227 |
228 |
229 | ); 230 | } 231 | -------------------------------------------------------------------------------- /packages/react-app/src/views/Hints.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/accessible-emoji */ 2 | 3 | import { formatEther } from "@ethersproject/units"; 4 | import { Select } from "antd"; 5 | import React, { useState } from "react"; 6 | import { Address, AddressInput } from "../components"; 7 | import { useTokenList } from "../hooks"; 8 | 9 | const { Option } = Select; 10 | 11 | export default function Hints({ yourLocalBalance, mainnetProvider, price, address }) { 12 | // Get a list of tokens from a tokenlist -> see tokenlists.org! 13 | const [selectedToken, setSelectedToken] = useState("Pick a token!"); 14 | const listOfTokens = useTokenList( 15 | "https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json", 16 | ); 17 | 18 | return ( 19 |
20 |
21 | 👷 22 | Edit your contract in 23 | 27 | packages/hardhat/contracts 28 | 29 |
30 | 31 |
32 | 🛰 33 | compile/deploy with 34 | 38 | yarn run deploy 39 | 40 |
41 | 42 |
43 | 🚀 44 | Your contract artifacts are automatically injected into your frontend at 45 | 49 | packages/react-app/src/contracts/ 50 | 51 |
52 | 53 |
54 | 🎛 55 | Edit your frontend in 56 | 60 | packages/reactapp/src/App.js 61 | 62 |
63 | 64 |
65 | 🔭 66 | explore the 67 | 78 | 🖇 hooks 79 | 80 | and 81 | 85 | 📦 components 86 | 87 |
88 | 89 |
90 | for example, the 91 | 95 | useBalance() 96 | {" "} 97 | hook keeps track of your balance: {formatEther(yourLocalBalance || 0)} 98 |
99 | 100 |
101 |
102 | useTokenList() can get you an array of tokens from{" "} 103 | 104 | tokenlists.org! 105 | 106 |
107 | 123 |
124 | 125 |
126 | as you build your app you'll need web3 specific components like an 127 | 131 | {""} 132 | 133 | component: 134 |
135 | 136 |
137 |
(try putting in your address, an ens address, or scanning a QR code)
138 |
139 | 140 |
141 | this balance could be multiplied by 142 | 146 | price 147 | {" "} 148 | that is loaded with the 149 | 153 | usePrice 154 | {" "} 155 | hook with the current value: ${price} 156 |
157 | 158 |
159 | 💧 160 | use the faucet to send funds to 161 | 165 |
{address} 166 | 167 |
168 | 169 |
170 | 📡 171 | deploy to a testnet or mainnet by editing 172 | 176 | packages/hardhat/hardhat.config.js 177 | 178 | and running 179 | 183 | yarn run deploy 184 | 185 |
186 | 187 |
188 | 🔑 189 | 193 | yarn run generate 194 | 195 | will create a deployer account in 196 | 200 | packages/hardhat 201 | 202 |
203 | (use{" "} 204 | 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... (inpect -> console) 🚀 268 |
269 |
270 | ); 271 | } 272 | -------------------------------------------------------------------------------- /packages/react-app/src/views/Subgraph.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/accessible-emoji */ 2 | import { gql, useQuery } from "@apollo/client"; 3 | import { Button, Input, Table, Typography } from "antd"; 4 | import "antd/dist/antd.css"; 5 | import GraphiQL from "graphiql"; 6 | import "graphiql/graphiql.min.css"; 7 | import fetch from "isomorphic-fetch"; 8 | import React, { useState } from "react"; 9 | import { Address } from "../components"; 10 | 11 | const highlight = { 12 | marginLeft: 4, 13 | marginRight: 8, 14 | /* backgroundColor: "#f9f9f9", */ padding: 4, 15 | borderRadius: 4, 16 | fontWeight: "bolder", 17 | }; 18 | 19 | function Subgraph(props) { 20 | function graphQLFetcher(graphQLParams) { 21 | return fetch(props.subgraphUri, { 22 | method: "post", 23 | headers: { "Content-Type": "application/json" }, 24 | body: JSON.stringify(graphQLParams), 25 | }).then(response => response.json()); 26 | } 27 | 28 | const EXAMPLE_GRAPHQL = ` 29 | { 30 | purposes(first: 25, orderBy: createdAt, orderDirection: desc) { 31 | id 32 | purpose 33 | createdAt 34 | sender { 35 | id 36 | } 37 | } 38 | senders { 39 | id 40 | address 41 | purposeCount 42 | } 43 | } 44 | `; 45 | const EXAMPLE_GQL = gql(EXAMPLE_GRAPHQL); 46 | const { loading, data } = useQuery(EXAMPLE_GQL, { pollInterval: 2500 }); 47 | 48 | const purposeColumns = [ 49 | { 50 | title: "Purpose", 51 | dataIndex: "purpose", 52 | key: "purpose", 53 | }, 54 | { 55 | title: "Sender", 56 | key: "id", 57 | render: record =>
, 58 | }, 59 | { 60 | title: "createdAt", 61 | key: "createdAt", 62 | dataIndex: "createdAt", 63 | render: d => new Date(d * 1000).toISOString(), 64 | }, 65 | ]; 66 | 67 | const [newPurpose, setNewPurpose] = useState("loading..."); 68 | 69 | const deployWarning = ( 70 |
Warning: 🤔 Have you deployed your subgraph yet?
71 | ); 72 | 73 | return ( 74 | <> 75 |
76 | You will find that parsing/tracking events with the{" "} 77 | 78 | useEventListener 79 | {" "} 80 | hook becomes a chore for every new project. 81 |
82 |
83 | Instead, you can use{" "} 84 | 85 | The Graph 86 | {" "} 87 | with 🏗 scaffold-eth ( 88 | 89 | learn more 90 | 91 | ): 92 |
93 | 94 |
95 | 🚮 96 | Clean up previous data: 97 | 98 | rm -rf docker/graph-node/data/ 99 | 100 |
101 | 102 |
103 | 📡 104 | Spin up a local graph node by running 105 | 106 | yarn graph-run-node 107 | 108 | 109 | {" "} 110 | (requires{" "} 111 | 112 | {" "} 113 | Docker 114 | 115 | ){" "} 116 | 117 |
118 | 119 |
120 | 📝 121 | Create your local subgraph by running 122 | 123 | yarn graph-create-local 124 | 125 | (only required once!) 126 |
127 | 128 |
129 | 🚢 130 | Deploy your local subgraph by running 131 | 132 | yarn graph-ship-local 133 | 134 |
135 | 136 |
137 | 🖍️ 138 | Edit your local subgraph in 139 | 140 | packages/subgraph/src 141 | 142 | (learn more about subgraph definition{" "} 143 | 144 | here 145 | 146 | ) 147 |
148 | 149 |
150 | 🤩 151 | Deploy your contracts and your subgraph in one go by running 152 | 153 | yarn deploy-and-graph 154 | 155 |
156 | 157 |
158 |
159 | { 161 | setNewPurpose(e.target.value); 162 | }} 163 | /> 164 | 173 |
174 | 175 | {data ? ( 176 | 177 | ) : ( 178 | {loading ? "Loading..." : deployWarning} 179 | )} 180 | 181 |
182 | 183 |
184 | 185 | 186 |
...
187 | 188 | ); 189 | } 190 | 191 | export default Subgraph; 192 | -------------------------------------------------------------------------------- /packages/react-app/src/views/index.js: -------------------------------------------------------------------------------- 1 | export { default as ExampleUI } from "./ExampleUI"; 2 | export { default as Hints } from "./Hints"; 3 | export { default as Subgraph } from "./Subgraph"; 4 | -------------------------------------------------------------------------------- /packages/subgraph/abis/YourContract.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": false, 12 | "internalType": "address", 13 | "name": "sender", 14 | "type": "address" 15 | }, 16 | { 17 | "indexed": false, 18 | "internalType": "int32", 19 | "name": "result", 20 | "type": "int32" 21 | } 22 | ], 23 | "name": "AddNumber", 24 | "type": "event" 25 | }, 26 | { 27 | "inputs": [ 28 | { 29 | "internalType": "int32", 30 | "name": "num1", 31 | "type": "int32" 32 | }, 33 | { 34 | "internalType": "int32", 35 | "name": "num2", 36 | "type": "int32" 37 | } 38 | ], 39 | "name": "addNumbers", 40 | "outputs": [], 41 | "stateMutability": "nonpayable", 42 | "type": "function" 43 | }, 44 | { 45 | "inputs": [], 46 | "name": "result", 47 | "outputs": [ 48 | { 49 | "internalType": "int32", 50 | "name": "", 51 | "type": "int32" 52 | } 53 | ], 54 | "stateMutability": "view", 55 | "type": "function" 56 | } 57 | ] -------------------------------------------------------------------------------- /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.18.0", 15 | "@graphprotocol/graph-ts": "0.18.0" 16 | }, 17 | "devDependencies": { 18 | "mustache": "^3.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/subgraph/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, Address } from "@graphprotocol/graph-ts" 2 | import { 3 | YourContract, 4 | SetPurpose 5 | } from "../generated/YourContract/YourContract" 6 | import { Purpose, Sender } from "../generated/schema" 7 | 8 | export function handleSetPurpose(event: SetPurpose): void { 9 | 10 | let senderString = event.params.sender.toHexString() 11 | 12 | let sender = Sender.load(senderString) 13 | 14 | if (sender == null) { 15 | sender = new Sender(senderString) 16 | sender.address = event.params.sender 17 | sender.createdAt = event.block.timestamp 18 | sender.purposeCount = BigInt.fromI32(1) 19 | } 20 | else { 21 | sender.purposeCount = sender.purposeCount.plus(BigInt.fromI32(1)) 22 | } 23 | 24 | let purpose = new Purpose(event.transaction.hash.toHex() + "-" + event.logIndex.toString()) 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 | } 35 | -------------------------------------------------------------------------------- /packages/subgraph/src/schema.graphql: -------------------------------------------------------------------------------- 1 | type Purpose @entity { 2 | id: ID! 3 | sender: Sender! 4 | purpose: String! 5 | createdAt: BigInt! 6 | transactionHash: String! 7 | } 8 | 9 | type Sender @entity { 10 | id: ID! 11 | address: Bytes! 12 | purposes: [Purpose!] @derivedFrom(field: "sender") 13 | createdAt: BigInt! 14 | purposeCount: BigInt! 15 | } 16 | -------------------------------------------------------------------------------- /packages/subgraph/src/subgraph.template.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | schema: 3 | file: ./src/schema.graphql 4 | dataSources: 5 | - kind: ethereum/contract 6 | name: YourContract 7 | network: localhost 8 | source: 9 | address: '{{YourContractAddress}}' 10 | abi: YourContract 11 | startBlock: 1 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.4 15 | language: wasm/assemblyscript 16 | entities: 17 | - Purpose 18 | - Sender 19 | abis: 20 | - name: YourContract 21 | file: ./abis/YourContract.json 22 | eventHandlers: 23 | - event: SetPurpose(address,string) 24 | handler: handleSetPurpose 25 | file: ./src/mapping.ts 26 | --------------------------------------------------------------------------------