├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .idx └── dev.nix ├── Dockerfile ├── Dockerfile.build ├── LICENSE ├── Makefile ├── README.md ├── abi_wrapper.go ├── accounts.go ├── assets ├── DIDRegistry.sol ├── OwnerUpgradeableProxy.sol ├── UpgradeableProxy.sol ├── did_registry.go ├── erc20.abi ├── erc20.go ├── erc721.abi ├── erc721.go └── owner_upgradeable_proxy.go ├── client.go ├── client_test.go ├── cmd └── web3 │ ├── contracts.go │ ├── did.go │ ├── flatten.go │ ├── generate.go │ ├── main.go │ ├── start.go │ ├── transactions.go │ └── version.go ├── contracts ├── README.md ├── goodbye.sol ├── hello.sol ├── types.sol └── voting.sol ├── did ├── did.go ├── did_test.go ├── document.go └── escape.go ├── go.mod ├── go.sum ├── install.sh ├── networks.go ├── rpc.go ├── solc.go ├── types.go ├── vc └── vc.go ├── vyper ├── ERC20.vy ├── compiler.go ├── examples │ ├── ERC20.vy │ └── voting.vy ├── generate.go └── utils.go ├── web3.go └── web3_test.go /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/go 3 | { 4 | "name": "Go", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/go:1-1-bookworm" 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | // "postCreateCommand": "go version", 16 | 17 | // Configure tool-specific properties. 18 | // "customizations": {}, 19 | 20 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 21 | // "remoteUser": "root" 22 | } 23 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | *.sol 2 | *.bin 3 | bin/ 4 | lib/ 5 | secrets/ 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | 9 | build-linux: 10 | name: Build Linux 11 | runs-on: ubuntu-latest 12 | outputs: 13 | version: ${{ env.VERSION }} 14 | steps: 15 | 16 | - name: stuffs 17 | run: | 18 | # https://developer.github.com/webhooks/#events 19 | echo "comment: ${{ github.event.commits[0].message }}" 20 | 21 | - name: skip check 22 | if: contains( github.event.commits[0].message, 'skip ci' ) 23 | # good functions and things here: https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#functions 24 | run: | 25 | echo "exiting, skip ci found" 26 | exit 78 27 | 28 | - name: Check out code into the Go module directory 29 | uses: actions/checkout@v4 30 | 31 | - name: Set up Go 32 | uses: actions/setup-go@v5 33 | with: 34 | go-version: 1.23 35 | id: go 36 | 37 | - name: Bump version 38 | run: | 39 | git config --global user.email "ci@gochain.io" 40 | git config --global user.name "CI" 41 | git fetch --tags 42 | wget -O - https://raw.githubusercontent.com/treeder/bump/master/gitbump.sh | bash 43 | echo "VERSION=$(git tag --sort=-v:refname --list 'v[0-9]*' | head -n 1 | cut -c 2-)" >> $GITHUB_ENV 44 | 45 | - name: Binary build 46 | run: | 47 | echo "Version: $VERSION" 48 | 49 | # Linux x86 build 50 | CGO_ENABLED=1 GOOS=linux go build -ldflags "-X main.Version=$VERSION" -o web3_linux ./cmd/web3 51 | # Linux arm64build 52 | sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu 53 | CGO_ENABLED=1 GOOS=linux GOARCH="arm64" CC=aarch64-linux-gnu-gcc go build -ldflags "-X main.Version=$VERSION" -o web3_linux_arm64 ./cmd/web3 54 | 55 | # # Alpine build 56 | # docker create -v /data --name web3_sources alpine /bin/true 57 | # docker cp -a . web3_sources:/data/ 58 | # docker build -t gochain/builder:latest -f Dockerfile.build . 59 | # docker run --rm --volumes-from web3_sources -w /data gochain/builder go build -ldflags "-X main.Version=$VERSION" -o web3_alpine ./cmd/web3 60 | # docker cp web3_sources:/data/web3_alpine web3_alpine 61 | # # docker rm -f web3_sources 62 | 63 | - name: GitHub release 64 | uses: ncipollo/release-action@v1 65 | with: 66 | artifacts: "web3_linux, web3_linux_arm64" 67 | token: ${{ secrets.GITHUB_TOKEN }} 68 | tag: v${{ env.VERSION }} 69 | name: ${{ env.VERSION }} 70 | 71 | - name: Docker release 72 | env: 73 | DOCKER_USER: ${{ secrets.DOCKER_USER }} 74 | DOCKER_PASS: ${{ secrets.DOCKER_PASS }} 75 | run: | 76 | echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin 77 | 78 | docker build -t gochain/web3:latest . 79 | docker tag gochain/web3:latest gochain/web3:$VERSION 80 | docker push gochain/web3:$VERSION 81 | docker push gochain/web3:latest 82 | 83 | build-mac: 84 | name: Build MacOS 85 | runs-on: macOS-latest 86 | needs: build-linux 87 | env: 88 | VERSION: ${{ needs.build-linux.outputs.version }} 89 | steps: 90 | 91 | - name: Set up Go 92 | uses: actions/setup-go@v5 93 | with: 94 | go-version: 1.23 95 | id: go 96 | 97 | - name: Check out code into the Go module directory 98 | uses: actions/checkout@v4 99 | 100 | - name: build 101 | run: | 102 | go build -ldflags "-X main.Version=$VERSION" -o web3_mac ./cmd/web3 103 | CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -ldflags "-X main.Version=$VERSION" -o web3_mac_arm64 ./cmd/web3 104 | 105 | - name: release 106 | uses: ncipollo/release-action@v1 107 | with: 108 | artifacts: "web3_mac, web3_mac_arm64" 109 | token: ${{ secrets.GITHUB_TOKEN }} 110 | tag: v${{ env.VERSION }} 111 | name: ${{ env.VERSION }} 112 | allowUpdates: true 113 | omitNameDuringUpdate: true 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /tmp 5 | # dependencies 6 | vendor/ 7 | 8 | # IDEs and editors 9 | /.idea 10 | .project 11 | .classpath 12 | .c9/ 13 | *.launch 14 | .settings/ 15 | *.sublime-workspace 16 | 17 | # IDE - VSCode 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | 24 | # System Files 25 | .DS_Store 26 | Thumbs.db 27 | 28 | web3-cli 29 | /web3 30 | 31 | bin/ 32 | *.bin 33 | *.abi 34 | /web3_linux 35 | /web3_mac 36 | /web3_alpine 37 | /*.sol 38 | lib/ 39 | 40 | secrets/ 41 | -------------------------------------------------------------------------------- /.idx/dev.nix: -------------------------------------------------------------------------------- 1 | # To learn more about how to use Nix to configure your environment 2 | # see: https://developers.google.com/idx/guides/customize-idx-env 3 | { pkgs, ... }: { 4 | # Which nixpkgs channel to use. 5 | channel = "stable-24.05"; # or "unstable" 6 | 7 | # Use https://search.nixos.org/packages to find packages 8 | packages = [ 9 | pkgs.go 10 | # pkgs.python311 11 | # pkgs.python311Packages.pip 12 | # pkgs.nodejs_20 13 | # pkgs.nodePackages.nodemon 14 | pkgs.gnumake 15 | pkgs.gcc 16 | ]; 17 | 18 | # Sets environment variables in the workspace 19 | env = {}; 20 | idx = { 21 | # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" 22 | extensions = [ 23 | # "vscodevim.vim" 24 | "golang.go" 25 | ]; 26 | 27 | # Enable previews 28 | previews = { 29 | enable = true; 30 | previews = { 31 | # web = { 32 | # # Example: run "npm run dev" with PORT set to IDX's defined port for previews, 33 | # # and show it in IDX's web preview panel 34 | # command = ["npm" "run" "dev"]; 35 | # manager = "web"; 36 | # env = { 37 | # # Environment variables to set for your server 38 | # PORT = "$PORT"; 39 | # }; 40 | # }; 41 | }; 42 | }; 43 | 44 | # Workspace lifecycle hooks 45 | workspace = { 46 | # Runs when a workspace is first created 47 | onCreate = { 48 | # Example: install JS dependencies from NPM 49 | # npm-install = "npm install"; 50 | }; 51 | # Runs when the workspace is (re)started 52 | onStart = { 53 | # Example: start a background task to watch and re-build backend code 54 | # watch-backend = "npm run watch-backend"; 55 | }; 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM golang:1.23-alpine AS build-env 3 | RUN apk --no-cache add build-base git gcc linux-headers 4 | ENV D=/web3 5 | WORKDIR $D 6 | # cache dependencies 7 | ADD go.mod $D 8 | ADD go.sum $D 9 | RUN go mod download 10 | # now build 11 | ADD . $D 12 | RUN cd $D && go build -o web3-alpine ./cmd/web3 && cp web3-alpine /tmp/ 13 | 14 | # final stage 15 | FROM alpine 16 | RUN apk add --no-cache ca-certificates git 17 | WORKDIR /app 18 | COPY --from=build-env /tmp/web3-alpine /app/web3 19 | ENTRYPOINT ["/app/web3"] 20 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS build-env 2 | RUN apk --no-cache add build-base git gcc linux-headers 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 GoChain.io 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNAME_S := $(shell uname -s) 2 | build: 3 | go build ./cmd/web3 4 | 5 | install: build 6 | sudo cp web3 /usr/local/bin/web3 7 | 8 | builder: 9 | docker build -t gochain/builder:latest -f Dockerfile.build . 10 | 11 | # We need to run this every so often when we want to update the go version used for the alpine release (only the alpine release uses this) 12 | push-builder: builder 13 | docker push gochain/builder:latest 14 | 15 | docker: 16 | docker build -t gochain/web3:latest . 17 | 18 | push: docker 19 | # todo: version these, or auto push this using CI 20 | docker push gochain/web3:latest 21 | 22 | test: 23 | go test ./... 24 | 25 | .PHONY: install test build docker release 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ██╗ ██╗███████╗██████╗ ██████╗ ██████╗██╗ ██╗ 2 | ██║ ██║██╔════╝██╔══██╗╚════██╗ ██╔════╝██║ ██║ 3 | ██║ █╗ ██║█████╗ ██████╔╝ █████╔╝ ██║ ██║ ██║ 4 | ██║███╗██║██╔══╝ ██╔══██╗ ╚═══██╗ ██║ ██║ ██║ 5 | ╚███╔███╔╝███████╗██████╔╝██████╔╝ ╚██████╗███████╗██║ 6 | ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ 7 | 8 | Simple command line tool for interacting with web3 enabled blockchains - GoChain, Ethereum, etc. 9 | This repository also exports the backing golang `package web3`. 10 | 11 | [![API Reference]( 12 | https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667 13 | )](https://godoc.org/github.com/gochain/web3) 14 | ![](https://github.com/gochain/web3/workflows/release/badge.svg) 15 | 16 | ```sh 17 | web3 --help 18 | NAME: 19 | web3 - web3 cli tool 20 | 21 | USAGE: 22 | web3 [global options] command [command options] [arguments...] 23 | 24 | VERSION: 25 | 0.2.34 26 | 27 | COMMANDS: 28 | block, bl Block details for a block number (decimal integer) or hash (hexadecimal with 0x prefix). Omit for latest. 29 | transaction, tx Transaction details for a tx hash 30 | receipt, rc Transaction receipt for a tx hash 31 | address, addr Account details for a specific address, or the one corresponding to the private key. 32 | balance Get balance for your private key or an address passed in(you could also use "block" as an optional parameter). eg: `balance 0xABC123` 33 | increasegas Increase gas for a transaction. Useful if a tx is taking too long and you want it to go faster. 34 | replace Replace transaction. If a transaction is still pending, you can attempt to replace it. 35 | contract, c Contract operations 36 | snapshot, sn Clique snapshot 37 | id, id Network/Chain information 38 | start Start a local GoChain development node 39 | myaddress Returns the address associated with WEB3_PRIVATE_KEY 40 | account, a Account operations 41 | transfer, send Transfer GO/ETH to an account. eg: `web3 transfer 10.1 to 0xADDRESS` 42 | env List environment variables 43 | generate, g Generate code 44 | did Distributed identity operations 45 | claim Verifiable claims operations 46 | help, h Shows a list of commands or help for one command 47 | 48 | GLOBAL OPTIONS: 49 | --network value, -n value The name of the network. Options: gochain/testnet/ethereum/ropsten/localhost. (default: "gochain") [$WEB3_NETWORK] 50 | --testnet Shorthand for '-network testnet'. 51 | --rpc-url value The network RPC URL [$WEB3_RPC_URL] 52 | --verbose Enable verbose logging 53 | --format value, -f value Output format. Options: json. Default: human readable output. 54 | --help, -h show help 55 | --version, -v print the version 56 | ``` 57 | 58 | 59 | ## Install web3 60 | 61 | Quick one line install: 62 | 63 | ```sh 64 | curl -LSs https://raw.githubusercontent.com/gochain/web3/master/install.sh | sh 65 | ``` 66 | 67 | [Install Docker](https://docs.docker.com/install/) (optional) - not required for all commands, but if you plan on building and deploying smart contracts, 68 | you'll need Docker installed. 69 | 70 | [More installation options](#More-installation-options) 71 | 72 | ## Quickstart 73 | 74 | If you just plan to read from the blockchain, you do not need any GO tokens and you do not need to set your `WEB3_PRIVATE_KEY`. If you plan to deploy contracts or write anything to the blockchain, you'll need tokens and you'll need to set your `WEB3_PRIVATE_KEY` for the account that has those tokens. 75 | 76 | ### Pick a network to use 77 | 78 | #### a) Run a local node 79 | 80 | Run this command to start a local node. It will print 10 addresses with keys upon starting that you can use to deploy and interact. 81 | 82 | ```sh 83 | web3 start 84 | export WEB3_NETWORK=localhost 85 | ``` 86 | 87 | #### b) Use the GoChain testnet 88 | 89 | ```sh 90 | export WEB3_NETWORK=testnet 91 | ``` 92 | 93 | To do any write operations, you'll need some testnet GO. You can get some at https://faucet.gochain.io/ or ask in [GoChain Developers Telegram Group](https://t.me/gochain_testnet). 94 | 95 | #### c) Use the GoChain mainnet or another web3 network 96 | 97 | ```sh 98 | export WEB3_NETWORK=gochain 99 | ``` 100 | 101 | You'll need mainnet GO for this which you can [buy on various exchanges](https://gochain.io/go). 102 | 103 | #### d) Ethereum or any other web3 compatible network 104 | 105 | Most people use Infura for Ethereum which requires an API key to use. Sign in to [Infura](https://infura.io), create a project, click the settings tab and find your unique mainnet RPC URL under "ENDPOINTS". Then set web3 to use it with: 106 | 107 | ```sh 108 | export WEB3_RPC_URL=https://mainnet.infura.io/v3/YOURUNIQUEKEY 109 | ``` 110 | 111 | ### Set Private Key (optional) 112 | 113 | Required if you plan to deploy or write transactions. 114 | 115 | ```sh 116 | export WEB3_PRIVATE_KEY=0x... 117 | ``` 118 | 119 | ### Deploy a contract 120 | 121 | Copy [contracts/hello.sol](contracts/hello.sol) into your current directory. 122 | 123 | Then: 124 | 125 | ```sh 126 | web3 contract build hello.sol 127 | web3 contract deploy Hello.bin 128 | ``` 129 | 130 | you could also verify it in the block explorer after deployment 131 | ```sh 132 | web3 contract deploy --verify hello_flatten.sol Hello.bin 133 | ``` 134 | 135 | This will return a contract address, copy it and use below. 136 | 137 | ### Read from a contract 138 | 139 | Let's call a read function (which is free): 140 | 141 | ```sh 142 | web3 contract call --address 0xCONTRACT_ADDRESS --abi Hello.abi --function hello 143 | ``` 144 | 145 | That should return: `[Hello World]`. 146 | 147 | ### Write to a contract 148 | 149 | Now let's change the name: 150 | 151 | ```sh 152 | web3 contract call --address 0xCONTRACT_ADDRESS --abi Hello.abi --function setName "Johnny" 153 | ``` 154 | 155 | And call the hello function again to see if the name changed: 156 | 157 | ```sh 158 | web3 contract call --address 0xCONTRACT_ADDRESS --abi Hello.abi --function hello 159 | ``` 160 | 161 | Now it should return `[Hello Johnny]` 162 | 163 | :boom: 164 | 165 | ### Troubleshooting 166 | 167 | If it doesn't return Hello Johnny, you can check the logs and receipt with: 168 | 169 | ```sh 170 | web3 rc TX_HASH 171 | ``` 172 | 173 | ## Testing 174 | 175 | To automate testing using web3 CLI, enable the JSON format flag with `--format json`. This will 176 | return easily parseable results for your tests. Eg: 177 | 178 | ```sh 179 | web3 --format json contract call --address 0xCONTRACT_ADDRESS --abi Hello.abi --function hello 180 | ``` 181 | 182 | And you'll get a JSON response like this: 183 | 184 | ```json 185 | { 186 | "response": [ 187 | "Hello", 188 | "World" 189 | ] 190 | } 191 | ``` 192 | 193 | ## Generating Common Contracts 194 | 195 | web3 includes some of the most common contracts so you can generate and deploy things like a token contract (ERC20) 196 | or a collectible contract (ERC721) in seconds. The generated contract uses [OpenZeppelin](https://openzeppelin.org/) contracts so you can be sure these are secure and industry standard. 197 | 198 | Generate an ERC20 contract: 199 | 200 | ```sh 201 | web3 generate contract erc20 --name "Test Tokens" --symbol TEST 202 | ``` 203 | 204 | That's it! Now you can literally just deploy it and be done. Or open the generated code to see what was generated and modify it to your liking. To see all the available options for generating an ERC20 contract, use `web3 generate contract erc20 --help` 205 | 206 | Generate an ERC721 contract: 207 | 208 | ```sh 209 | web3 generate contract erc721 --name "Kitties" --symbol CAT 210 | ``` 211 | 212 | To see all the available options for generating an ERC721 contract, use `web3 generate contract erc721 --help` 213 | 214 | ## Deploying an Upgradeable Contract 215 | 216 | The `web3` tool comes with built-in support for deploying contracts that can be 217 | upgraded later. To deploy an upgradeable contract, simply specify the 218 | `--upgradeable` flag while deploying. From our `Hello` example above: 219 | 220 | ```sh 221 | web3 contract deploy --upgradeable Hello.bin 222 | ``` 223 | 224 | This will return the contract address. Let's set the contract address environment variable so you can use it throughout the rest of this 225 | tutorial (alternatively you can pass in the `--address CONTRACT_ADDRESS` flag on all the commands). 226 | 227 | ```sh 228 | export WEB3_ADDRESS=0xCONTRACT_ADDRESS 229 | ``` 230 | 231 | Internally, deploying an upgradeable contract will actually deploy two separate contracts: 232 | 233 | 1. Your original `Hello` contract. 234 | 2. A proxy contract for redirecting calls and storage. 235 | 236 | The returned contract address is the address of your proxy. To see the contract 237 | address that your proxy is pointing to, you can use the `target` command in 238 | the CLI: 239 | 240 | ```sh 241 | web3 contract target 242 | ``` 243 | 244 | One caveat to using upgradeable contracts is that their constructors will not 245 | execute. To get around this, we will have to initialize our contract with an 246 | initial call to `setName`: 247 | 248 | ```sh 249 | web3 contract call --abi Hello.abi --function setName "World" 250 | ``` 251 | 252 | Now we can interact with our upgradeable contract just like a normal contract: 253 | 254 | ```sh 255 | web3 contract call --abi Hello.abi --function hello 256 | # returns: [Hello World] 257 | ``` 258 | 259 | Alright, so we have a working contract. Let's upgrade it! 260 | 261 | ### Upgrading the contract 262 | 263 | We can now deploy a different contract (without the `upgradeable` flag) and 264 | redirect our upgradeable contract to point to that new contract. 265 | 266 | Copy [contracts/goodbye.sol](contracts/goodbye.sol) into your current directory 267 | and build and deploy it: 268 | 269 | ```sh 270 | web3 contract build goodbye.sol 271 | web3 contract deploy Goodbye.bin 272 | ``` 273 | 274 | Using the new `Goodbye` contract address, we can upgrade our previous contract 275 | using the `contract upgrade` command: 276 | 277 | ```sh 278 | web3 contract upgrade --to 0xGOODBYE_CONTRACT_ADDRESS 279 | ``` 280 | 281 | We can see that our proxy contract now points to this new contract by 282 | calling the `hello` function again: 283 | 284 | ```sh 285 | web3 contract call --abi Hello.abi --function hello 286 | # returns: [Goodbye World] 287 | ``` 288 | 289 | Note that contracts can only be upgraded by the account that created them. 290 | 291 | ### Pausing and resuming a contract 292 | 293 | Upgradeable contracts also include the ability to pause & resume execution. 294 | This can be useful if you discover a bug in your contract and you wish to cease 295 | operation until you can upgrade to a fixed version. 296 | 297 | Pausing a contract is simple: 298 | 299 | ```sh 300 | web3 contract pause 301 | ``` 302 | 303 | Wait a minute for the transaction to go through, then try to use the contract again and it will fail: 304 | 305 | ```sh 306 | web3 contract call --abi Hello.abi --function hello 307 | # returns: ERROR: Cannot call the contract: abi: unmarshalling empty output 308 | ``` 309 | 310 | Contracts can be upgraded while they are paused. To execute any other contract functions, you 311 | will need to first resume operation: 312 | 313 | ```sh 314 | web3 contract resume 315 | ``` 316 | 317 | ## The Most Common Available commands 318 | 319 | ### Global parameters 320 | 321 | #### Choosing a network 322 | 323 | To choose a network, you can either set `WEB3_NETWORK` or `WEB3_RPC_URL` environment variables or pass it in explicitly 324 | on each command with the `--network` or `--rpc-url` flag. 325 | 326 | Available name networks are: 327 | 328 | * gochain (default) 329 | * testnet 330 | * ethereum 331 | * ropsten 332 | * localhost 333 | 334 | The RPC URL is a full URL to a host, for eg: `https://rpc.gochain.io` or `http://localhost:8545` 335 | 336 | #### Setting your private key 337 | 338 | Set your private key in the environment so it can be used in all the commands below: 339 | 340 | ```sh 341 | export WEB3_PRIVATE_KEY=0xKEY 342 | ``` 343 | 344 | ### Check balance 345 | 346 | ```sh 347 | web3 balance 348 | ``` 349 | 350 | ### Transfer tokens 351 | 352 | ```sh 353 | web3 transfer 0.1 to 0x67683dd2a499E765BCBE0035439345f48996892f 354 | ``` 355 | 356 | ### Get transaction details 357 | 358 | ```sh 359 | web3 tx TX_HASH 360 | ``` 361 | 362 | ### Build a smart contract 363 | 364 | ```sh 365 | web3 contract build FILENAME.sol --solc-version SOLC_VERSION 366 | ``` 367 | 368 | **Parameters:** 369 | 370 | * FILENAME - the name of the .sol file, eg: `hello.sol` 371 | * SOLC_VERSION - the version of the solc compiler 372 | 373 | ### Flatten a smart contract 374 | 375 | Sometimes to verify a contract you have to flatten it before. 376 | 377 | ```sh 378 | web3 contract flatten FILENAME.sol -o OUTPUT_FILE 379 | ``` 380 | 381 | **Parameters:** 382 | 383 | * FILENAME - the name of the .sol file, eg: `hello.sol` 384 | 385 | * OUTPUT_FILE (optional) - the output file 386 | 387 | ### Deploy a smart contract to a network 388 | 389 | ```sh 390 | web3 contract deploy FILENAME.bin 391 | ``` 392 | 393 | **Parameters:** 394 | 395 | * FILENAME - the name of the .bin 396 | 397 | ### Call a function of a deployed contract 398 | 399 | Note: you can set `WEB3_ADDRESS=0xCONTRACT_ADDRESS` environment variable to skip the `--address` flag in the commands below. 400 | 401 | ```sh 402 | web3 contract call --amount AMOUNT --address CONTRACT_ADDRESS --abi CONTRACT_ABI_FILE --function FUNCTION_NAME FUNCTION_PARAMETERS 403 | ``` 404 | 405 | or using bundled abi files 406 | 407 | ```sh 408 | web3 contract call --amount AMOUNT --address CONTRACT_ADDRESS --abi erc20|erc721 --function FUNCTION_NAME FUNCTION_PARAMETERS 409 | ``` 410 | 411 | **Parameters:** 412 | 413 | * CONTRACT_ADDRESS - the address of the deployed contract 414 | * CONTRACT_ABI_FILE - the abi file of the deployed contract (take into account that there are some bundled abi files like erc20 and erc721 so you could use them without downloading or compiling them) 415 | * FUNCTION_NAME - the name of the function you want to call 416 | * FUNCTION_PARAMETERS - the list of the function parameters 417 | * AMOUNT - amount of wei to be send with transaction (require only for paid transact functions) 418 | 419 | ### List functions in an ABI 420 | 421 | ```sh 422 | web3 contract list --abi CONTRACT_ABI_FILE 423 | ``` 424 | 425 | **Parameters:** 426 | 427 | * CONTRACT_ABI_FILE - the abi file of the compiled contract 428 | 429 | ### Generate common contracts - ERC20, ERC721, etc 430 | 431 | ```sh 432 | web3 generate contract [erc20/erc721] --name "TEST Tokens" --symbol "TEST" 433 | ``` 434 | 435 | See `web3 generate contract --help` for more information. 436 | 437 | ### Generate ABI bindings 438 | 439 | ```sh 440 | web3 generate code --abi CONTRACT_ABI_FILE --out OUT_FILENAME --lang [go|objc|java] --pkg PGK_NAME 441 | ``` 442 | 443 | See `web3 generate code --help` for more information. 444 | 445 | **Parameters:** 446 | - CONTRACT_ABI_FILE - the abi file of the compiled contract 447 | - OUT_FILENAME - the output file 448 | - PGK_NAME - package name 449 | 450 | ### Show information about a block 451 | 452 | ```sh 453 | web3 block BLOCK_ID 454 | ``` 455 | 456 | **Parameters:** 457 | 458 | - BLOCK_ID - id of a block (omit for `latest`) 459 | 460 | ### Show information about an address 461 | 462 | ```sj 463 | web3 transaction ADDRESS_HASH 464 | ``` 465 | 466 | **Parameters:** 467 | 468 | * ADDRESS_HASH - hash of the address 469 | 470 | ### Verify a smart contract to a block explorer 471 | 472 | ```sh 473 | web3 contract verify --explorer-api EXPLORER_API_URL --address CONTRACT_ADDRESS --contract-name CONTRACT_NAME FILENAME.sol 474 | ``` 475 | 476 | **Parameters:** 477 | 478 | * EXPLORER_API_URL - URL for block explorer API (eg https://testnet-explorer.gochain.io/api) - Optional for GoChain networks, which use `{testnet-}explorer.gochain.io` by default. 479 | * CONTRACT_ADDRESS - address of a deployed contract 480 | * CONTRACT_NAME - name of a deployed contract 481 | * FILENAME - the name of the .sol file with a contract source 482 | 483 | ## More installation options 484 | 485 | ### Install a specific version 486 | 487 | You can use the script to install a specific version: 488 | 489 | ```sh 490 | curl -LSs https://raw.githubusercontent.com/gochain/web3/master/install.sh | sh -s v0.0.9 491 | ``` 492 | 493 | ### Install using the Go language 494 | 495 | ```sh 496 | go install github.com/gochain/web3/cmd/web3 497 | ``` 498 | 499 | ### Build from source 500 | 501 | Clone this repo: 502 | 503 | ```sh 504 | git clone https://github.com/gochain/web3 505 | cd web3 506 | make install 507 | # or just `make build` to build it into current directory 508 | web3 help 509 | ``` 510 | -------------------------------------------------------------------------------- /abi_wrapper.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/gochain/gochain/v4/accounts/abi" 12 | "github.com/gochain/web3/assets" 13 | ) 14 | 15 | // GetABI accepts either built in contracts (erc20, erc721), a file location or a URL 16 | func GetABI(abiFile string) (*abi.ABI, error) { 17 | abi, err := ABIBuiltIn(abiFile) 18 | if err != nil { 19 | return nil, fmt.Errorf("Cannot get ABI from the bundled storage: %v", err) 20 | } 21 | if abi != nil { 22 | return abi, nil 23 | } 24 | abi, err = ABIOpenFile(abiFile) 25 | if err == nil { 26 | return abi, nil 27 | } 28 | // else most likely just not found, log it? 29 | 30 | abi, err = ABIOpenURL(abiFile) 31 | if err == nil { 32 | return abi, nil 33 | } 34 | return nil, err 35 | } 36 | 37 | func ABIBuiltIn(abiFile string) (*abi.ABI, error) { 38 | if val, ok := bundledContracts[abiFile]; ok { 39 | return readAbi(strings.NewReader(val)) 40 | } 41 | return nil, nil 42 | } 43 | 44 | func ABIOpenFile(abiFile string) (*abi.ABI, error) { 45 | jsonReader, err := os.Open(abiFile) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return readAbi(jsonReader) 50 | } 51 | 52 | func ABIOpenURL(abiFile string) (*abi.ABI, error) { 53 | resp, err := http.Get(abiFile) 54 | if err != nil { 55 | return nil, fmt.Errorf("error getting ABI: %v", err) 56 | } 57 | defer resp.Body.Close() 58 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { 59 | bodyBytes, err := ioutil.ReadAll(resp.Body) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return nil, fmt.Errorf("error getting ABI %v: %v", resp.StatusCode, string(bodyBytes)) 64 | } 65 | return readAbi(resp.Body) 66 | } 67 | 68 | func readAbi(reader io.Reader) (*abi.ABI, error) { 69 | abi, err := abi.JSON(reader) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return &abi, nil 74 | } 75 | 76 | var bundledContracts = map[string]string{ 77 | "erc20": assets.ERC20ABI, 78 | "erc721": assets.ERC721ABI} 79 | -------------------------------------------------------------------------------- /accounts.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "encoding/hex" 6 | "strings" 7 | 8 | "github.com/gochain/gochain/v4/common" 9 | "github.com/gochain/gochain/v4/crypto" 10 | ) 11 | 12 | func CreateAccount() (*Account, error) { 13 | key, err := crypto.GenerateKey() 14 | if err != nil { 15 | return nil, err 16 | } 17 | return &Account{ 18 | key: key, 19 | }, nil 20 | } 21 | 22 | func ParsePrivateKey(pkHex string) (*Account, error) { 23 | fromPK := strings.TrimPrefix(pkHex, "0x") 24 | key, err := crypto.HexToECDSA(fromPK) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &Account{ 29 | key: key, 30 | }, nil 31 | } 32 | 33 | type Account struct { 34 | key *ecdsa.PrivateKey 35 | } 36 | 37 | func (a *Account) Key() *ecdsa.PrivateKey { 38 | return a.key 39 | } 40 | 41 | func (a *Account) Address() common.Address { 42 | return crypto.PubkeyToAddress(a.key.PublicKey) 43 | } 44 | 45 | func (a *Account) PublicKey() string { 46 | return a.Address().Hex() 47 | } 48 | 49 | func (a *Account) PrivateKey() string { 50 | return "0x" + hex.EncodeToString(crypto.FromECDSA(a.key)) 51 | } 52 | -------------------------------------------------------------------------------- /assets/DIDRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract DIDRegistry { 4 | mapping(bytes32 => address) public owners; 5 | mapping(bytes32 => string) public hashes; 6 | 7 | /* 8 | * Registers associates an identifier with an IPFS hash. 9 | * 10 | * The identifier must have been previously unregistered or the 11 | * registration must belong to the message sender. 12 | */ 13 | function register(bytes32 identifier, string hash) public { 14 | address owner = owners[identifier]; 15 | require(owner == 0x0 || owner == msg.sender); 16 | owners[identifier] = msg.sender; 17 | hashes[identifier] = hash; 18 | } 19 | 20 | /* 21 | * Returns the owner address of the given identifier. 22 | * Returns 0x0 if no owner has claimed the identifier. 23 | */ 24 | function owner(bytes32 identifier) public view returns (address) { 25 | return owners[identifier]; 26 | } 27 | 28 | /* 29 | * Returns the IPFS hash for the given identifier. 30 | * Returns an empty string if the identifier has not been registered. 31 | */ 32 | function hash(bytes32 identifier) public view returns (string) { 33 | return hashes[identifier]; 34 | } 35 | } -------------------------------------------------------------------------------- /assets/OwnerUpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./UpgradeableProxy.sol"; 4 | 5 | /* 6 | * OwnerUpgradeableProxy is an upgradeable proxy that only allows the contract 7 | * owner to upgrade and pause the proxy. 8 | */ 9 | contract OwnerUpgradeableProxy is UpgradeableProxy { 10 | bytes32 private constant ownerPosition = keccak256("gochain.proxy.owner"); 11 | 12 | /* 13 | * Initializes the proxy and sets the owner. 14 | */ 15 | constructor() public { 16 | _setOwner(msg.sender); 17 | } 18 | 19 | /* 20 | * Restricts a function to only allow execution by the proxy owner. 21 | */ 22 | modifier ownerOnly() { 23 | require(msg.sender == owner()); 24 | _; 25 | } 26 | 27 | /* 28 | * Returns the owner of the proxy contract. 29 | */ 30 | function owner() public view returns (address addr) { 31 | bytes32 pos = ownerPosition; 32 | assembly { 33 | addr := sload(pos) 34 | } 35 | } 36 | 37 | /* 38 | * Sets the owner of the contract. 39 | */ 40 | function _setOwner(address addr) internal { 41 | bytes32 pos = ownerPosition; 42 | assembly { 43 | sstore(pos, addr) 44 | } 45 | } 46 | 47 | /* 48 | * Upgrades the contract to a new target address. Only allowed by the owner. 49 | */ 50 | function upgrade(address target) public ownerOnly { 51 | _upgrade(target); 52 | } 53 | 54 | /* 55 | * Pauses the contract and does not allow functions to be executed besides 56 | * declared functions directly on the proxy (e.g. upgrade(), resume()). 57 | */ 58 | function pause() public ownerOnly { 59 | _pause(); 60 | } 61 | 62 | /* 63 | * Resumes a previously paused contract. 64 | */ 65 | function resume() public ownerOnly { 66 | _resume(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/UpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | * UpgradeableProxy is the base contract for all upgradeable contracts. 5 | * It implements proxy functionality, internal upgrade mechanisms, and internal 6 | * pause/resume mechanisms. 7 | * 8 | * Implementations must handle the specific upgrade & pause rules. 9 | */ 10 | contract UpgradeableProxy { 11 | event Upgraded(address indexed target); 12 | event Paused(); 13 | event Resumed(); 14 | 15 | bytes32 private constant targetPosition = keccak256("gochain.proxy.target"); 16 | bytes32 private constant pausedPosition = keccak256("gochain.proxy.paused"); 17 | 18 | /* 19 | * Initializes the starting target contract address. The placeholder 20 | * address is replaced during deployment to the correct address. 21 | */ 22 | constructor() public { 23 | address initialTarget = 0xEEfFEEffeEffeeFFeeffeeffeEfFeEffeEFfEeff; 24 | _upgrade(initialTarget); 25 | } 26 | 27 | /* 28 | * Returns the contract address that is currently being proxied to. 29 | */ 30 | function target() public view returns (address addr) { 31 | bytes32 pos = targetPosition; 32 | assembly { 33 | addr := sload(pos) 34 | } 35 | } 36 | 37 | /* 38 | * Abstract declaration of upgrade function. 39 | */ 40 | function upgrade(address addr) public; 41 | 42 | /* 43 | * Updates the target contract address. 44 | */ 45 | function _upgrade(address addr) internal { 46 | address current = target(); 47 | require(current != addr); 48 | bytes32 pos = targetPosition; 49 | assembly { 50 | sstore(pos, addr) 51 | } 52 | emit Upgraded(addr); 53 | } 54 | 55 | /* 56 | * Returns whether the contract is currently paused. 57 | */ 58 | function paused() public view returns (bool val) { 59 | bytes32 pos = pausedPosition; 60 | bytes32 val32 = 0; 61 | assembly { 62 | val32 := sload(pos) 63 | } 64 | val = val32 != 0; 65 | } 66 | 67 | /* 68 | * Abstract declaration of pause function. 69 | */ 70 | function pause() public; 71 | 72 | /* 73 | * Abstract declaration of resume function. 74 | */ 75 | function resume() public; 76 | 77 | /* 78 | * Marks the contract as paused. 79 | */ 80 | function _pause() internal { 81 | bytes32 pos = pausedPosition; 82 | bytes1 val = 1; 83 | assembly { 84 | sstore(pos, val) 85 | } 86 | emit Paused(); 87 | } 88 | 89 | /* 90 | * Marks the contract as resumed (aka unpaused). 91 | */ 92 | function _resume() internal { 93 | bytes32 pos = pausedPosition; 94 | bytes1 val = 0; 95 | assembly { 96 | sstore(pos, val) 97 | } 98 | emit Resumed(); 99 | } 100 | 101 | /* 102 | * Passthrough function for all function calls that cannot be found. 103 | * Functions are delegated to the target contract but maintain the local storage. 104 | */ 105 | function() payable public { 106 | bool _paused = paused(); 107 | require(!_paused); 108 | 109 | address _target = target(); 110 | require(_target != address(0)); 111 | 112 | assembly { 113 | let ptr := mload(0x40) 114 | calldatacopy(ptr, 0, calldatasize) 115 | let result := delegatecall(gas, _target, ptr, calldatasize, 0, 0) 116 | let size := returndatasize 117 | returndatacopy(ptr, 0, size) 118 | 119 | switch result 120 | case 0 { revert(ptr, size) } 121 | default { return(ptr, size) } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /assets/did_registry.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | const DIDRegistryABI = `[ 4 | { 5 | "constant": true, 6 | "inputs": [ 7 | { 8 | "name": "identifier", 9 | "type": "bytes32" 10 | } 11 | ], 12 | "name": "owner", 13 | "outputs": [ 14 | { 15 | "name": "", 16 | "type": "address" 17 | } 18 | ], 19 | "payable": false, 20 | "stateMutability": "view", 21 | "type": "function" 22 | }, 23 | { 24 | "constant": false, 25 | "inputs": [ 26 | { 27 | "name": "identifier", 28 | "type": "bytes32" 29 | }, 30 | { 31 | "name": "hash", 32 | "type": "string" 33 | } 34 | ], 35 | "name": "register", 36 | "outputs": [], 37 | "payable": false, 38 | "stateMutability": "nonpayable", 39 | "type": "function" 40 | }, 41 | { 42 | "constant": true, 43 | "inputs": [ 44 | { 45 | "name": "", 46 | "type": "bytes32" 47 | } 48 | ], 49 | "name": "hashes", 50 | "outputs": [ 51 | { 52 | "name": "", 53 | "type": "string" 54 | } 55 | ], 56 | "payable": false, 57 | "stateMutability": "view", 58 | "type": "function" 59 | }, 60 | { 61 | "constant": true, 62 | "inputs": [ 63 | { 64 | "name": "identifier", 65 | "type": "bytes32" 66 | } 67 | ], 68 | "name": "hash", 69 | "outputs": [ 70 | { 71 | "name": "", 72 | "type": "string" 73 | } 74 | ], 75 | "payable": false, 76 | "stateMutability": "view", 77 | "type": "function" 78 | }, 79 | { 80 | "constant": true, 81 | "inputs": [ 82 | { 83 | "name": "", 84 | "type": "bytes32" 85 | } 86 | ], 87 | "name": "owners", 88 | "outputs": [ 89 | { 90 | "name": "", 91 | "type": "address" 92 | } 93 | ], 94 | "payable": false, 95 | "stateMutability": "view", 96 | "type": "function" 97 | } 98 | ] 99 | ` 100 | -------------------------------------------------------------------------------- /assets/erc20.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] -------------------------------------------------------------------------------- /assets/erc20.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type Erc20Params struct { 10 | Symbol string 11 | TokenName string 12 | } 13 | 14 | func EscapeName(symbol string) string { 15 | name := strings.ReplaceAll(symbol, "-", "_") 16 | return name 17 | } 18 | 19 | func GenERC20(ctx context.Context, openZeppelinVersion string, params *Erc20Params) (string, error) { 20 | var part1, part2, part3 strings.Builder 21 | part1.WriteString(fmt.Sprintf("// @openzeppelin v%v\n", openZeppelinVersion)) 22 | part1.WriteString("pragma solidity ^0.8.4;\n\nimport \"./lib/oz/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol\";\n") 23 | part2.WriteString("\ncontract ") 24 | part2.WriteString(EscapeName(params.Symbol)) 25 | part2.WriteString(" is") 26 | { 27 | part3.WriteString(" constructor() public ERC20PresetMinterPauser(\"") 28 | part3.WriteString(params.TokenName) 29 | part3.WriteString("\", \"") 30 | part3.WriteString(params.Symbol) 31 | part3.WriteString("\")") 32 | 33 | } 34 | part2.WriteString(" ERC20PresetMinterPauser {\n\n") 35 | 36 | part3.WriteString(" {}\n\n}\n") 37 | 38 | return part1.String() + part2.String() + part3.String(), nil 39 | } 40 | 41 | const ERC20ABI = `[ 42 | { 43 | "constant": true, 44 | "inputs": [], 45 | "name": "name", 46 | "outputs": [ 47 | { 48 | "name": "", 49 | "type": "string" 50 | } 51 | ], 52 | "payable": false, 53 | "stateMutability": "view", 54 | "type": "function" 55 | }, 56 | { 57 | "constant": false, 58 | "inputs": [ 59 | { 60 | "name": "_spender", 61 | "type": "address" 62 | }, 63 | { 64 | "name": "_value", 65 | "type": "uint256" 66 | } 67 | ], 68 | "name": "approve", 69 | "outputs": [ 70 | { 71 | "name": "", 72 | "type": "bool" 73 | } 74 | ], 75 | "payable": false, 76 | "stateMutability": "nonpayable", 77 | "type": "function" 78 | }, 79 | { 80 | "constant": true, 81 | "inputs": [], 82 | "name": "totalSupply", 83 | "outputs": [ 84 | { 85 | "name": "", 86 | "type": "uint256" 87 | } 88 | ], 89 | "payable": false, 90 | "stateMutability": "view", 91 | "type": "function" 92 | }, 93 | { 94 | "constant": false, 95 | "inputs": [ 96 | { 97 | "name": "_from", 98 | "type": "address" 99 | }, 100 | { 101 | "name": "_to", 102 | "type": "address" 103 | }, 104 | { 105 | "name": "_value", 106 | "type": "uint256" 107 | } 108 | ], 109 | "name": "transferFrom", 110 | "outputs": [ 111 | { 112 | "name": "", 113 | "type": "bool" 114 | } 115 | ], 116 | "payable": false, 117 | "stateMutability": "nonpayable", 118 | "type": "function" 119 | }, 120 | { 121 | "constant": true, 122 | "inputs": [], 123 | "name": "decimals", 124 | "outputs": [ 125 | { 126 | "name": "", 127 | "type": "uint8" 128 | } 129 | ], 130 | "payable": false, 131 | "stateMutability": "view", 132 | "type": "function" 133 | }, 134 | { 135 | "constant": true, 136 | "inputs": [ 137 | { 138 | "name": "_owner", 139 | "type": "address" 140 | } 141 | ], 142 | "name": "balanceOf", 143 | "outputs": [ 144 | { 145 | "name": "balance", 146 | "type": "uint256" 147 | } 148 | ], 149 | "payable": false, 150 | "stateMutability": "view", 151 | "type": "function" 152 | }, 153 | { 154 | "constant": true, 155 | "inputs": [], 156 | "name": "symbol", 157 | "outputs": [ 158 | { 159 | "name": "", 160 | "type": "string" 161 | } 162 | ], 163 | "payable": false, 164 | "stateMutability": "view", 165 | "type": "function" 166 | }, 167 | { 168 | "constant": false, 169 | "inputs": [ 170 | { 171 | "name": "_to", 172 | "type": "address" 173 | }, 174 | { 175 | "name": "_value", 176 | "type": "uint256" 177 | } 178 | ], 179 | "name": "transfer", 180 | "outputs": [ 181 | { 182 | "name": "", 183 | "type": "bool" 184 | } 185 | ], 186 | "payable": false, 187 | "stateMutability": "nonpayable", 188 | "type": "function" 189 | }, 190 | { 191 | "constant": true, 192 | "inputs": [ 193 | { 194 | "name": "_owner", 195 | "type": "address" 196 | }, 197 | { 198 | "name": "_spender", 199 | "type": "address" 200 | } 201 | ], 202 | "name": "allowance", 203 | "outputs": [ 204 | { 205 | "name": "", 206 | "type": "uint256" 207 | } 208 | ], 209 | "payable": false, 210 | "stateMutability": "view", 211 | "type": "function" 212 | }, 213 | { 214 | "payable": true, 215 | "stateMutability": "payable", 216 | "type": "fallback" 217 | }, 218 | { 219 | "anonymous": false, 220 | "inputs": [ 221 | { 222 | "indexed": true, 223 | "name": "owner", 224 | "type": "address" 225 | }, 226 | { 227 | "indexed": true, 228 | "name": "spender", 229 | "type": "address" 230 | }, 231 | { 232 | "indexed": false, 233 | "name": "value", 234 | "type": "uint256" 235 | } 236 | ], 237 | "name": "Approval", 238 | "type": "event" 239 | }, 240 | { 241 | "anonymous": false, 242 | "inputs": [ 243 | { 244 | "indexed": true, 245 | "name": "from", 246 | "type": "address" 247 | }, 248 | { 249 | "indexed": true, 250 | "name": "to", 251 | "type": "address" 252 | }, 253 | { 254 | "indexed": false, 255 | "name": "value", 256 | "type": "uint256" 257 | } 258 | ], 259 | "name": "Transfer", 260 | "type": "event" 261 | } 262 | ] 263 | ` 264 | -------------------------------------------------------------------------------- /assets/erc721.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "_name", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [ 19 | { 20 | "name": "_tokenId", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "getApproved", 25 | "outputs": [ 26 | { 27 | "name": "_approved", 28 | "type": "address" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "view", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": false, 37 | "inputs": [ 38 | { 39 | "name": "_to", 40 | "type": "address" 41 | }, 42 | { 43 | "name": "_tokenId", 44 | "type": "uint256" 45 | } 46 | ], 47 | "name": "approve", 48 | "outputs": [], 49 | "payable": false, 50 | "stateMutability": "nonpayable", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": true, 55 | "inputs": [], 56 | "name": "implementsERC721", 57 | "outputs": [ 58 | { 59 | "name": "_implementsERC721", 60 | "type": "bool" 61 | } 62 | ], 63 | "payable": false, 64 | "stateMutability": "view", 65 | "type": "function" 66 | }, 67 | { 68 | "constant": true, 69 | "inputs": [], 70 | "name": "totalSupply", 71 | "outputs": [ 72 | { 73 | "name": "_totalSupply", 74 | "type": "uint256" 75 | } 76 | ], 77 | "payable": false, 78 | "stateMutability": "view", 79 | "type": "function" 80 | }, 81 | { 82 | "constant": false, 83 | "inputs": [ 84 | { 85 | "name": "_from", 86 | "type": "address" 87 | }, 88 | { 89 | "name": "_to", 90 | "type": "address" 91 | }, 92 | { 93 | "name": "_tokenId", 94 | "type": "uint256" 95 | } 96 | ], 97 | "name": "transferFrom", 98 | "outputs": [], 99 | "payable": false, 100 | "stateMutability": "nonpayable", 101 | "type": "function" 102 | }, 103 | { 104 | "constant": true, 105 | "inputs": [ 106 | { 107 | "name": "_owner", 108 | "type": "address" 109 | }, 110 | { 111 | "name": "_index", 112 | "type": "uint256" 113 | } 114 | ], 115 | "name": "tokenOfOwnerByIndex", 116 | "outputs": [ 117 | { 118 | "name": "_tokenId", 119 | "type": "uint256" 120 | } 121 | ], 122 | "payable": false, 123 | "stateMutability": "view", 124 | "type": "function" 125 | }, 126 | { 127 | "constant": true, 128 | "inputs": [ 129 | { 130 | "name": "_tokenId", 131 | "type": "uint256" 132 | } 133 | ], 134 | "name": "ownerOf", 135 | "outputs": [ 136 | { 137 | "name": "_owner", 138 | "type": "address" 139 | } 140 | ], 141 | "payable": false, 142 | "stateMutability": "view", 143 | "type": "function" 144 | }, 145 | { 146 | "constant": true, 147 | "inputs": [ 148 | { 149 | "name": "_tokenId", 150 | "type": "uint256" 151 | } 152 | ], 153 | "name": "tokenMetadata", 154 | "outputs": [ 155 | { 156 | "name": "_infoUrl", 157 | "type": "string" 158 | } 159 | ], 160 | "payable": false, 161 | "stateMutability": "view", 162 | "type": "function" 163 | }, 164 | { 165 | "constant": true, 166 | "inputs": [ 167 | { 168 | "name": "_owner", 169 | "type": "address" 170 | } 171 | ], 172 | "name": "balanceOf", 173 | "outputs": [ 174 | { 175 | "name": "_balance", 176 | "type": "uint256" 177 | } 178 | ], 179 | "payable": false, 180 | "stateMutability": "view", 181 | "type": "function" 182 | }, 183 | { 184 | "constant": false, 185 | "inputs": [ 186 | { 187 | "name": "_owner", 188 | "type": "address" 189 | }, 190 | { 191 | "name": "_tokenId", 192 | "type": "uint256" 193 | }, 194 | { 195 | "name": "_approvedAddress", 196 | "type": "address" 197 | }, 198 | { 199 | "name": "_metadata", 200 | "type": "string" 201 | } 202 | ], 203 | "name": "mint", 204 | "outputs": [], 205 | "payable": false, 206 | "stateMutability": "nonpayable", 207 | "type": "function" 208 | }, 209 | { 210 | "constant": true, 211 | "inputs": [], 212 | "name": "symbol", 213 | "outputs": [ 214 | { 215 | "name": "_symbol", 216 | "type": "string" 217 | } 218 | ], 219 | "payable": false, 220 | "stateMutability": "view", 221 | "type": "function" 222 | }, 223 | { 224 | "constant": false, 225 | "inputs": [ 226 | { 227 | "name": "_to", 228 | "type": "address" 229 | }, 230 | { 231 | "name": "_tokenId", 232 | "type": "uint256" 233 | } 234 | ], 235 | "name": "transfer", 236 | "outputs": [], 237 | "payable": false, 238 | "stateMutability": "nonpayable", 239 | "type": "function" 240 | }, 241 | { 242 | "constant": true, 243 | "inputs": [], 244 | "name": "numTokensTotal", 245 | "outputs": [ 246 | { 247 | "name": "", 248 | "type": "uint256" 249 | } 250 | ], 251 | "payable": false, 252 | "stateMutability": "view", 253 | "type": "function" 254 | }, 255 | { 256 | "constant": true, 257 | "inputs": [ 258 | { 259 | "name": "_owner", 260 | "type": "address" 261 | } 262 | ], 263 | "name": "getOwnerTokens", 264 | "outputs": [ 265 | { 266 | "name": "_tokenIds", 267 | "type": "uint256[]" 268 | } 269 | ], 270 | "payable": false, 271 | "stateMutability": "view", 272 | "type": "function" 273 | }, 274 | { 275 | "anonymous": false, 276 | "inputs": [ 277 | { 278 | "indexed": true, 279 | "name": "_to", 280 | "type": "address" 281 | }, 282 | { 283 | "indexed": true, 284 | "name": "_tokenId", 285 | "type": "uint256" 286 | } 287 | ], 288 | "name": "Mint", 289 | "type": "event" 290 | }, 291 | { 292 | "anonymous": false, 293 | "inputs": [ 294 | { 295 | "indexed": true, 296 | "name": "_from", 297 | "type": "address" 298 | }, 299 | { 300 | "indexed": true, 301 | "name": "_to", 302 | "type": "address" 303 | }, 304 | { 305 | "indexed": false, 306 | "name": "_tokenId", 307 | "type": "uint256" 308 | } 309 | ], 310 | "name": "Transfer", 311 | "type": "event" 312 | }, 313 | { 314 | "anonymous": false, 315 | "inputs": [ 316 | { 317 | "indexed": true, 318 | "name": "_owner", 319 | "type": "address" 320 | }, 321 | { 322 | "indexed": true, 323 | "name": "_approved", 324 | "type": "address" 325 | }, 326 | { 327 | "indexed": false, 328 | "name": "_tokenId", 329 | "type": "uint256" 330 | } 331 | ], 332 | "name": "Approval", 333 | "type": "event" 334 | } 335 | ] -------------------------------------------------------------------------------- /assets/erc721.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | type Erc721Params struct { 4 | Symbol string 5 | TokenName string 6 | BaseURI string // Base URL to look up token metadata 7 | ContractName string // for the contract, has to be escaped 8 | // Pausable bool 9 | // Mintable bool 10 | // Burnable bool 11 | } 12 | 13 | const ERC721Template = `pragma solidity ^0.8.4; 14 | 15 | import "./lib/oz/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol"; 16 | 17 | contract {{.ContractName}} is ERC721PresetMinterPauserAutoId { 18 | 19 | constructor() public 20 | ERC721PresetMinterPauserAutoId("{{.TokenName}}", "{{.Symbol}}", "{{.BaseURI}}") 21 | {} 22 | 23 | }` 24 | 25 | const ERC721ABI = `[ 26 | { 27 | "constant": true, 28 | "inputs": [], 29 | "name": "name", 30 | "outputs": [ 31 | { 32 | "name": "_name", 33 | "type": "string" 34 | } 35 | ], 36 | "payable": false, 37 | "stateMutability": "view", 38 | "type": "function" 39 | }, 40 | { 41 | "constant": true, 42 | "inputs": [ 43 | { 44 | "name": "_tokenId", 45 | "type": "uint256" 46 | } 47 | ], 48 | "name": "getApproved", 49 | "outputs": [ 50 | { 51 | "name": "_approved", 52 | "type": "address" 53 | } 54 | ], 55 | "payable": false, 56 | "stateMutability": "view", 57 | "type": "function" 58 | }, 59 | { 60 | "constant": false, 61 | "inputs": [ 62 | { 63 | "name": "_to", 64 | "type": "address" 65 | }, 66 | { 67 | "name": "_tokenId", 68 | "type": "uint256" 69 | } 70 | ], 71 | "name": "approve", 72 | "outputs": [], 73 | "payable": false, 74 | "stateMutability": "nonpayable", 75 | "type": "function" 76 | }, 77 | { 78 | "constant": true, 79 | "inputs": [], 80 | "name": "implementsERC721", 81 | "outputs": [ 82 | { 83 | "name": "_implementsERC721", 84 | "type": "bool" 85 | } 86 | ], 87 | "payable": false, 88 | "stateMutability": "view", 89 | "type": "function" 90 | }, 91 | { 92 | "constant": true, 93 | "inputs": [], 94 | "name": "totalSupply", 95 | "outputs": [ 96 | { 97 | "name": "_totalSupply", 98 | "type": "uint256" 99 | } 100 | ], 101 | "payable": false, 102 | "stateMutability": "view", 103 | "type": "function" 104 | }, 105 | { 106 | "constant": false, 107 | "inputs": [ 108 | { 109 | "name": "_from", 110 | "type": "address" 111 | }, 112 | { 113 | "name": "_to", 114 | "type": "address" 115 | }, 116 | { 117 | "name": "_tokenId", 118 | "type": "uint256" 119 | } 120 | ], 121 | "name": "transferFrom", 122 | "outputs": [], 123 | "payable": false, 124 | "stateMutability": "nonpayable", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": true, 129 | "inputs": [ 130 | { 131 | "name": "_owner", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_index", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "tokenOfOwnerByIndex", 140 | "outputs": [ 141 | { 142 | "name": "_tokenId", 143 | "type": "uint256" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "view", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_tokenId", 155 | "type": "uint256" 156 | } 157 | ], 158 | "name": "ownerOf", 159 | "outputs": [ 160 | { 161 | "name": "_owner", 162 | "type": "address" 163 | } 164 | ], 165 | "payable": false, 166 | "stateMutability": "view", 167 | "type": "function" 168 | }, 169 | { 170 | "constant": true, 171 | "inputs": [ 172 | { 173 | "name": "_tokenId", 174 | "type": "uint256" 175 | } 176 | ], 177 | "name": "tokenMetadata", 178 | "outputs": [ 179 | { 180 | "name": "_infoUrl", 181 | "type": "string" 182 | } 183 | ], 184 | "payable": false, 185 | "stateMutability": "view", 186 | "type": "function" 187 | }, 188 | { 189 | "constant": true, 190 | "inputs": [ 191 | { 192 | "name": "_owner", 193 | "type": "address" 194 | } 195 | ], 196 | "name": "balanceOf", 197 | "outputs": [ 198 | { 199 | "name": "_balance", 200 | "type": "uint256" 201 | } 202 | ], 203 | "payable": false, 204 | "stateMutability": "view", 205 | "type": "function" 206 | }, 207 | { 208 | "constant": false, 209 | "inputs": [ 210 | { 211 | "name": "_owner", 212 | "type": "address" 213 | }, 214 | { 215 | "name": "_tokenId", 216 | "type": "uint256" 217 | }, 218 | { 219 | "name": "_approvedAddress", 220 | "type": "address" 221 | }, 222 | { 223 | "name": "_metadata", 224 | "type": "string" 225 | } 226 | ], 227 | "name": "mint", 228 | "outputs": [], 229 | "payable": false, 230 | "stateMutability": "nonpayable", 231 | "type": "function" 232 | }, 233 | { 234 | "constant": true, 235 | "inputs": [], 236 | "name": "symbol", 237 | "outputs": [ 238 | { 239 | "name": "_symbol", 240 | "type": "string" 241 | } 242 | ], 243 | "payable": false, 244 | "stateMutability": "view", 245 | "type": "function" 246 | }, 247 | { 248 | "constant": false, 249 | "inputs": [ 250 | { 251 | "name": "_to", 252 | "type": "address" 253 | }, 254 | { 255 | "name": "_tokenId", 256 | "type": "uint256" 257 | } 258 | ], 259 | "name": "transfer", 260 | "outputs": [], 261 | "payable": false, 262 | "stateMutability": "nonpayable", 263 | "type": "function" 264 | }, 265 | { 266 | "constant": true, 267 | "inputs": [], 268 | "name": "numTokensTotal", 269 | "outputs": [ 270 | { 271 | "name": "", 272 | "type": "uint256" 273 | } 274 | ], 275 | "payable": false, 276 | "stateMutability": "view", 277 | "type": "function" 278 | }, 279 | { 280 | "constant": true, 281 | "inputs": [ 282 | { 283 | "name": "_owner", 284 | "type": "address" 285 | } 286 | ], 287 | "name": "getOwnerTokens", 288 | "outputs": [ 289 | { 290 | "name": "_tokenIds", 291 | "type": "uint256[]" 292 | } 293 | ], 294 | "payable": false, 295 | "stateMutability": "view", 296 | "type": "function" 297 | }, 298 | { 299 | "anonymous": false, 300 | "inputs": [ 301 | { 302 | "indexed": true, 303 | "name": "_to", 304 | "type": "address" 305 | }, 306 | { 307 | "indexed": true, 308 | "name": "_tokenId", 309 | "type": "uint256" 310 | } 311 | ], 312 | "name": "Mint", 313 | "type": "event" 314 | }, 315 | { 316 | "anonymous": false, 317 | "inputs": [ 318 | { 319 | "indexed": true, 320 | "name": "_from", 321 | "type": "address" 322 | }, 323 | { 324 | "indexed": true, 325 | "name": "_to", 326 | "type": "address" 327 | }, 328 | { 329 | "indexed": false, 330 | "name": "_tokenId", 331 | "type": "uint256" 332 | } 333 | ], 334 | "name": "Transfer", 335 | "type": "event" 336 | }, 337 | { 338 | "anonymous": false, 339 | "inputs": [ 340 | { 341 | "indexed": true, 342 | "name": "_owner", 343 | "type": "address" 344 | }, 345 | { 346 | "indexed": true, 347 | "name": "_approved", 348 | "type": "address" 349 | }, 350 | { 351 | "indexed": false, 352 | "name": "_tokenId", 353 | "type": "uint256" 354 | } 355 | ], 356 | "name": "Approval", 357 | "type": "event" 358 | } 359 | ]` 360 | -------------------------------------------------------------------------------- /assets/owner_upgradeable_proxy.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gochain/gochain/v4/common" 8 | ) 9 | 10 | // Contract only upgradeable by owner. 11 | const OwnerUpgradeableProxyBin = `0x608060405234801561001057600080fd5b50600073eeffeeffeeffeeffeeffeeffeeffeeffeeffeeff905061004281610060640100000000026401000000009004565b5061005b3361013a640100000000026401000000009004565b6101be565b60008061007a61017b640100000000026401000000009004565b91508273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141515156100b757600080fd5b60405180807f676f636861696e2e70726f78792e7461726765740000000000000000000000008152506014019050604051809103902090508281558273ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a2505050565b600060405180807f676f636861696e2e70726f78792e6f776e6572000000000000000000000000008152506013019050604051809103902090508181555050565b60008060405180807f676f636861696e2e70726f78792e746172676574000000000000000000000000815250601401905060405180910390209050805491505090565b610630806101cd6000396000f300608060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063046f7da2146100ff5780630900f010146101165780635c975abb146101595780638456cb59146101885780638da5cb5b1461019f578063d4b83992146101f6575b60008061008361024d565b91508115151561009257600080fd5b61009a6102a8565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515156100d857600080fd5b60405136600082376000803683855af43d806000843e81600081146100fb578184f35b8184fd5b34801561010b57600080fd5b506101146102eb565b005b34801561012257600080fd5b50610157600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610336565b005b34801561016557600080fd5b5061016e61024d565b604051808215151515815260200191505060405180910390f35b34801561019457600080fd5b5061019d610383565b005b3480156101ab57600080fd5b506101b46103ce565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561020257600080fd5b5061020b6102a8565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600080600060405180807f676f636861696e2e70726f78792e70617573656400000000000000000000000081525060140190506040518091039020915060006001029050815490506000600102816000191614159250505090565b60008060405180807f676f636861696e2e70726f78792e746172676574000000000000000000000000815250601401905060405180910390209050805491505090565b6102f36103ce565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561032c57600080fd5b610334610411565b565b61033e6103ce565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561037757600080fd5b610380816104a5565b50565b61038b6103ce565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156103c457600080fd5b6103cc610570565b565b60008060405180807f676f636861696e2e70726f78792e6f776e657200000000000000000000000000815250601301905060405180910390209050805491505090565b60008060405180807f676f636861696e2e70726f78792e70617573656400000000000000000000000081525060140190506040518091039020915060007f01000000000000000000000000000000000000000000000000000000000000000290508082557f62451d457bc659158be6e6247f56ec1df424a5c7597f71c20c2bc44e0965c8f960405160405180910390a15050565b6000806104b06102a8565b91508273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141515156104ed57600080fd5b60405180807f676f636861696e2e70726f78792e7461726765740000000000000000000000008152506014019050604051809103902090508281558273ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a2505050565b60008060405180807f676f636861696e2e70726f78792e70617573656400000000000000000000000081525060140190506040518091039020915060017f01000000000000000000000000000000000000000000000000000000000000000290508082557f9e87fac88ff661f02d44f95383c817fece4bce600a3dab7a54406878b965e75260405160405180910390a150505600a165627a7a72305820fb83ed4a5dce35fddc4424d2b82ae073f393ec3c109002bcbc397ce64d1ed3f00029` 12 | 13 | // OwnerUpgradeableProxyCode returns the code for an owner-upgradeable proxy contract. 14 | func OwnerUpgradeableProxyCode(target common.Address) string { 15 | code := OwnerUpgradeableProxyBin 16 | 17 | // Replace placeholder addresses for target contract in constructor. 18 | code = strings.Replace(code, `eeffeeffeeffeeffeeffeeffeeffeeffeeffeeff`, strings.TrimPrefix(target.String(), "0x"), -1) 19 | 20 | // Strip auxdata. 21 | return TrimContractCodeAuxdata(code) 22 | } 23 | 24 | // TrimContractCodeAuxdata removes the auxdata produced at the end of a contract. 25 | // This only used to strip system contract code so it only supports "bzzr0". 26 | func TrimContractCodeAuxdata(code string) string { 27 | const auxdataLen = 43 28 | if len(code) < auxdataLen { 29 | return code 30 | } 31 | auxdata := code[len(code)-auxdataLen:] 32 | if !strings.HasPrefix(auxdata, fmt.Sprintf("a165%08x", "bzzr0")) { 33 | return code 34 | } 35 | return strings.TrimSuffix(code, auxdata) 36 | } 37 | 38 | const UpgradeableProxyABI = `[ 39 | { 40 | "constant": false, 41 | "inputs": [], 42 | "name": "resume", 43 | "outputs": [], 44 | "payable": false, 45 | "stateMutability": "nonpayable", 46 | "type": "function" 47 | }, 48 | { 49 | "constant": false, 50 | "inputs": [ 51 | { 52 | "name": "addr", 53 | "type": "address" 54 | } 55 | ], 56 | "name": "upgrade", 57 | "outputs": [], 58 | "payable": false, 59 | "stateMutability": "nonpayable", 60 | "type": "function" 61 | }, 62 | { 63 | "constant": true, 64 | "inputs": [], 65 | "name": "paused", 66 | "outputs": [ 67 | { 68 | "name": "val", 69 | "type": "bool" 70 | } 71 | ], 72 | "payable": false, 73 | "stateMutability": "view", 74 | "type": "function" 75 | }, 76 | { 77 | "constant": false, 78 | "inputs": [], 79 | "name": "pause", 80 | "outputs": [], 81 | "payable": false, 82 | "stateMutability": "nonpayable", 83 | "type": "function" 84 | }, 85 | { 86 | "constant": true, 87 | "inputs": [], 88 | "name": "target", 89 | "outputs": [ 90 | { 91 | "name": "addr", 92 | "type": "address" 93 | } 94 | ], 95 | "payable": false, 96 | "stateMutability": "view", 97 | "type": "function" 98 | }, 99 | { 100 | "inputs": [], 101 | "payable": false, 102 | "stateMutability": "nonpayable", 103 | "type": "constructor" 104 | }, 105 | { 106 | "payable": true, 107 | "stateMutability": "payable", 108 | "type": "fallback" 109 | }, 110 | { 111 | "anonymous": false, 112 | "inputs": [ 113 | { 114 | "indexed": true, 115 | "name": "target", 116 | "type": "address" 117 | } 118 | ], 119 | "name": "Upgraded", 120 | "type": "event" 121 | }, 122 | { 123 | "anonymous": false, 124 | "inputs": [], 125 | "name": "Paused", 126 | "type": "event" 127 | }, 128 | { 129 | "anonymous": false, 130 | "inputs": [], 131 | "name": "Resumed", 132 | "type": "event" 133 | } 134 | ]` 135 | 136 | const OwnerUpgradeableProxyABI = `[ 137 | { 138 | "constant": false, 139 | "inputs": [], 140 | "name": "resume", 141 | "outputs": [], 142 | "payable": false, 143 | "stateMutability": "nonpayable", 144 | "type": "function" 145 | }, 146 | { 147 | "constant": false, 148 | "inputs": [ 149 | { 150 | "name": "target", 151 | "type": "address" 152 | } 153 | ], 154 | "name": "upgrade", 155 | "outputs": [], 156 | "payable": false, 157 | "stateMutability": "nonpayable", 158 | "type": "function" 159 | }, 160 | { 161 | "constant": true, 162 | "inputs": [], 163 | "name": "paused", 164 | "outputs": [ 165 | { 166 | "name": "val", 167 | "type": "bool" 168 | } 169 | ], 170 | "payable": false, 171 | "stateMutability": "view", 172 | "type": "function" 173 | }, 174 | { 175 | "constant": false, 176 | "inputs": [], 177 | "name": "pause", 178 | "outputs": [], 179 | "payable": false, 180 | "stateMutability": "nonpayable", 181 | "type": "function" 182 | }, 183 | { 184 | "constant": true, 185 | "inputs": [], 186 | "name": "owner", 187 | "outputs": [ 188 | { 189 | "name": "addr", 190 | "type": "address" 191 | } 192 | ], 193 | "payable": false, 194 | "stateMutability": "view", 195 | "type": "function" 196 | }, 197 | { 198 | "constant": true, 199 | "inputs": [], 200 | "name": "target", 201 | "outputs": [ 202 | { 203 | "name": "addr", 204 | "type": "address" 205 | } 206 | ], 207 | "payable": false, 208 | "stateMutability": "view", 209 | "type": "function" 210 | }, 211 | { 212 | "inputs": [], 213 | "payable": false, 214 | "stateMutability": "nonpayable", 215 | "type": "constructor" 216 | }, 217 | { 218 | "payable": true, 219 | "stateMutability": "payable", 220 | "type": "fallback" 221 | }, 222 | { 223 | "anonymous": false, 224 | "inputs": [ 225 | { 226 | "indexed": true, 227 | "name": "target", 228 | "type": "address" 229 | } 230 | ], 231 | "name": "Upgraded", 232 | "type": "event" 233 | }, 234 | { 235 | "anonymous": false, 236 | "inputs": [], 237 | "name": "Paused", 238 | "type": "event" 239 | }, 240 | { 241 | "anonymous": false, 242 | "inputs": [], 243 | "name": "Resumed", 244 | "type": "event" 245 | } 246 | ]` 247 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | "sync/atomic" 10 | 11 | "github.com/gochain/gochain/v4/common" 12 | "github.com/gochain/gochain/v4/common/hexutil" 13 | "github.com/gochain/gochain/v4/core/types" 14 | "github.com/gochain/gochain/v4/rpc" 15 | ) 16 | 17 | // Client is an interface for the web3 RPC API. 18 | type Client interface { 19 | // GetBalance returns the balance for an address at the given block number (nil for latest). 20 | GetBalance(ctx context.Context, address string, blockNumber *big.Int) (*big.Int, error) 21 | // GetCode returns the code for an address at the given block number (nil for latest). 22 | GetCode(ctx context.Context, address string, blockNumber *big.Int) ([]byte, error) 23 | // GetBlockByNumber returns block details by number (nil for latest), optionally including full txs. 24 | GetBlockByNumber(ctx context.Context, number *big.Int, includeTxs bool) (*Block, error) 25 | // GetBlockByHash returns block details for the given hash, optionally include full transaction details. 26 | GetBlockByHash(ctx context.Context, hash string, includeTxs bool) (*Block, error) 27 | // GetTransactionByHash returns transaction details for a hash. 28 | GetTransactionByHash(ctx context.Context, hash common.Hash) (*Transaction, error) 29 | // GetSnapshot returns the latest clique snapshot. 30 | GetSnapshot(ctx context.Context) (*Snapshot, error) 31 | // GetID returns unique identifying information for the network. 32 | GetID(ctx context.Context) (*ID, error) 33 | // GetTransactionReceipt returns the receipt for a transaction hash. 34 | GetTransactionReceipt(ctx context.Context, hash common.Hash) (*Receipt, error) 35 | // GetChainID returns the chain id for the network. 36 | GetChainID(ctx context.Context) (*big.Int, error) 37 | // GetNetworkID returns the network id. 38 | GetNetworkID(ctx context.Context) (*big.Int, error) 39 | // GetGasPrice returns a suggested gas price. 40 | GetGasPrice(ctx context.Context) (*big.Int, error) 41 | // GetPendingTransactionCount returns the transaction count including pending txs. 42 | // This value is also the next legal nonce. 43 | GetPendingTransactionCount(ctx context.Context, account common.Address) (uint64, error) 44 | // SendRawTransaction sends the signed raw transaction bytes. 45 | SendRawTransaction(ctx context.Context, tx []byte) error 46 | // Call executes a call without submitting a transaction. 47 | Call(ctx context.Context, msg CallMsg) ([]byte, error) 48 | Close() 49 | SetChainID(*big.Int) 50 | } 51 | 52 | // Dial returns a new client backed by dialing url (supported schemes "http", "https", "ws" and "wss"). 53 | func Dial(url string) (Client, error) { 54 | r, err := rpc.Dial(url) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return NewClient(r), nil 59 | } 60 | 61 | // NewClient returns a new client backed by an existing rpc.Client. 62 | func NewClient(r *rpc.Client) Client { 63 | return &client{r: r} 64 | } 65 | 66 | type client struct { 67 | r *rpc.Client 68 | chainID atomic.Value 69 | } 70 | 71 | func (c *client) Close() { 72 | c.r.Close() 73 | } 74 | 75 | func (c *client) Call(ctx context.Context, msg CallMsg) ([]byte, error) { 76 | var result hexutil.Bytes 77 | err := c.r.CallContext(ctx, &result, "eth_call", toCallArg(msg), "latest") 78 | if err != nil { 79 | return nil, err 80 | } 81 | return result, err 82 | } 83 | 84 | func (c *client) GetBalance(ctx context.Context, address string, blockNumber *big.Int) (*big.Int, error) { 85 | var result hexutil.Big 86 | err := c.r.CallContext(ctx, &result, "eth_getBalance", common.HexToAddress(address), toBlockNumArg(blockNumber)) 87 | return (*big.Int)(&result), err 88 | } 89 | 90 | func (c *client) GetCode(ctx context.Context, address string, blockNumber *big.Int) ([]byte, error) { 91 | var result hexutil.Bytes 92 | err := c.r.CallContext(ctx, &result, "eth_getCode", common.HexToAddress(address), toBlockNumArg(blockNumber)) 93 | return result, err 94 | } 95 | 96 | func (c *client) GetBlockByNumber(ctx context.Context, number *big.Int, includeTxs bool) (*Block, error) { 97 | return c.getBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), includeTxs) 98 | } 99 | 100 | func (c *client) GetBlockByHash(ctx context.Context, hash string, includeTxs bool) (*Block, error) { 101 | return c.getBlock(ctx, "eth_getBlockByHash", hash, includeTxs) 102 | } 103 | 104 | func (c *client) GetTransactionByHash(ctx context.Context, hash common.Hash) (*Transaction, error) { 105 | var tx *Transaction 106 | err := c.r.CallContext(ctx, &tx, "eth_getTransactionByHash", hash.String()) 107 | if err != nil { 108 | return nil, err 109 | } else if tx == nil { 110 | return nil, NotFoundErr 111 | } else if tx.R == nil { 112 | return nil, fmt.Errorf("server returned transaction without signature") 113 | } 114 | return tx, nil 115 | } 116 | 117 | func (c *client) GetSnapshot(ctx context.Context) (*Snapshot, error) { 118 | var s Snapshot 119 | err := c.r.CallContext(ctx, &s, "clique_getSnapshot", "latest") 120 | if err != nil { 121 | return nil, err 122 | } 123 | return &s, nil 124 | } 125 | 126 | func (c *client) GetID(ctx context.Context) (*ID, error) { 127 | var block Block 128 | var netIDStr string 129 | chainID := new(hexutil.Big) 130 | batch := []rpc.BatchElem{ 131 | {Method: "eth_getBlockByNumber", Args: []interface{}{"0x0", false}, Result: &block}, 132 | {Method: "net_version", Result: &netIDStr}, 133 | {Method: "eth_chainId", Result: chainID}, 134 | } 135 | if err := c.r.BatchCallContext(ctx, batch); err != nil { 136 | return nil, err 137 | } 138 | for _, e := range batch { 139 | if e.Error != nil { 140 | log.Printf("Method %q failed: %v\n", e.Method, e.Error) 141 | } 142 | } 143 | netID := new(big.Int) 144 | if _, ok := netID.SetString(netIDStr, 10); !ok { 145 | return nil, fmt.Errorf("invalid net_version result %q", netIDStr) 146 | } 147 | return &ID{NetworkID: netID, ChainID: (*big.Int)(chainID), GenesisHash: block.Hash}, nil 148 | } 149 | 150 | func (c *client) GetNetworkID(ctx context.Context) (*big.Int, error) { 151 | version := new(big.Int) 152 | var ver string 153 | if err := c.r.CallContext(ctx, &ver, "net_version"); err != nil { 154 | return nil, err 155 | } 156 | if _, ok := version.SetString(ver, 10); !ok { 157 | return nil, fmt.Errorf("invalid net_version result %q", ver) 158 | } 159 | return version, nil 160 | } 161 | 162 | func (c *client) SetChainID(chainID *big.Int) { 163 | c.chainID.Store(chainID) 164 | } 165 | 166 | func (c *client) GetChainID(ctx context.Context) (*big.Int, error) { 167 | if l := c.chainID.Load(); l != nil { 168 | if i := l.(*big.Int); i != nil { 169 | return i, nil 170 | } 171 | } 172 | var result hexutil.Big 173 | err := c.r.CallContext(ctx, &result, "eth_chainId") 174 | i := (*big.Int)(&result) 175 | c.SetChainID(i) 176 | return i, err 177 | } 178 | 179 | func (c *client) GetTransactionReceipt(ctx context.Context, hash common.Hash) (*Receipt, error) { 180 | var r *Receipt 181 | err := c.r.CallContext(ctx, &r, "eth_getTransactionReceipt", hash) 182 | if err == nil { 183 | if r == nil { 184 | return nil, NotFoundErr 185 | } 186 | } 187 | return r, err 188 | } 189 | 190 | func (c *client) GetGasPrice(ctx context.Context) (*big.Int, error) { 191 | var hex hexutil.Big 192 | if err := c.r.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { 193 | return nil, err 194 | } 195 | return (*big.Int)(&hex), nil 196 | } 197 | 198 | func (c *client) GetPendingTransactionCount(ctx context.Context, account common.Address) (uint64, error) { 199 | return c.getTransactionCount(ctx, account, "pending") 200 | } 201 | 202 | func (c *client) getTransactionCount(ctx context.Context, account common.Address, blockNumArg string) (uint64, error) { 203 | var result hexutil.Uint64 204 | err := c.r.CallContext(ctx, &result, "eth_getTransactionCount", account, blockNumArg) 205 | return uint64(result), err 206 | } 207 | 208 | func (c *client) SendRawTransaction(ctx context.Context, tx []byte) error { 209 | return c.r.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(tx)) 210 | } 211 | 212 | func (c *client) getBlock(ctx context.Context, method string, hashOrNum string, includeTxs bool) (*Block, error) { 213 | var raw json.RawMessage 214 | err := c.r.CallContext(ctx, &raw, method, hashOrNum, includeTxs) 215 | if err != nil { 216 | return nil, err 217 | } else if len(raw) == 0 { 218 | return nil, NotFoundErr 219 | } 220 | var block Block 221 | if err := json.Unmarshal(raw, &block); err != nil { 222 | return nil, fmt.Errorf("failed to unmarshal json response: %v", err) 223 | } 224 | // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. 225 | if block.Sha3Uncles == types.EmptyUncleHash && len(block.Uncles) > 0 { 226 | return nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles") 227 | } 228 | if block.Sha3Uncles != types.EmptyUncleHash && len(block.Uncles) == 0 { 229 | return nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles") 230 | } 231 | if block.TxsRoot == types.EmptyRootHash && block.TxCount() > 0 { 232 | return nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions") 233 | } 234 | if block.TxsRoot != types.EmptyRootHash && len(block.TxsRoot) == 0 { 235 | return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions") 236 | } 237 | // Load uncles because they are not included in the block response. 238 | var uncles []*types.Header 239 | if len(block.Uncles) > 0 { 240 | uncles = make([]*types.Header, len(block.Uncles)) 241 | reqs := make([]rpc.BatchElem, len(block.Uncles)) 242 | for i := range reqs { 243 | reqs[i] = rpc.BatchElem{ 244 | Method: "eth_getUncleByBlockHashAndIndex", 245 | Args: []interface{}{block.Hash, hexutil.EncodeUint64(uint64(i))}, 246 | Result: &uncles[i], 247 | } 248 | } 249 | if err := c.r.BatchCallContext(ctx, reqs); err != nil { 250 | return nil, err 251 | } 252 | for i := range reqs { 253 | if reqs[i].Error != nil { 254 | return nil, reqs[i].Error 255 | } 256 | if uncles[i] == nil { 257 | return nil, fmt.Errorf("got null header for uncle %d of block %x", i, block.Hash[:]) 258 | } 259 | } 260 | } 261 | return &block, nil 262 | } 263 | 264 | func toBlockNumArg(number *big.Int) string { 265 | if number == nil { 266 | return "latest" 267 | } 268 | return hexutil.EncodeBig(number) 269 | } 270 | 271 | func toCallArg(msg CallMsg) interface{} { 272 | arg := map[string]interface{}{ 273 | "to": msg.To, 274 | } 275 | if msg.From != nil { 276 | arg["from"] = msg.From 277 | } 278 | if len(msg.Data) > 0 { 279 | arg["data"] = hexutil.Bytes(msg.Data) 280 | } 281 | if msg.Value != nil { 282 | arg["value"] = (*hexutil.Big)(msg.Value) 283 | } 284 | if msg.Gas != 0 { 285 | arg["gas"] = hexutil.Uint64(msg.Gas) 286 | } 287 | if msg.GasPrice != nil { 288 | arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) 289 | } 290 | return arg 291 | } 292 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | func ExampleRPCClient_GetBlockByNumber() { 10 | for _, network := range []string{mainnetURL, testnetURL} { 11 | exampleRPCClient_GetBlockByNumber(network) 12 | } 13 | // Output: 14 | // Got ID. 15 | // Got latest block. 16 | // Got genesis block. 17 | // Got latest snapshot. 18 | // Got initial alloc balance. 19 | // Got ID. 20 | // Got latest block. 21 | // Got genesis block. 22 | // Got latest snapshot. 23 | // Got initial alloc balance. 24 | } 25 | 26 | func exampleRPCClient_GetBlockByNumber(url string) { 27 | c, err := Dial(url) 28 | if err != nil { 29 | fmt.Printf("Failed to connect to network %q: %v\n", url, err) 30 | return 31 | } 32 | defer c.Close() 33 | 34 | ctx := context.Background() 35 | id, err := c.GetID(ctx) 36 | if err != nil { 37 | fmt.Printf("Failed to get id: %v\n", err) 38 | } 39 | if id == nil { 40 | fmt.Println("ID nil.") 41 | } else { 42 | fmt.Println("Got ID.") 43 | } 44 | 45 | bl, err := c.GetBlockByNumber(ctx, nil, false) 46 | if err != nil { 47 | fmt.Printf("Failed to get latest block: %v", err) 48 | } 49 | if bl == nil { 50 | fmt.Println("Latest block nil.") 51 | } else { 52 | fmt.Println("Got latest block.") 53 | } 54 | 55 | bl, err = c.GetBlockByNumber(ctx, big.NewInt(0), false) 56 | if err != nil { 57 | fmt.Printf("Failed to get genesis block: %v", err) 58 | } 59 | if bl == nil { 60 | fmt.Println("Genesis block nil.") 61 | } else { 62 | fmt.Println("Got genesis block.") 63 | } 64 | 65 | sn, err := c.GetSnapshot(ctx) 66 | if err != nil { 67 | fmt.Printf("Failed to get snapshot: %v\n", err) 68 | } 69 | if sn == nil { 70 | fmt.Println("Latest snapshot nil.") 71 | } else { 72 | fmt.Println("Got latest snapshot.") 73 | } 74 | 75 | initAlloc, ok := new(big.Int).SetString("1000000000000000000000000000", 10) 76 | if !ok { 77 | panic("failed to parse big.Int string") 78 | } 79 | bal, err := c.GetBalance(ctx, testAddr(url), big.NewInt(0)) 80 | if err != nil { 81 | fmt.Printf("Failed to get balance: %v\n", err) 82 | } 83 | if bal == nil { 84 | fmt.Println("Initial alloc balance nil.") 85 | } else if bal.Cmp(initAlloc) != 0 { 86 | fmt.Printf("Unexpected initial alloc balance %s, wanted %s\n", bal, initAlloc) 87 | } else { 88 | fmt.Println("Got initial alloc balance.") 89 | } 90 | } 91 | 92 | func testAddr(network string) string { 93 | switch network { 94 | case mainnetURL: 95 | return "0xf75b6e2d2d69da07f2940e239e25229350f8103f" 96 | case testnetURL: 97 | return "0x2fe70f1df222c85ad6dd24a3376eb5ac32136978" 98 | default: 99 | panic("unsupported network: " + network) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cmd/web3/contracts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "time" 8 | 9 | "github.com/gochain/gochain/v4/accounts/abi" 10 | "github.com/gochain/web3" 11 | ) 12 | func ListContract(contractFile string) { 13 | myabi, err := web3.GetABI(contractFile) 14 | if err != nil { 15 | fatalExit(err) 16 | } 17 | switch format { 18 | case "json": 19 | fmt.Println(marshalJSON(myabi.Methods)) 20 | return 21 | } 22 | 23 | for _, method := range myabi.Methods { 24 | fmt.Println(method) 25 | } 26 | } 27 | 28 | func GetContractConst(ctx context.Context, rpcURL, contractAddress, contractFile, functionName string, parameters ...interface{}) ([]interface{}, error) { 29 | client, err := web3.Dial(rpcURL) 30 | if err != nil { 31 | return nil, fmt.Errorf("Failed to connect to %q: %v", rpcURL, err) 32 | } 33 | defer client.Close() 34 | myabi, err := web3.GetABI(contractFile) 35 | if err != nil { 36 | return nil, err 37 | } 38 | fn, ok := myabi.Methods[functionName] 39 | if !ok { 40 | return nil, fmt.Errorf("There is no such function: %v", functionName) 41 | } 42 | if !fn.IsConstant() { 43 | return nil, err 44 | } 45 | res, err := web3.CallConstantFunction(ctx, client, *myabi, contractAddress, functionName, parameters...) 46 | if err != nil { 47 | return nil, fmt.Errorf("Error calling constant function: %v", err) 48 | } 49 | return res, nil 50 | } 51 | 52 | func callContract(ctx context.Context, client web3.Client, privateKey, contractAddress, abiFile, functionName string, 53 | amount *big.Int, gasPrice *big.Int, gasLimit uint64, waitForReceipt, toString bool, data []byte, timeoutInSeconds uint64, parameters ...interface{}) { 54 | 55 | var err error 56 | var tx *web3.Transaction 57 | var myabi *abi.ABI 58 | if len(data) > 0 { 59 | tx, err = web3.CallFunctionWithData(ctx, client, privateKey, contractAddress, amount, gasPrice, gasLimit, data) 60 | } else { 61 | // var m abi.Method 62 | myabi, err = web3.GetABI(abiFile) 63 | if err != nil { 64 | fatalExit(err) 65 | } 66 | ok := true 67 | m, ok := myabi.Methods[functionName] 68 | if !ok { 69 | fmt.Println("There is no such function:", functionName) 70 | return 71 | } 72 | 73 | if m.IsConstant() { 74 | res, err := web3.CallConstantFunction(ctx, client, *myabi, contractAddress, functionName, parameters...) 75 | if err != nil { 76 | fatalExit(fmt.Errorf("Error calling constant function: %v", err)) 77 | } 78 | switch format { 79 | case "json": 80 | m := make(map[string]interface{}) 81 | if len(res) == 1 { 82 | m["response"] = res[0] 83 | } else { 84 | m["response"] = res 85 | } 86 | fmt.Println(marshalJSON(m)) 87 | return 88 | } 89 | if toString { 90 | for i := range res { 91 | fmt.Printf("%s\n", res[i]) 92 | } 93 | return 94 | } 95 | for _, r := range res { 96 | // These explicit checks ensure we get hex encoded output. 97 | if s, ok := r.(fmt.Stringer); ok { 98 | r = s.String() 99 | } 100 | fmt.Println(r) 101 | } 102 | return 103 | } 104 | tx, err = web3.CallTransactFunction(ctx, client, *myabi, contractAddress, privateKey, functionName, amount, gasPrice, gasLimit, parameters...) 105 | } 106 | if err != nil { 107 | fatalExit(fmt.Errorf("Error calling contract: %v", err)) 108 | } 109 | fmt.Println("Transaction hash:", tx.Hash.Hex()) 110 | if !waitForReceipt { 111 | return 112 | } 113 | fmt.Println("Waiting for receipt...") 114 | ctx, _ = context.WithTimeout(ctx, time.Duration(timeoutInSeconds)*time.Second) 115 | receipt, err := web3.WaitForReceipt(ctx, client, tx.Hash) 116 | if err != nil { 117 | fatalExit(fmt.Errorf("getting receipt: %v", err)) 118 | } 119 | printReceiptDetails(receipt, myabi) 120 | } 121 | -------------------------------------------------------------------------------- /cmd/web3/did.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "math/big" 10 | "net/http" 11 | "os" 12 | "sort" 13 | "strings" 14 | "time" 15 | 16 | "github.com/gochain/gochain/v4/core/types" 17 | 18 | "github.com/gochain/gochain/v4/accounts/abi" 19 | "github.com/gochain/gochain/v4/common" 20 | "github.com/gochain/gochain/v4/crypto" 21 | "github.com/gochain/web3" 22 | "github.com/gochain/web3/assets" 23 | "github.com/gochain/web3/did" 24 | "github.com/gochain/web3/vc" 25 | "golang.org/x/crypto/sha3" 26 | ) 27 | 28 | // MaxDIDLength is the maximum size of the idstring of the GoChain DID. 29 | const MaxDIDLength = 32 30 | 31 | func CreateDID(ctx context.Context, rpcURL string, chainID *big.Int, privateKey, id, registryAddress string, timeoutInSeconds uint64) { 32 | if registryAddress == "" { 33 | log.Fatalf("Registry contract address required") 34 | } else if id == "" { 35 | log.Fatalf("DID required") 36 | } 37 | 38 | d, err := did.Parse(id) 39 | if err != nil { 40 | log.Fatalf("Invalid DID: %s", err) 41 | } else if d.Method != "go" { 42 | log.Fatalf("Only 'go' DID methods can be registered.") 43 | } else if len(id) > MaxDIDLength { 44 | log.Fatalf("ID must be less than 32 characters") 45 | } 46 | 47 | // Parse key. 48 | acc, err := web3.ParsePrivateKey(privateKey) 49 | if err != nil { 50 | log.Fatalf("Cannot parse private key: %s", err) 51 | } 52 | publicKey := acc.Key().PublicKey 53 | 54 | // Build DID identifier. 55 | publicKeyID := *d 56 | publicKeyID.Fragment = "owner" 57 | 58 | // Build DID document. 59 | now := time.Now() 60 | doc := did.NewDocument() 61 | doc.ID = d.String() 62 | doc.Created = &now 63 | doc.Updated = &now 64 | doc.PublicKeys = []did.PublicKey{{ 65 | ID: publicKeyID.String(), 66 | Type: "Secp256k1VerificationKey2018", 67 | Controller: d.String(), 68 | PublicKeyHex: common.ToHex(crypto.FromECDSAPub(&publicKey)), 69 | }} 70 | doc.Authentications = []interface{}{publicKeyID.String()} 71 | 72 | // Pretty print document. 73 | data, err := json.MarshalIndent(doc, "", "\t") 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | 78 | // Upload to IPFS. 79 | hash, err := IPFSUpload(ctx, "did.json", data) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | 84 | client, err := web3.Dial(rpcURL) 85 | if err != nil { 86 | log.Fatalf("Failed to connect to %q: %v", rpcURL, err) 87 | } 88 | client.SetChainID(chainID) 89 | defer client.Close() 90 | 91 | myabi, err := abi.JSON(strings.NewReader(assets.DIDRegistryABI)) 92 | if err != nil { 93 | log.Fatalf("Cannot initialize DIDRegistry ABI: %v", err) 94 | } 95 | 96 | var idBytes32 [32]byte 97 | copy(idBytes32[:], d.ID) 98 | 99 | tx, err := web3.CallTransactFunction(ctx, client, myabi, registryAddress, privateKey, "register", &big.Int{}, nil, 70000, idBytes32, hash) 100 | if err != nil { 101 | log.Fatalf("Cannot register DID identifier: %v", err) 102 | } 103 | 104 | ctx, _ = context.WithTimeout(ctx, time.Duration(timeoutInSeconds)*time.Second) 105 | receipt, err := web3.WaitForReceipt(ctx, client, tx.Hash) 106 | if err != nil { 107 | log.Fatalf("Cannot get the receipt for transaction with hash '%v': %v", tx.Hash.Hex(), err) 108 | } 109 | 110 | if receipt.Status != types.ReceiptStatusSuccessful { 111 | fatalExit(fmt.Errorf("DID contract call failed: %s", tx.Hash.Hex())) 112 | } 113 | 114 | fmt.Println("Successfully registered DID:", d.String()) 115 | fmt.Println("DID Document IPFS Hash:", hash) 116 | fmt.Println("Transaction address:", receipt.TxHash.Hex()) 117 | } 118 | 119 | func DIDOwner(ctx context.Context, rpcURL, privateKey, id, registryAddress string) { 120 | if registryAddress == "" { 121 | log.Fatalf("Registry contract address required") 122 | } 123 | 124 | d, err := did.Parse(id) 125 | if err != nil { 126 | log.Fatalf("Invalid DID: %s", err) 127 | } 128 | 129 | client, err := web3.Dial(rpcURL) 130 | if err != nil { 131 | log.Fatalf("Failed to connect to %q: %v", rpcURL, err) 132 | } 133 | defer client.Close() 134 | 135 | myabi, err := abi.JSON(strings.NewReader(assets.DIDRegistryABI)) 136 | if err != nil { 137 | log.Fatalf("Cannot initialize DIDRegistry ABI: %v", err) 138 | } 139 | 140 | var idBytes32 [32]byte 141 | copy(idBytes32[:], d.ID) 142 | 143 | result, err := web3.CallConstantFunction(ctx, client, myabi, registryAddress, "owner", idBytes32) 144 | if err != nil { 145 | log.Fatalf("Cannot call the contract: %v", err) 146 | } 147 | if len(result) != 1 { 148 | log.Fatalf("Expected single result but got: %v", result) 149 | } 150 | address := result[0].(common.Address) 151 | fmt.Println(address.Hex()) 152 | } 153 | 154 | func DIDHash(ctx context.Context, rpcURL, privateKey, id, registryAddress string) { 155 | if registryAddress == "" { 156 | log.Fatalf("Registry contract address required") 157 | } 158 | 159 | d, err := did.Parse(id) 160 | if err != nil { 161 | log.Fatalf("Invalid DID: %s", id) 162 | } 163 | 164 | client, err := web3.Dial(rpcURL) 165 | if err != nil { 166 | log.Fatalf("Failed to connect to %q: %v", rpcURL, err) 167 | } 168 | defer client.Close() 169 | 170 | myabi, err := abi.JSON(strings.NewReader(assets.DIDRegistryABI)) 171 | if err != nil { 172 | log.Fatalf("Cannot initialize DIDRegistry ABI: %v", err) 173 | } 174 | 175 | var idBytes32 [32]byte 176 | copy(idBytes32[:], d.ID) 177 | 178 | result, err := web3.CallConstantFunction(ctx, client, myabi, registryAddress, "hash", idBytes32) 179 | if err != nil { 180 | log.Fatalf("Cannot call the contract: %v", err) 181 | } 182 | if len(result) != 1 { 183 | log.Fatalf("Expected single result but got: %v", result) 184 | } 185 | hash := result[0].(string) 186 | fmt.Println(hash) 187 | } 188 | 189 | func ShowDID(ctx context.Context, rpcURL, privateKey, id, registryAddress string) { 190 | // Read current DID document for ID from IPFS. 191 | doc, err := readDIDDocument(ctx, rpcURL, registryAddress, id) 192 | if err != nil { 193 | log.Fatal(err) 194 | } 195 | 196 | // Pretty print document. 197 | data, err := json.MarshalIndent(doc, "", "\t") 198 | if err != nil { 199 | log.Fatal(err) 200 | } 201 | fmt.Println(string(data)) 202 | } 203 | 204 | func SignClaim(ctx context.Context, rpcURL, privateKey, id, typ, issuerID, subjectID, subjectJSON string) { 205 | if id == "" { 206 | log.Fatalf("Credential ID required") 207 | } else if typ == "" { 208 | log.Fatalf("Credential type required") 209 | } 210 | if issuerID == "" { 211 | log.Fatalf("Credential issuer DID required") 212 | } else if _, err := did.Parse(issuerID); err != nil { 213 | log.Fatalf("Invalid credential issuer DID: %s", err) 214 | } 215 | if subjectID == "" { 216 | log.Fatalf("Credential subject DID required") 217 | } else if _, err := did.Parse(subjectID); err != nil { 218 | log.Fatalf("Invalid credential subject DID: %s", err) 219 | } 220 | 221 | // Parse key. 222 | acc, err := web3.ParsePrivateKey(privateKey) 223 | if err != nil { 224 | log.Fatalf("Cannot parse private key: %s", err) 225 | } 226 | 227 | // Parse subject object. 228 | subject := make(map[string]interface{}) 229 | if subjectJSON != "" { 230 | if err := json.Unmarshal([]byte(subjectJSON), &subject); err != nil { 231 | log.Fatalf("Cannot parse subject JSON data: %s", err) 232 | } 233 | } 234 | subject["id"] = subjectID 235 | 236 | // Store current time to the second. 237 | now := time.Now().UTC().Truncate(1 * time.Second) 238 | 239 | // Build verifiable credential. 240 | cred := vc.NewVerifiableCredential() 241 | cred.ID = id 242 | cred.Type = append(cred.Type, typ) 243 | cred.Issuer = issuerID 244 | cred.IssuanceDate = &now 245 | cred.CredentialSubject = subject 246 | 247 | // Marshal data without proof. 248 | hw := sha3.NewLegacyKeccak256() 249 | if err := json.NewEncoder(hw).Encode(cred); err != nil { 250 | log.Fatalf("Cannot marshal credential to JSON: %s", err) 251 | } 252 | 253 | // Sign hash of credential document. 254 | var h common.Hash 255 | hw.Sum(h[:0]) 256 | proofValue, err := crypto.Sign(h[:], acc.Key()) 257 | if err != nil { 258 | log.Fatalf("Cannot sign credential: %s", err) 259 | } 260 | 261 | // Trim "V" off end of proof value. 262 | proofValue = proofValue[:len(proofValue)-1] 263 | 264 | // Add proof to credential. 265 | cred.Proof = &vc.Proof{ 266 | Type: "Secp256k1VerificationKey2018", 267 | Created: &now, 268 | ProofValue: common.Bytes2Hex(proofValue), 269 | } 270 | 271 | // Pretty print credential. 272 | output, err := json.MarshalIndent(cred, "", "\t") 273 | if err != nil { 274 | log.Fatal(err) 275 | } 276 | fmt.Println(string(output)) 277 | } 278 | 279 | func VerifyClaim(ctx context.Context, rpcURL, privateKey, registryAddress, filename string) { 280 | // Decode file into VerifiableCredential. 281 | var cred vc.VerifiableCredential 282 | if buf, err := ioutil.ReadFile(filename); err != nil { 283 | log.Fatalf("Cannot read file: %s", err) 284 | } else if err := json.Unmarshal(buf, &cred); err != nil { 285 | log.Fatalf("Cannot decode credential: %s", err) 286 | } 287 | 288 | // Read issuer DID document. 289 | doc, err := readDIDDocument(ctx, rpcURL, registryAddress, cred.Issuer) 290 | if err != nil { 291 | log.Fatalf("Cannot read issuer DID document: %s", err) 292 | } 293 | 294 | // Encode credential to JSON without proof to generate hash. 295 | other := cred // shallow copy 296 | other.Proof = nil 297 | hw := sha3.NewLegacyKeccak256() 298 | if err := json.NewEncoder(hw).Encode(other); err != nil { 299 | log.Fatalf("Cannot hash claim: %s", err) 300 | } 301 | var h common.Hash 302 | hw.Sum(h[:0]) 303 | 304 | // Attempt verification against each of issuer's public keys. 305 | // Only Secp256k1 is currently supported. 306 | var verified bool 307 | for _, pub := range doc.PublicKeys { 308 | if pub.Type != "Secp256k1VerificationKey2018" { 309 | continue 310 | } 311 | 312 | pubkey := common.Hex2Bytes(strings.TrimPrefix(pub.PublicKeyHex, "0x")) 313 | if crypto.VerifySignature(pubkey, h[:], common.Hex2Bytes(cred.Proof.ProofValue)) { 314 | verified = true 315 | break 316 | } 317 | } 318 | 319 | // Display error if no keys can verify the signature. 320 | if !verified { 321 | fmt.Println("Status: NOT VERIFIED") 322 | os.Exit(1) 323 | } 324 | 325 | // Extract subject & extract ID. 326 | subject := cred.CredentialSubject 327 | if subject == nil { 328 | subject = make(map[string]interface{}) 329 | } 330 | subjectID := subject["id"] 331 | delete(subject, "id") 332 | 333 | // Sort subject keys. 334 | keys := make([]string, 0, len(subject)) 335 | for k := range subject { 336 | keys = append(keys, k) 337 | } 338 | sort.Strings(keys) 339 | 340 | // Display credential info on success. 341 | 342 | fmt.Printf("ID: %s\n", cred.ID) 343 | fmt.Printf("Type: %s\n", strings.Join(cred.Type, ", ")) 344 | fmt.Println("Status: VERIFIED") 345 | fmt.Println("") 346 | 347 | fmt.Printf("Subject: %s\n", subjectID) 348 | fmt.Printf("Issuer: %s\n", cred.Issuer) 349 | fmt.Printf("Issued On: %s\n", cred.IssuanceDate) 350 | fmt.Println("") 351 | 352 | if len(keys) != 0 { 353 | fmt.Println("CLAIMS:") 354 | for _, k := range keys { 355 | fmt.Printf("%s: %v\n", k, subject[k]) 356 | } 357 | fmt.Println("") 358 | } 359 | } 360 | 361 | func readDIDDocument(ctx context.Context, rpcURL, registryAddress, id string) (*did.Document, error) { 362 | if registryAddress == "" { 363 | return nil, fmt.Errorf("Registry contract address required") 364 | } 365 | 366 | d, err := did.Parse(id) 367 | if err != nil { 368 | return nil, fmt.Errorf("Invalid DID: %s", id) 369 | } 370 | 371 | client, err := web3.Dial(rpcURL) 372 | if err != nil { 373 | return nil, fmt.Errorf("Failed to connect to %q: %v", rpcURL, err) 374 | } 375 | defer client.Close() 376 | 377 | myabi, err := abi.JSON(strings.NewReader(assets.DIDRegistryABI)) 378 | if err != nil { 379 | return nil, fmt.Errorf("Cannot initialize DIDRegistry ABI: %v", err) 380 | } 381 | 382 | var idBytes32 [32]byte 383 | copy(idBytes32[:], d.ID) 384 | 385 | result, err := web3.CallConstantFunction(ctx, client, myabi, registryAddress, "hash", idBytes32) 386 | if err != nil { 387 | return nil, fmt.Errorf("Cannot call the contract: %v", err) 388 | } 389 | if len(result) != 1 { 390 | log.Fatalf("Expected single result but got: %v", result) 391 | } 392 | 393 | hash := result[0].(string) 394 | resp, err := http.Get(fmt.Sprintf("https://ipfs.infura.io:5001/api/v0/cat?arg=%s", hash)) 395 | if err != nil { 396 | return nil, fmt.Errorf("Unable to fetch DID document from IPFS: %s", err) 397 | } 398 | defer resp.Body.Close() 399 | 400 | var doc did.Document 401 | if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil { 402 | return nil, fmt.Errorf("Unable to decode DID document: %s", err) 403 | } 404 | return &doc, nil 405 | } 406 | -------------------------------------------------------------------------------- /cmd/web3/flatten.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | type importRec struct { 14 | FullPath string 15 | Code []string 16 | Uses map[string]bool 17 | Created bool // has been depended on or processed 18 | Processed bool // has been processed for includes 19 | Resolved bool // all includes have been resolved 20 | Written bool // written out 21 | } 22 | 23 | func extractFilePath(line string) string { 24 | line = strings.Replace(line, "import ", "", 2) 25 | line = strings.Replace(line, "\"", "", 2) 26 | line = strings.Replace(line, "'", "", 2) 27 | line = strings.Replace(line, ";", "", 2) 28 | line = strings.TrimSpace(filepath.Clean(line)) 29 | return line 30 | } 31 | 32 | func loadAndSplitFile(imports map[string]importRec, fileName string) (name string, newFiles bool, openZeppelinVersion, pragma string, err error) { 33 | thisPath := filepath.Dir(fileName) 34 | shortName := filepath.Base(fileName) 35 | if imports[shortName].Processed { 36 | return 37 | } 38 | thisRec := importRec{FullPath: fileName, Created: true, Uses: make(map[string]bool)} 39 | data, err := ioutil.ReadFile(fileName) 40 | contents := string(data) 41 | lines := strings.Split(contents, "\n") 42 | noImports := true 43 | for li, line := range lines { 44 | if strings.Contains(line, "@openzeppelin") { 45 | // get openzep version 46 | ozi := strings.Index(line, "@openzeppelin") 47 | vi := strings.Index(line[ozi:], " v") 48 | openZeppelinVersion = line[ozi+vi+2:] 49 | // fmt.Println("VERSION:", openZeppelinVersion) 50 | } 51 | if strings.HasPrefix(line, "pragma solidity") { 52 | pragma = line 53 | continue 54 | } 55 | if strings.HasPrefix(line, "import") { 56 | noImports = false 57 | fpath := thisPath + "/" + extractFilePath(line) 58 | fname := filepath.Base(fpath) 59 | if !imports[fname].Created { 60 | newFiles = true 61 | imports[fname] = importRec{ 62 | FullPath: fpath, 63 | Created: true, 64 | Uses: make(map[string]bool), 65 | } 66 | } 67 | thisRec.Uses[fname] = true 68 | } 69 | if strings.HasPrefix(line, "contract") { 70 | // grab name 71 | s := strings.Split(line, " ") 72 | name = s[1] 73 | } 74 | if strings.HasPrefix(line, "abstract contract") || strings.HasPrefix(line, "contract") || strings.HasPrefix(line, "library") || strings.HasPrefix(line, "interface") { 75 | thisRec.Code = lines[li:] 76 | break 77 | } 78 | } 79 | thisRec.Processed = true 80 | imports[shortName] = thisRec 81 | thisRec.Resolved = noImports 82 | return 83 | } 84 | 85 | // FlattenSourceFile flattens the source solidity file, but only if it has imports. 86 | // The flattened file will be generated at output, or in the current directory 87 | // as _flatten.sol. 88 | func FlattenSourceFile(ctx context.Context, source, output string) (string, string, error) { 89 | if output == "" { 90 | basename := filepath.Base(source) 91 | output = strings.TrimSuffix(basename, filepath.Ext(basename)) + "_flatten.sol" 92 | } 93 | if _, err := os.Stat(source); err != nil { 94 | return "", "", fmt.Errorf("failed to find source file: %v", err) 95 | } 96 | imports := make(map[string]importRec) 97 | name, newFiles, openZeppelinVersion, pragma, err := loadAndSplitFile(imports, source) 98 | if err != nil { 99 | return "", "", err 100 | } 101 | if newFiles { //file has imports 102 | err = getOpenZeppelinLib(ctx, openZeppelinVersion) 103 | if err != nil { 104 | return name, "", fmt.Errorf("failed to get openzeppelin lib: %v", err) 105 | } 106 | if err := os.MkdirAll(filepath.Dir(output), 0777); err != nil { 107 | return name, "", fmt.Errorf("failed to make parent directories: %v", err) 108 | } 109 | f, _ := os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) 110 | defer f.Close() 111 | w := bufio.NewWriter(f) 112 | for { 113 | repeat := false 114 | for _, iRec := range imports { 115 | if iRec.Processed { 116 | continue 117 | } 118 | // fmt.Println("handling:", iRec.FullPath) 119 | _, newFiles2, _, _, err2 := loadAndSplitFile(imports, iRec.FullPath) 120 | if err2 != nil { 121 | return name, output, err2 122 | } 123 | repeat = repeat || newFiles2 124 | } 125 | if !repeat { 126 | break 127 | } 128 | } 129 | fmt.Fprintln(w, pragma) 130 | for { 131 | completed := true 132 | for key, mp := range imports { 133 | if mp.Written { 134 | continue 135 | } 136 | completed = false 137 | if mp.Resolved { 138 | for _, line := range mp.Code { 139 | fmt.Fprintln(w, line) 140 | } 141 | mp.Written = true 142 | imports[key] = mp 143 | continue 144 | } 145 | amResolved := true 146 | for k2 := range mp.Uses { 147 | if !imports[filepath.Base(k2)].Written { 148 | amResolved = false 149 | } 150 | } 151 | if amResolved { 152 | mp.Resolved = true 153 | imports[key] = mp 154 | continue 155 | } 156 | } 157 | if completed { 158 | break 159 | } 160 | } 161 | return name, output, w.Flush() 162 | } //file doesn't have any imports, so just return the same file 163 | return name, source, err 164 | 165 | } 166 | -------------------------------------------------------------------------------- /cmd/web3/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "html/template" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "os/exec" 13 | 14 | "github.com/gochain/gochain/v4/accounts/abi/bind" 15 | "github.com/gochain/web3/assets" 16 | "github.com/gochain/web3/vyper" 17 | "github.com/urfave/cli" 18 | ) 19 | 20 | const ( 21 | OpenZeppelinVersion = "4.6.0" 22 | ) 23 | 24 | func GenerateCode(ctx context.Context, c *cli.Context) { 25 | var lang bind.Lang 26 | switch c.String("lang") { 27 | case "go": 28 | lang = bind.LangGo 29 | case "java": 30 | lang = bind.LangJava 31 | case "objc": 32 | lang = bind.LangObjC 33 | default: 34 | fatalExit(fmt.Errorf("Unsupported destination language: %v", lang)) 35 | } 36 | 37 | abiFile := c.String("abi") 38 | 39 | if abiFile == "" { 40 | fatalExit(errors.New("Please set the ABI file name")) 41 | } 42 | 43 | abi, err := ioutil.ReadFile(abiFile) 44 | if err != nil { 45 | fatalExit(fmt.Errorf("Failed to read file %q: %v", abiFile, err)) 46 | } 47 | 48 | abis := []string{string(abi)} 49 | bins := []string{c.String("")} 50 | types := []string{c.String("pkg")} 51 | 52 | code, err := bind.Bind(types, abis, bins, nil, c.String("pkg"), lang, nil, nil) 53 | if err != nil { 54 | fatalExit(fmt.Errorf("Failed to generate ABI binding %q: %v", abiFile, err)) 55 | } 56 | outFile := c.String("out") 57 | 58 | if err := ioutil.WriteFile(outFile, []byte(code), 0600); err != nil { 59 | fatalExit(fmt.Errorf("Failed to write ABI binding %q: %v", abiFile, err)) 60 | } 61 | fmt.Println("The generated code has been successfully written to", outFile, "file") 62 | } 63 | 64 | func getOpenZeppelinLib(ctx context.Context, version string) error { 65 | if _, err := os.Stat("lib/oz"); os.IsNotExist(err) { 66 | if version == "" { 67 | version = OpenZeppelinVersion 68 | } 69 | cmd := exec.Command("git", "clone", "--depth", "1", "--branch", "v"+version, "https://github.com/OpenZeppelin/openzeppelin-contracts", "lib/oz") 70 | log.Printf("Cloning OpenZeppelin v%v...", version) 71 | cmd.Stdout = os.Stdout 72 | cmd.Stderr = os.Stderr 73 | err := cmd.Run() 74 | if err != nil { 75 | fatalExit(fmt.Errorf("Cloning finished with error: %v", err)) 76 | } 77 | err = os.RemoveAll("lib/oz/.git") 78 | if err != nil { 79 | fatalExit(fmt.Errorf("Cannot cleanup .git dir in lib/oz: %v", err)) 80 | } 81 | } 82 | return nil 83 | } 84 | 85 | // TODO return the vyper contract for ERC020 86 | func GenerateVyperContract(contractName string) { 87 | cntr := vyper.GenerateVyperERC20Token() 88 | vyper.WriteToFile(contractName, cntr) 89 | fmt.Println(cntr) 90 | 91 | } 92 | 93 | func GenerateContract(ctx context.Context, contractType string, c *cli.Context) { 94 | if contractType != "erc20-vyper" { 95 | if c.String("symbol") == "" { 96 | fatalExit(errors.New("symbol is required")) 97 | } 98 | if c.String("name") == "" { 99 | fatalExit(errors.New("name is required")) 100 | } 101 | if c.String("symbol") == "" { 102 | fatalExit(errors.New("symbol is required")) 103 | } 104 | if c.String("name") == "" { 105 | fatalExit(errors.New("name is required")) 106 | } 107 | } 108 | err := getOpenZeppelinLib(ctx, OpenZeppelinVersion) 109 | if err != nil { 110 | fatalExit(err) 111 | } 112 | if contractType == "erc20" { 113 | // var capped *big.Int 114 | // decimals := c.Int("decimals") 115 | // if decimals <= 0 { 116 | // fatalExit(errors.New("Decimals should be greater than 0")) 117 | // } 118 | // if c.String("capped") != "" { 119 | // var ok bool 120 | // capped, ok = new(big.Int).SetString(c.String("capped"), 10) 121 | // if !ok { 122 | // fatalExit(errors.New("Cannot parse capped value")) 123 | // } 124 | // if capped.Cmp(big.NewInt(0)) < 1 { 125 | // fatalExit(errors.New("Capped should be greater than 0")) 126 | // } 127 | // capped.Mul(capped, new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)) 128 | // } 129 | params := assets.Erc20Params{ 130 | Symbol: c.String("symbol"), 131 | TokenName: c.String("name"), 132 | // Cap: capped, 133 | // Pausable: c.Bool("pausable"), 134 | // Mintable: c.Bool("mintable"), 135 | // Burnable: c.Bool("burnable"), 136 | // Decimals: decimals, 137 | } 138 | // TODO: add initial-supply flag 139 | // TODO: must have initial supply or be mintable, otherwise this is zero 140 | // TODO: initial supply can be set in constructor given to owner, eg: _mint(msg.sender, initialSupply) 141 | s, err := assets.GenERC20(ctx, OpenZeppelinVersion, ¶ms) 142 | if err != nil { 143 | fatalExit(err) 144 | } 145 | writeStringToFile(s, params.Symbol) 146 | } else if contractType == "erc721" { 147 | // we're going to assume metadata 148 | params := assets.Erc721Params{ 149 | Symbol: c.String("symbol"), 150 | ContractName: assets.EscapeName(c.String("symbol")), 151 | TokenName: c.String("name"), 152 | BaseURI: c.String("base-uri"), 153 | // Pausable: c.Bool("pausable"), 154 | // Mintable: c.Bool("mintable"), 155 | // Burnable: c.Bool("burnable"), 156 | } 157 | processTemplate(OpenZeppelinVersion, params, params.Symbol, assets.ERC721Template) 158 | } else if contractType == "erc20-vyper" { 159 | GenerateVyperContract(c.String("symbol")) 160 | //fmt.Println("You selected an erc20 token in Vyper.") 161 | } 162 | } 163 | 164 | func processTemplate(openZeppelinVersion string, params interface{}, fileName, contractTemplate string) { 165 | tmpl, err := template.New("contract").Parse(contractTemplate) 166 | if err != nil { 167 | fatalExit(fmt.Errorf("Cannot parse the template: %v", err)) 168 | } 169 | var buff bytes.Buffer 170 | err = tmpl.Execute(&buff, params) 171 | if err != nil { 172 | fatalExit(fmt.Errorf("Cannot execute the template: %v", err)) 173 | } 174 | s := fmt.Sprintf("// @openzeppelin v%v\n", openZeppelinVersion) 175 | s += buff.String() 176 | writeStringToFile(s, fileName) 177 | } 178 | func writeStringToFile(s, fileName string) { 179 | err := ioutil.WriteFile(fileName+".sol", []byte(s), 0666) 180 | if err != nil { 181 | fatalExit(fmt.Errorf("Cannot create the file: %v", err)) 182 | } 183 | fmt.Println("The sample contract has been successfully written to", fileName+".sol", "file") 184 | } 185 | -------------------------------------------------------------------------------- /cmd/web3/start.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/gochain/web3" 10 | "github.com/urfave/cli" 11 | ) 12 | 13 | func start(ctx context.Context, c *cli.Context) error { 14 | privateKey := c.String("private-key") 15 | var acc *web3.Account 16 | var err error 17 | if privateKey == "" { 18 | acc, err = web3.CreateAccount() 19 | if err != nil { 20 | return err 21 | } 22 | } else { 23 | acc, err = web3.ParsePrivateKey(privateKey) 24 | if err != nil { 25 | return err 26 | } 27 | } 28 | 29 | // var dDir string 30 | // home := config.GetHomeDir() 31 | // if c.String("data-dir") != "" { 32 | // dDir = c.String("data-dir") 33 | // } else { 34 | // dDir = filepath.Join(home, ".fn") 35 | // } 36 | 37 | // check if the container already exists 38 | // docker ps -a --filter name=gochain --format "{{.Names}}" 39 | cmd := exec.CommandContext(ctx, "docker", "ps", "-a", "--filter", "name=gochain", "--format", "{{.Names}}") 40 | stdoutStderr, err := cmd.CombinedOutput() 41 | if err != nil { 42 | fatalExit(err) 43 | } 44 | if len(stdoutStderr) != 0 { 45 | // then already exists, so just start it again 46 | fmt.Println("Restarting existing container 'gochain'...") 47 | cmd = exec.CommandContext(ctx, "docker", "start", "gochain") 48 | } else { 49 | args := []string{"run", 50 | // todo: // should use the `--rm` flag if we allow user to mount a local data dir 51 | // It's a much better experience than having to do docker rm or switch to docker start. 52 | // We could also check to see if the container exists and if it does, automatically do a `start` rather than a `run` 53 | // "--rm", 54 | // "-v", fmt.Sprintf("%s/data:/app/data", dDir), 55 | "-i", 56 | "--name", "gochain", 57 | "-v", "/var/run/docker.sock:/var/run/docker.sock", 58 | // "--privileged", // if we to run docker-in-docker 59 | "-p", fmt.Sprintf("%d:8545", 8545), // fmt'd so we an let use pass these in 60 | "-p", fmt.Sprintf("%d:8546", 8546), 61 | "--entrypoint", "gochain", 62 | } 63 | // if c.String("log-level") != "" { 64 | // args = append(args, "-e", fmt.Sprintf("FN_LOG_LEVEL=%v", c.String("log-level"))) 65 | // } 66 | if c.String("env-file") != "" { 67 | args = append(args, "--env-file", c.String("env-file")) 68 | } 69 | if c.Bool("detach") { 70 | args = append(args, "-d") 71 | } 72 | args = append(args, "gochain/gochain", "--local") 73 | args = append(args, "--local.fund", acc.PublicKey()) 74 | cmd = exec.CommandContext(ctx, "docker", args...) 75 | fmt.Println("Starting your own, personal GoChain instance...") 76 | fmt.Println(asciiLogo) 77 | fmt.Println() 78 | if privateKey == "" { 79 | fmt.Printf("We created an account for you to get started quickly.\n\nYour private key is:\n\n%v\n\n"+ 80 | "Type: `export WEB3_PRIVATE_KEY=%v` to make using this tool easier.\n\n", acc.PrivateKey(), acc.PrivateKey()) 81 | 82 | } 83 | fmt.Printf("Your account %v is pre-funded with %v GO.\n", acc.PublicKey(), 1000) 84 | } 85 | cmd.Stdout = os.Stdout 86 | cmd.Stderr = os.Stderr 87 | err = cmd.Run() 88 | if err != nil { 89 | fatalExit(fmt.Errorf("Failed to run command %v", err)) 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /cmd/web3/transactions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math/big" 8 | 9 | "github.com/gochain/gochain/v4/accounts/abi" 10 | "github.com/gochain/gochain/v4/common" 11 | "github.com/gochain/gochain/v4/core/types" 12 | "github.com/gochain/web3" 13 | "github.com/shopspring/decimal" 14 | ) 15 | 16 | func IncreaseGas(ctx context.Context, privateKey string, network web3.Network, txHash string, amountGwei string) { 17 | client, err := web3.Dial(network.URL) 18 | if err != nil { 19 | fatalExit(fmt.Errorf("Failed to connect to %q: %v", network.URL, err)) 20 | } 21 | defer client.Close() 22 | // then we'll clone the original and increase gas 23 | txOrig, err := client.GetTransactionByHash(ctx, common.HexToHash(txHash)) 24 | if err != nil { 25 | fatalExit(fmt.Errorf("error on GetTransactionByHash: %v", err)) 26 | } 27 | if txOrig.BlockNumber != nil { 28 | fmt.Printf("tx isn't pending, so can't increase gas") 29 | return 30 | } 31 | amount, err := web3.ParseGwei(amountGwei) 32 | if err != nil { 33 | fmt.Printf("failed to parse amount %q: %v", amountGwei, err) 34 | return 35 | } 36 | newPrice := new(big.Int).Add(txOrig.GasPrice, amount) 37 | _ = ReplaceTx(ctx, privateKey, network, txOrig.Nonce, *txOrig.To, txOrig.Value, newPrice, txOrig.GasLimit, txOrig.Input) 38 | fmt.Printf("Increased gas price to %v\n", newPrice) 39 | } 40 | 41 | func ReplaceTx(ctx context.Context, privateKey string, network web3.Network, nonce uint64, to common.Address, amount *big.Int, 42 | gasPrice *big.Int, gasLimit uint64, data []byte) *types.Transaction { 43 | client, err := web3.Dial(network.URL) 44 | if err != nil { 45 | fatalExit(fmt.Errorf("Failed to connect to %q: %v", network.URL, err)) 46 | } 47 | defer client.Close() 48 | if gasPrice == nil { 49 | gasPrice, err = client.GetGasPrice(ctx) 50 | if err != nil { 51 | fatalExit(fmt.Errorf("couldn't get suggested gas price: %v", err)) 52 | } 53 | fmt.Printf("Using suggested gas price: %v\n", gasPrice) 54 | } 55 | chainID := network.ChainID 56 | if chainID == nil { 57 | chainID, err = client.GetChainID(ctx) 58 | if err != nil { 59 | fatalExit(fmt.Errorf("couldn't get chain ID: %v", err)) 60 | } 61 | } 62 | tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) 63 | acct, err := web3.ParsePrivateKey(privateKey) 64 | if err != nil { 65 | fatalExit(err) 66 | } 67 | 68 | fmt.Printf("Replacing transaction nonce: %v, gasPrice: %v, gasLimit: %v\n", nonce, gasPrice, gasLimit) 69 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), acct.Key()) 70 | if err != nil { 71 | fatalExit(fmt.Errorf("couldn't sign tx: %v", err)) 72 | } 73 | 74 | err = web3.SendTransaction(ctx, client, signedTx) 75 | if err != nil { 76 | fatalExit(fmt.Errorf("error sending transaction: %v", err)) 77 | } 78 | fmt.Printf("Replaced transaction. New transaction: %s\n", signedTx.Hash().Hex()) 79 | return tx 80 | } 81 | 82 | func Transfer(ctx context.Context, rpcURL string, chainID *big.Int, privateKey, contractAddress string, gasPrice *big.Int, gasLimit uint64, wait, toString bool, timeoutInSeconds uint64, tail []string) { 83 | if len(tail) < 3 { 84 | fatalExit(errors.New("Invalid arguments. Format is: `transfer X to ADDRESS`")) 85 | } 86 | 87 | amountS := tail[0] 88 | amountD, err := decimal.NewFromString(amountS) 89 | if err != nil { 90 | fatalExit(fmt.Errorf("invalid amount %v", amountS)) 91 | } 92 | toAddress := tail[2] 93 | 94 | client, err := web3.Dial(rpcURL) 95 | if err != nil { 96 | fatalExit(fmt.Errorf("Failed to connect to %q: %v", rpcURL, err)) 97 | } 98 | client.SetChainID(chainID) 99 | defer client.Close() 100 | 101 | if contractAddress != "" { 102 | decimals, err := GetContractConst(ctx, rpcURL, contractAddress, "erc20", "decimals") 103 | if err != nil { 104 | fatalExit(err) 105 | } 106 | amount := web3.DecToInt(amountD, int32(decimals[0].(uint8))) 107 | callContract(ctx, client, privateKey, contractAddress, "erc20", "transfer", &big.Int{}, nil, 70000, wait, toString, nil, timeoutInSeconds, toAddress, amount) 108 | return 109 | } 110 | 111 | amount := web3.DecToInt(amountD, 18) 112 | if toAddress == "" { 113 | fatalExit(errors.New("The recipient address cannot be empty")) 114 | } 115 | if !common.IsHexAddress(toAddress) { 116 | fatalExit(fmt.Errorf("Invalid to 'address': %s", toAddress)) 117 | } 118 | address := common.HexToAddress(toAddress) 119 | tx, err := web3.Send(ctx, client, privateKey, address, amount, gasPrice, gasLimit) 120 | if err != nil { 121 | fatalExit(fmt.Errorf("Cannot create transaction: %v", err)) 122 | } 123 | fmt.Println("Transaction address:", tx.Hash.Hex()) 124 | } 125 | 126 | func printReceiptDetails(r *web3.Receipt, myabi *abi.ABI) { 127 | var logs []web3.Event 128 | var err error 129 | if myabi != nil && r != nil && r.Logs != nil { 130 | logs, err = web3.ParseLogs(*myabi, r.Logs) 131 | r.ParsedLogs = logs 132 | if err != nil { 133 | fmt.Printf("ERROR: Cannot parse the receipt logs: %v\ncontinuing...\n", err) 134 | } 135 | } 136 | switch format { 137 | case "json": 138 | fmt.Println(marshalJSON(r)) 139 | return 140 | } 141 | 142 | fmt.Println("Transaction receipt address:", r.TxHash.Hex()) 143 | fmt.Printf("Block: #%d %s\n", r.BlockNumber, r.BlockHash.Hex()) 144 | fmt.Println("Tx Index:", r.TxIndex) 145 | fmt.Println("Tx Hash:", r.TxHash.String()) 146 | fmt.Println("From:", r.From.Hex()) 147 | if r.To != nil { 148 | fmt.Println("To:", r.To.Hex()) 149 | } 150 | if r.ContractAddress != (common.Address{}) { 151 | fmt.Println("Contract Address:", r.ContractAddress.String()) 152 | } 153 | fmt.Println("Gas Used:", r.GasUsed) 154 | fmt.Println("Cumulative Gas Used:", r.CumulativeGasUsed) 155 | var status string 156 | switch r.Status { 157 | case types.ReceiptStatusFailed: 158 | status = "Failed" 159 | case types.ReceiptStatusSuccessful: 160 | status = "Successful" 161 | default: 162 | status = fmt.Sprintf("%d (unrecognized status)", r.Status) 163 | } 164 | fmt.Println("Status:", status) 165 | fmt.Println("Post State:", "0x"+common.Bytes2Hex(r.PostState)) 166 | fmt.Println("Bloom:", "0x"+common.Bytes2Hex(r.Bloom.Bytes())) 167 | fmt.Println("Logs:", r.Logs) 168 | if myabi != nil { 169 | fmt.Println("Parsed Logs:", marshalJSON(r.ParsedLogs)) 170 | } 171 | } 172 | 173 | func GetTransactionReceipt(ctx context.Context, rpcURL, txhash, contractFile string) { 174 | var myabi *abi.ABI 175 | client, err := web3.Dial(rpcURL) 176 | if err != nil { 177 | fatalExit(fmt.Errorf("Failed to connect to %q: %v", rpcURL, err)) 178 | } 179 | defer client.Close() 180 | if contractFile != "" { 181 | myabi, err = web3.GetABI(contractFile) 182 | if err != nil { 183 | fatalExit(err) 184 | } 185 | } 186 | r, err := client.GetTransactionReceipt(ctx, common.HexToHash(txhash)) 187 | if err != nil { 188 | fatalExit(fmt.Errorf("Failed to get transaction receipt: %v", err)) 189 | } 190 | if verbose { 191 | fmt.Println("Transaction Receipt Details:") 192 | } 193 | 194 | printReceiptDetails(r, myabi) 195 | } 196 | -------------------------------------------------------------------------------- /cmd/web3/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var Version = "0.0.0" 4 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Example contracts for use in tutorials 2 | 3 | To try out the types: 4 | 5 | ```sh 6 | web3 contract build types.sol 7 | web3 contract deploy Types.bin 8 | export WEB3_ADDRESS=RETURN ADDRESS 9 | ``` 10 | 11 | then try calling the functions in the contract, both setters and getters. 12 | -------------------------------------------------------------------------------- /contracts/goodbye.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.11; 2 | 3 | contract Goodbye { 4 | 5 | string name; 6 | 7 | /* This runs when the contract is executed */ 8 | constructor() public { 9 | name = "World"; 10 | } 11 | 12 | function hello() public view returns (string memory, string memory) { 13 | return ("Goodbye", name); 14 | } 15 | 16 | function setName(string memory _name) public { 17 | name = _name; 18 | } 19 | } -------------------------------------------------------------------------------- /contracts/hello.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.11; 2 | 3 | contract Hello { 4 | 5 | string name; 6 | 7 | /* This runs when the contract is executed */ 8 | constructor() public { 9 | name = "World"; 10 | } 11 | 12 | function hello() public view returns (string memory, string memory) { 13 | return ("Hello", name); 14 | } 15 | 16 | function setName(string memory _name) public { 17 | name = _name; 18 | } 19 | } -------------------------------------------------------------------------------- /contracts/types.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.11; 2 | 3 | contract Types { 4 | 5 | string name; 6 | bytes32 public tbytes32; 7 | bytes public tbytes; 8 | string public tstring; 9 | uint256 public tuint256; 10 | uint8 public tuint8; 11 | 12 | /* This runs when the contract is executed */ 13 | constructor() public { 14 | name = "World"; 15 | tbytes32 = "i'm bytes"; 16 | tbytes = "this is a long string with variable length, so what are we going to do about it?"; 17 | tstring = "this is another long string with variable length that doesn't fit into bytes32"; 18 | } 19 | 20 | function hello() public view returns (string memory, string memory) { 21 | return ("Hello", name); 22 | } 23 | 24 | function setName(string memory _name) public { 25 | name = _name; 26 | } 27 | 28 | function setBytes32(bytes32 x) public { 29 | tbytes32 = x; 30 | } 31 | 32 | function setBytes(bytes memory x) public { 33 | tbytes = x; 34 | } 35 | 36 | function setUint256(uint256 x) public { 37 | tuint256 = x; 38 | } 39 | 40 | function setUint8(uint8 x) public { 41 | tuint8 = x; 42 | } 43 | } -------------------------------------------------------------------------------- /contracts/voting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.7.0 <0.9.0; 2 | 3 | contract Ballot { 4 | struct Voter { 5 | uint weight; 6 | bool voted; 7 | address delegate; 8 | uint vote; 9 | } 10 | 11 | struct Proposal { 12 | bytes32 name; 13 | uint voteCount; 14 | } 15 | 16 | address public chairperson; 17 | mapping(address => Voter) public voters; 18 | Proposal[] public proposals; 19 | 20 | constructor(bytes32[] memory proposalNames) { 21 | chairperson = msg.sender; 22 | voters[chairperson].weight = 1; 23 | 24 | for (uint i = 0; i < proposalNames.length; i++) { 25 | proposals.push(Proposal({ 26 | name: proposalNames[i], 27 | voteCount: 0 28 | })); 29 | } 30 | } 31 | 32 | function giveRightToVote(address voter) external { 33 | require(msg.sender == chairperson, "Only chairperson can give right to vote."); 34 | require(!voters[voter].voted, "The voter already voted."); 35 | require(voters[voter].weight == 0); 36 | voters[voter].weight = 1; 37 | } 38 | 39 | function delegate(address to) external { 40 | Voter storage sender = voters[msg.sender]; 41 | require(sender.weight != 0, "You have no right to vote"); 42 | require(!sender.voted, "You already voted."); 43 | require(to != msg.sender, "Self-delegation is disallowed."); 44 | 45 | while (voters[to].delegate != address(0)) { 46 | to = voters[to].delegate; 47 | require(to != msg.sender, "Found loop in delegation."); 48 | } 49 | 50 | Voter storage delegate_ = voters[to]; 51 | require(delegate_.weight >= 1); 52 | 53 | sender.voted = true; 54 | sender.delegate = to; 55 | 56 | if (delegate_.voted) { 57 | proposals[delegate_.vote].voteCount += sender.weight; 58 | } else { 59 | delegate_.weight += sender.weight; 60 | } 61 | } 62 | 63 | function vote(uint proposal) external { 64 | Voter storage sender = voters[msg.sender]; 65 | require(sender.weight != 0, "Has no right to vote"); 66 | require(!sender.voted, "Already voted."); 67 | sender.voted = true; 68 | sender.vote = proposal; 69 | 70 | proposals[proposal].voteCount += sender.weight; 71 | } 72 | 73 | function winningProposal() public view returns (uint winningProposal_) { 74 | uint winningVoteCount = 0; 75 | for (uint p = 0; p < proposals.length; p++) { 76 | if (proposals[p].voteCount > winningVoteCount) { 77 | winningVoteCount = proposals[p].voteCount; 78 | winningProposal_ = p; 79 | } 80 | } 81 | } 82 | 83 | function winnerName() external view returns (bytes32 winnerName_) { 84 | winnerName_ = proposals[winningProposal()].name; 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /did/did.go: -------------------------------------------------------------------------------- 1 | package did 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | // DID represents a decentralized identifier. 11 | // https://w3c-ccg.github.io/did-spec/#decentralized-identifiers-dids 12 | type DID struct { 13 | Method string 14 | ID string 15 | Path string 16 | Fragment string 17 | } 18 | 19 | // String returns the string representation of the DID. 20 | func (d *DID) String() string { 21 | var buf bytes.Buffer 22 | fmt.Fprintf(&buf, "did:%s:%s%s", d.Method, d.ID, escape(d.Path, encodePath)) 23 | if d.Fragment != "" { 24 | fmt.Fprintf(&buf, "#%s", escape(d.Fragment, encodeFragment)) 25 | } 26 | return buf.String() 27 | } 28 | 29 | func Parse(rawdid string) (*DID, error) { 30 | var err error 31 | var did DID 32 | 33 | // Ensure scheme is for a DID. 34 | if !strings.HasPrefix(rawdid, "did:") { 35 | return nil, errors.New("did.Parse(): invalid scheme") 36 | } 37 | rawdid = rawdid[4:] 38 | 39 | // Separate fragment & path. 40 | if idx := strings.Index(rawdid, "#"); idx >= 0 { 41 | rawdid, did.Fragment = rawdid[:idx], rawdid[idx+1:] 42 | } 43 | if idx := strings.Index(rawdid, "/"); idx >= 0 { 44 | rawdid, did.Path = rawdid[:idx], rawdid[idx:] 45 | } 46 | 47 | // Parse method & idstring 48 | if did.Method, rawdid, err = parseMethod(rawdid); err != nil { 49 | return nil, err 50 | } else if did.ID, err = parseIDString(rawdid); err != nil { 51 | return nil, err 52 | } 53 | 54 | // Escape path & fragment. 55 | if did.Path, err = unescape(did.Path, encodePath); err != nil { 56 | return nil, err 57 | } else if did.Fragment, err = unescape(did.Fragment, encodeFragment); err != nil { 58 | return nil, err 59 | } 60 | return &did, nil 61 | } 62 | 63 | func parseMethod(s string) (method, rest string, err error) { 64 | if len(s) == 0 { 65 | return "", "", errors.New("did.Parse(): missing method") 66 | } else if s[0] == ':' { 67 | return "", "", errors.New("did.Parse(): empty method not allowed") 68 | } 69 | 70 | var buf bytes.Buffer 71 | for i, ch := range s { 72 | if ch == ':' { 73 | return buf.String(), s[i+1:], nil 74 | } else if !isMethodChar(ch) { 75 | return "", "", fmt.Errorf("did.Parse(): invalid method character: %q", ch) 76 | } 77 | buf.WriteRune(ch) 78 | } 79 | return "", "", errors.New("did.Parse(): missing id separator") 80 | } 81 | 82 | func parseIDString(s string) (id string, err error) { 83 | if len(s) == 0 { 84 | return "", errors.New("did.Parse(): missing id") 85 | } 86 | 87 | var buf bytes.Buffer 88 | for _, ch := range s { 89 | if !isIDChar(ch) { 90 | return "", fmt.Errorf("did.Parse(): invalid id character: %q", ch) 91 | } 92 | buf.WriteRune(ch) 93 | } 94 | return buf.String(), nil 95 | } 96 | 97 | // IsValidIDString returns true if s is non-blank and contains only valid ID characters. 98 | func IsValidIDString(s string) bool { 99 | for _, ch := range s { 100 | if !isIDChar(ch) { 101 | return false 102 | } 103 | } 104 | return len(s) > 0 105 | } 106 | 107 | // isMethodChar returns true if ch is a valid character for the method. 108 | func isMethodChar(ch rune) bool { 109 | return (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') 110 | } 111 | 112 | // isIDChar returns true if ch is a valid character for the idstring. 113 | func isIDChar(ch rune) bool { 114 | return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' 115 | } 116 | -------------------------------------------------------------------------------- /did/did_test.go: -------------------------------------------------------------------------------- 1 | package did_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gochain/web3/did" 7 | ) 8 | 9 | func TestParse(t *testing.T) { 10 | t.Run("Minimal", func(t *testing.T) { 11 | if d, err := did.Parse("did:x:y"); err != nil { 12 | t.Fatalf("unexpected error: %s", err) 13 | } else if *d != (did.DID{Method: "x", ID: "y"}) { 14 | t.Fatalf("unexpected did: %#v", d) 15 | } 16 | }) 17 | t.Run("AllChars", func(t *testing.T) { 18 | if d, err := did.Parse("did:abcxyz123:abcxyzABCXYZ123.-"); err != nil { 19 | t.Fatalf("unexpected error: %s", err) 20 | } else if *d != (did.DID{Method: "abcxyz123", ID: "abcxyzABCXYZ123.-"}) { 21 | t.Fatalf("unexpected did: %#v", d) 22 | } 23 | }) 24 | t.Run("WithPath", func(t *testing.T) { 25 | if d, err := did.Parse("did:x:y/foo%20"); err != nil { 26 | t.Fatalf("unexpected error: %s", err) 27 | } else if *d != (did.DID{Method: "x", ID: "y", Path: "/foo "}) { 28 | t.Fatalf("unexpected did: %#v", d) 29 | } 30 | }) 31 | t.Run("WithFragment", func(t *testing.T) { 32 | if d, err := did.Parse("did:x:y#foo%20bar"); err != nil { 33 | t.Fatalf("unexpected error: %s", err) 34 | } else if *d != (did.DID{Method: "x", ID: "y", Fragment: "foo bar"}) { 35 | t.Fatalf("unexpected did: %#v", d) 36 | } 37 | }) 38 | t.Run("WithPathAndFragment", func(t *testing.T) { 39 | if d, err := did.Parse("did:x:y/foo#bar"); err != nil { 40 | t.Fatalf("unexpected error: %s", err) 41 | } else if *d != (did.DID{Method: "x", ID: "y", Path: "/foo", Fragment: "bar"}) { 42 | t.Fatalf("unexpected did: %#v", d) 43 | } 44 | }) 45 | 46 | t.Run("ErrInvalidScheme", func(t *testing.T) { 47 | if _, err := did.Parse(""); err == nil || err.Error() != `did.Parse(): invalid scheme` { 48 | t.Fatalf("unexpected error: %s", err) 49 | } 50 | }) 51 | t.Run("ErrMissingMethod", func(t *testing.T) { 52 | if _, err := did.Parse("did:"); err == nil || err.Error() != `did.Parse(): missing method` { 53 | t.Fatalf("unexpected error: %s", err) 54 | } 55 | }) 56 | t.Run("ErrEmptyMethod", func(t *testing.T) { 57 | if _, err := did.Parse("did::foo"); err == nil || err.Error() != `did.Parse(): empty method not allowed` { 58 | t.Fatalf("unexpected error: %s", err) 59 | } 60 | }) 61 | t.Run("ErrInvalidMethod", func(t *testing.T) { 62 | if _, err := did.Parse("did:foo!bar:baz"); err == nil || err.Error() != `did.Parse(): invalid method character: '!'` { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | }) 66 | t.Run("ErrMissingIDSeparator", func(t *testing.T) { 67 | if _, err := did.Parse("did:x"); err == nil || err.Error() != `did.Parse(): missing id separator` { 68 | t.Fatalf("unexpected error: %s", err) 69 | } 70 | }) 71 | t.Run("ErrMissingID", func(t *testing.T) { 72 | if _, err := did.Parse("did:x:"); err == nil || err.Error() != `did.Parse(): missing id` { 73 | t.Fatalf("unexpected error: %s", err) 74 | } 75 | }) 76 | t.Run("ErrInvalidID", func(t *testing.T) { 77 | if _, err := did.Parse("did:foo:bar!baz"); err == nil || err.Error() != `did.Parse(): invalid id character: '!'` { 78 | t.Fatalf("unexpected error: %s", err) 79 | } 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /did/document.go: -------------------------------------------------------------------------------- 1 | package did 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // ContextV1 is the required context for all DID documents. 8 | const ContextV1 = "https://w3id.org/did/v1" 9 | 10 | // Document represents a DID document. 11 | type Document struct { 12 | // MUST be set to ContextV1. 13 | Context string `json:"@context,omitempty"` 14 | 15 | // The identifier that the DID Document is about, i.e. the DID. 16 | ID string `json:"id,omitempty"` 17 | 18 | // Public keys are used for digital signatures, encryption and other 19 | // cryptographic operations, which in turn are the basis for purposes such 20 | // as authentication, or establishing secure communication with service 21 | // endpoints. In addition, public keys may play a role in authorization 22 | // mechanisms of DID CRUD operations 23 | PublicKeys []PublicKey `json:"publicKey,omitempty"` 24 | 25 | // Specifies zero or more embedded or referenced public keys by which a 26 | // DID subject can cryptographically prove that they are associated with a DID. 27 | // 28 | // Each element MUST be a PublicKey (embedded) or string (referenced). 29 | Authentications []interface{} `json:"authentication,omitempty"` 30 | 31 | // Represent any type of service the subject wishes to advertise, including 32 | // decentralized identity management services for further discovery, 33 | // authentication, authorization, or interaction. 34 | Services []Service `json:"services,omitempty"` 35 | 36 | // Timestamp when document was first created, normalized to UTC. Optional. 37 | Created *time.Time `json:"created,omitempty"` 38 | 39 | // Timestamp when document was last updated, normalized to UTC. Optional. 40 | Updated *time.Time `json:"updated,omitempty"` 41 | 42 | // Cryptographic proof of the integrity of the DID Document. 43 | // This proof is NOT proof of the binding between a DID and a DID Document. 44 | Proof *Proof `json:"proof,omitempty"` 45 | } 46 | 47 | // NewDocument returns a new Document with the appropriate context. 48 | func NewDocument() *Document { 49 | return &Document{Context: ContextV1} 50 | } 51 | 52 | // PublicKey represents a specification of public key on the document. 53 | type PublicKey struct { 54 | // Unique identifier of the key within the document. 55 | ID string `json:"id,omitempty"` 56 | 57 | // Type of encryption, as specified in Linked Data Cryptographic Suite Registry. 58 | // https://w3c-ccg.github.io/ld-cryptosuite-registry/ 59 | Type string `json:"type,omitempty"` 60 | 61 | // DID identifying the controller of the corresponding private key. 62 | Controller string `json:"controller,omitempty"` 63 | 64 | // Only one of these can be specified based on type. 65 | PublicKeyPEM string `json:"publicKeyPem,omitempty"` 66 | PublicKeyJWK string `json:"publicKeyJwk,omitempty"` 67 | PublicKeyHex string `json:"publicKeyHex,omitempty"` 68 | PublicKeyBase64 string `json:"publicKeyBase64,omitempty"` 69 | PublicKeyBase58 string `json:"publicKeyBase58,omitempty"` 70 | PublicKeyMultibase string `json:"publicKeyMultibase,omitempty"` 71 | } 72 | 73 | // Service represents a service endpoint specification. 74 | type Service struct { 75 | // Unique identifier of the service within the document. 76 | ID string `json:"id,omitempty"` 77 | Type string `json:"type,omitempty"` 78 | ServiceEndpoint string `json:"serviceEndpoint,omitempty"` 79 | } 80 | 81 | // Proof represents a JSON-LD proof of the integrity of a DID document. 82 | type Proof struct { 83 | Type string `json:"type,omitempty"` 84 | Creator string `json:"creator,omitempty"` 85 | Created string `json:"created,omitempty"` 86 | Domain string `json:"domain,omitempty"` 87 | Nonce string `json:"nonce,omitempty"` 88 | SignatureValue string `json:"signatureValue,omitempty"` 89 | } 90 | -------------------------------------------------------------------------------- /did/escape.go: -------------------------------------------------------------------------------- 1 | package did 2 | 3 | // NOTE: These functions are copied from the stdlib net/url because they 4 | // are currently unexported so they cannot be used through the API. The 5 | // DID specification requires URI escaping for the path & fragment sections. 6 | 7 | import ( 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type encoding int 13 | 14 | const ( 15 | encodePath encoding = 1 + iota 16 | encodePathSegment 17 | encodeHost 18 | encodeZone 19 | encodeUserPassword 20 | encodeQueryComponent 21 | encodeFragment 22 | ) 23 | 24 | // NOTE: This function is copied from the stdlib because it is unexported. 25 | func escape(s string, mode encoding) string { 26 | spaceCount, hexCount := 0, 0 27 | for i := 0; i < len(s); i++ { 28 | c := s[i] 29 | if shouldEscape(c, mode) { 30 | if c == ' ' && mode == encodeQueryComponent { 31 | spaceCount++ 32 | } else { 33 | hexCount++ 34 | } 35 | } 36 | } 37 | 38 | if spaceCount == 0 && hexCount == 0 { 39 | return s 40 | } 41 | 42 | var buf [64]byte 43 | var t []byte 44 | 45 | required := len(s) + 2*hexCount 46 | if required <= len(buf) { 47 | t = buf[:required] 48 | } else { 49 | t = make([]byte, required) 50 | } 51 | 52 | if hexCount == 0 { 53 | copy(t, s) 54 | for i := 0; i < len(s); i++ { 55 | if s[i] == ' ' { 56 | t[i] = '+' 57 | } 58 | } 59 | return string(t) 60 | } 61 | 62 | j := 0 63 | for i := 0; i < len(s); i++ { 64 | switch c := s[i]; { 65 | case c == ' ' && mode == encodeQueryComponent: 66 | t[j] = '+' 67 | j++ 68 | case shouldEscape(c, mode): 69 | t[j] = '%' 70 | t[j+1] = "0123456789ABCDEF"[c>>4] 71 | t[j+2] = "0123456789ABCDEF"[c&15] 72 | j += 3 73 | default: 74 | t[j] = s[i] 75 | j++ 76 | } 77 | } 78 | return string(t) 79 | } 80 | 81 | // unescape unescapes a string; the mode specifies 82 | // which section of the URL string is being unescaped. 83 | // 84 | // NOTE: This function is copied from the stdlib because it is unexported. 85 | func unescape(s string, mode encoding) (string, error) { 86 | // Count %, check that they're well-formed. 87 | n := 0 88 | hasPlus := false 89 | for i := 0; i < len(s); { 90 | switch s[i] { 91 | case '%': 92 | n++ 93 | if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { 94 | s = s[i:] 95 | if len(s) > 3 { 96 | s = s[:3] 97 | } 98 | return "", EscapeError(s) 99 | } 100 | // Per https://tools.ietf.org/html/rfc3986#page-21 101 | // in the host component %-encoding can only be used 102 | // for non-ASCII bytes. 103 | // But https://tools.ietf.org/html/rfc6874#section-2 104 | // introduces %25 being allowed to escape a percent sign 105 | // in IPv6 scoped-address literals. Yay. 106 | if mode == encodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" { 107 | return "", EscapeError(s[i : i+3]) 108 | } 109 | if mode == encodeZone { 110 | // RFC 6874 says basically "anything goes" for zone identifiers 111 | // and that even non-ASCII can be redundantly escaped, 112 | // but it seems prudent to restrict %-escaped bytes here to those 113 | // that are valid host name bytes in their unescaped form. 114 | // That is, you can use escaping in the zone identifier but not 115 | // to introduce bytes you couldn't just write directly. 116 | // But Windows puts spaces here! Yay. 117 | v := unhex(s[i+1])<<4 | unhex(s[i+2]) 118 | if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, encodeHost) { 119 | return "", EscapeError(s[i : i+3]) 120 | } 121 | } 122 | i += 3 123 | case '+': 124 | hasPlus = mode == encodeQueryComponent 125 | i++ 126 | default: 127 | i++ 128 | } 129 | } 130 | 131 | if n == 0 && !hasPlus { 132 | return s, nil 133 | } 134 | 135 | t := make([]byte, len(s)-2*n) 136 | j := 0 137 | for i := 0; i < len(s); { 138 | switch s[i] { 139 | case '%': 140 | t[j] = unhex(s[i+1])<<4 | unhex(s[i+2]) 141 | j++ 142 | i += 3 143 | case '+': 144 | if mode == encodeQueryComponent { 145 | t[j] = ' ' 146 | } else { 147 | t[j] = '+' 148 | } 149 | j++ 150 | i++ 151 | default: 152 | t[j] = s[i] 153 | j++ 154 | i++ 155 | } 156 | } 157 | return string(t), nil 158 | } 159 | 160 | // Return true if the specified character should be escaped when 161 | // appearing in a URL string, according to RFC 3986. 162 | // 163 | // Please be informed that for now shouldEscape does not check all 164 | // reserved characters correctly. See golang.org/issue/5684. 165 | // 166 | // NOTE: This function is copied from the stdlib because it is unexported. 167 | func shouldEscape(c byte, mode encoding) bool { 168 | // §2.3 Unreserved characters (alphanum) 169 | if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { 170 | return false 171 | } 172 | 173 | if mode == encodeHost || mode == encodeZone { 174 | // §3.2.2 Host allows 175 | // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" 176 | // as part of reg-name. 177 | // We add : because we include :port as part of host. 178 | // We add [ ] because we include [ipv6]:port as part of host. 179 | // We add < > because they're the only characters left that 180 | // we could possibly allow, and Parse will reject them if we 181 | // escape them (because hosts can't use %-encoding for 182 | // ASCII bytes). 183 | switch c { 184 | case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"': 185 | return false 186 | } 187 | } 188 | 189 | switch c { 190 | case '-', '_', '.', '~': // §2.3 Unreserved characters (mark) 191 | return false 192 | 193 | case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved) 194 | // Different sections of the URL allow a few of 195 | // the reserved characters to appear unescaped. 196 | switch mode { 197 | case encodePath: // §3.3 198 | // The RFC allows : @ & = + $ but saves / ; , for assigning 199 | // meaning to individual path segments. This package 200 | // only manipulates the path as a whole, so we allow those 201 | // last three as well. That leaves only ? to escape. 202 | return c == '?' 203 | 204 | case encodePathSegment: // §3.3 205 | // The RFC allows : @ & = + $ but saves / ; , for assigning 206 | // meaning to individual path segments. 207 | return c == '/' || c == ';' || c == ',' || c == '?' 208 | 209 | case encodeUserPassword: // §3.2.1 210 | // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in 211 | // userinfo, so we must escape only '@', '/', and '?'. 212 | // The parsing of userinfo treats ':' as special so we must escape 213 | // that too. 214 | return c == '@' || c == '/' || c == '?' || c == ':' 215 | 216 | case encodeQueryComponent: // §3.4 217 | // The RFC reserves (so we must escape) everything. 218 | return true 219 | 220 | case encodeFragment: // §4.1 221 | // The RFC text is silent but the grammar allows 222 | // everything, so escape nothing. 223 | return false 224 | } 225 | } 226 | 227 | if mode == encodeFragment { 228 | // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are 229 | // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not 230 | // need to be escaped. To minimize potential breakage, we apply two restrictions: 231 | // (1) we always escape sub-delims outside of the fragment, and (2) we always 232 | // escape single quote to avoid breaking callers that had previously assumed that 233 | // single quotes would be escaped. See issue #19917. 234 | switch c { 235 | case '!', '(', ')', '*': 236 | return false 237 | } 238 | } 239 | 240 | // Everything else must be escaped. 241 | return true 242 | } 243 | 244 | // Maybe s is of the form t c u. 245 | // If so, return t, c u (or t, u if cutc == true). 246 | // If not, return s, "". 247 | func split(s string, c string, cutc bool) (string, string) { 248 | i := strings.Index(s, c) 249 | if i < 0 { 250 | return s, "" 251 | } 252 | if cutc { 253 | return s[:i], s[i+len(c):] 254 | } 255 | return s[:i], s[i:] 256 | } 257 | 258 | func ishex(c byte) bool { 259 | switch { 260 | case '0' <= c && c <= '9': 261 | return true 262 | case 'a' <= c && c <= 'f': 263 | return true 264 | case 'A' <= c && c <= 'F': 265 | return true 266 | } 267 | return false 268 | } 269 | 270 | func unhex(c byte) byte { 271 | switch { 272 | case '0' <= c && c <= '9': 273 | return c - '0' 274 | case 'a' <= c && c <= 'f': 275 | return c - 'a' + 10 276 | case 'A' <= c && c <= 'F': 277 | return c - 'A' + 10 278 | } 279 | return 0 280 | } 281 | 282 | type EscapeError string 283 | 284 | func (e EscapeError) Error() string { 285 | return "invalid URL escape " + strconv.Quote(string(e)) 286 | } 287 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gochain/web3 2 | 3 | go 1.22 4 | toolchain go1.24.1 5 | 6 | require ( 7 | github.com/gochain/gochain/v4 v4.3.1 8 | github.com/shopspring/decimal v1.4.0 9 | github.com/treeder/gotils/v2 v2.0.18 10 | github.com/urfave/cli v1.22.5 11 | golang.org/x/crypto v0.36.0 12 | ) 13 | 14 | require ( 15 | github.com/OneOfOne/xxhash v1.2.8 // indirect 16 | github.com/allegro/bigcache v1.2.1 // indirect 17 | github.com/aristanetworks/goarista v0.0.0-20210319202508-5b0c587084ea // indirect 18 | github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect 19 | github.com/cespare/cp v1.1.1 // indirect 20 | github.com/cespare/xxhash v1.1.0 // indirect 21 | github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/deckarep/golang-set v1.8.0 // indirect 24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect 25 | github.com/edsrzf/mmap-go v1.2.0 // indirect 26 | github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect 27 | github.com/go-stack/stack v1.8.1 // indirect 28 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 29 | github.com/golang/snappy v0.0.4 // indirect 30 | github.com/google/uuid v1.2.0 // indirect 31 | github.com/gorilla/websocket v1.5.3 // indirect 32 | github.com/hashicorp/golang-lru v1.0.2 // indirect 33 | github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 // indirect 34 | github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 // indirect 35 | github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect 36 | github.com/mattn/go-runewidth v0.0.9 // indirect 37 | github.com/pborman/uuid v1.2.1 // indirect 38 | github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect 39 | github.com/rjeczalik/notify v0.9.2 // indirect 40 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 41 | github.com/spaolacci/murmur3 v1.1.0 // indirect 42 | github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 // indirect 43 | github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect 44 | github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect 45 | go.opencensus.io v0.24.0 // indirect 46 | golang.org/x/net v0.38.0 // indirect 47 | golang.org/x/sync v0.12.0 // indirect 48 | golang.org/x/sys v0.31.0 // indirect 49 | golang.org/x/text v0.23.0 // indirect 50 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 51 | ) 52 | 53 | // replace github.com/gochain/gochain/v4 => ../gochain 54 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Install script to install web3 5 | if [ "$1" != "" ]; then 6 | version="$1" 7 | else 8 | version=`curl --silent https://api.github.com/repos/gochain/web3/releases/latest | grep tag_name | cut -f 2 -d : | cut -f 2 -d '"'` 9 | fi 10 | 11 | 12 | command_exists() { 13 | command -v "$@" > /dev/null 2>&1 14 | } 15 | 16 | case "$(uname -m)" in 17 | *64) 18 | ;; 19 | *) 20 | echo >&2 'Error: you are not using a 64bit platform.' 21 | echo >&2 'Web3 CLI currently only supports 64bit platforms.' 22 | exit 1 23 | ;; 24 | esac 25 | 26 | user="$(id -un 2>/dev/null || true)" 27 | 28 | sh_c='sh -c' 29 | if [ "$user" != 'root' ]; then 30 | if command_exists sudo; then 31 | sh_c='sudo -E sh -c' 32 | elif command_exists su; then 33 | sh_c='su -c' 34 | else 35 | echo >&2 'Error: this installer needs the ability to run commands as root.' 36 | echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' 37 | exit 1 38 | fi 39 | fi 40 | 41 | curl='' 42 | if command_exists curl; then 43 | curl='curl -sSL -o' 44 | elif command_exists wget; then 45 | curl='wget -qO' 46 | elif command_exists busybox && busybox --list-modules | grep -q wget; then 47 | curl='busybox wget -qO' 48 | else 49 | echo >&2 'Error: this installer needs the ability to run wget or curl.' 50 | echo >&2 'We are unable to find either "wget" or "curl" available to make this happen.' 51 | exit 1 52 | fi 53 | 54 | url='https://github.com/gochain/web3/releases/download' 55 | 56 | 57 | # perform some very rudimentary platform/architecture detection 58 | case "$(uname)" in 59 | Linux) 60 | case "`uname -m`" in 61 | x86_64|amd64) 62 | echo "Installing web3 for linux x86_64" 63 | $sh_c "$curl /tmp/web3_linux $url/$version/web3_linux" 64 | ;; 65 | arm*|aarch*) 66 | $sh_c "$curl /tmp/web3_linux $url/$version/web3_linux_arm64" 67 | echo "Installing web3 for linux_arm64" 68 | ;; 69 | *) 70 | echo "Unknown architecture: $(uname -i)";; 71 | esac 72 | $sh_c "mv /tmp/web3_linux /usr/local/bin/web3" 73 | $sh_c "chmod +x /usr/local/bin/web3" 74 | web3 75 | ;; 76 | Darwin) 77 | case "`uname -m`" in 78 | x86_64|amd64) 79 | $sh_c "$curl /tmp/web3_mac $url/$version/web3_mac" 80 | echo "Installing web3 for mac x86_64" 81 | ;; 82 | arm*|aarch*) 83 | $sh_c "$curl /tmp/web3_mac $url/$version/web3_mac_arm64" 84 | echo "Installing web3 for mac arm64" 85 | ;; 86 | *) 87 | echo "Unknown architecture: $(uname -i)";; 88 | esac 89 | $sh_c "mv /tmp/web3_mac /usr/local/bin/web3" 90 | $sh_c "chmod +x /usr/local/bin/web3" 91 | web3 92 | ;; 93 | WindowsNT) 94 | $sh_c "$curl $url/$version/web3.exe" 95 | # TODO how to make executable? chmod? how to do tmp file and move? 96 | web3.exe 97 | ;; 98 | *) 99 | cat >&2 <<'EOF' 100 | 101 | Either your platform is not easily detectable or is not supported by this 102 | installer script (yet - PRs welcome!. 103 | EOF 104 | exit 1 105 | esac 106 | 107 | exit 0 108 | -------------------------------------------------------------------------------- /networks.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import "math/big" 4 | 5 | const ( 6 | testnetExplorerURL = "https://testnet-explorer.gochain.io/api" 7 | mainnetExplorerURL = "https://explorer.gochain.io/api" 8 | testnetURL = "https://testnet-rpc.gochain.io" 9 | mainnetURL = "https://rpc.gochain.io" 10 | ) 11 | 12 | var Networks = map[string]Network{ 13 | "testnet": { 14 | Name: "testnet", 15 | URL: testnetURL, 16 | ChainID: big.NewInt(31337), 17 | Unit: "GO", 18 | ExplorerURL: testnetExplorerURL, 19 | }, 20 | "gochain": { 21 | Name: "gochain", 22 | URL: mainnetURL, 23 | ChainID: big.NewInt(60), 24 | Unit: "GO", 25 | ExplorerURL: mainnetExplorerURL, 26 | }, 27 | "localhost": { 28 | Name: "localhost", 29 | URL: "http://localhost:8545", 30 | Unit: "GO", 31 | }, 32 | "ethereum": { 33 | Name: "ethereum", 34 | URL: "https://mainnet.infura.io/v3/bc5b0e5cfd9b4385befb69a68a9400c3", 35 | // URL: "https://cloudflare-eth.com", // these don't worry very well, constant problems 36 | // URL: "https://main-rpc.linkpool.io", 37 | ChainID: big.NewInt(1), 38 | Unit: "ETH", 39 | ExplorerURL: "https://etherscan.io", 40 | }, 41 | "ropsten": { 42 | Name: "ropsten", 43 | URL: "https://ropsten-rpc.linkpool.io", 44 | ChainID: big.NewInt(3), 45 | Unit: "ETH", 46 | }, 47 | "sepolia": { 48 | Name: "sepolia", 49 | URL: "https://sepolia.infura.io/v3/bc5b0e5cfd9b4385befb69a68a9400c3", 50 | ChainID: big.NewInt(11155111), 51 | Unit: "ETH", 52 | }, 53 | } 54 | 55 | type Network struct { 56 | Name string 57 | URL string 58 | ExplorerURL string 59 | Unit string 60 | ChainID *big.Int 61 | } 62 | -------------------------------------------------------------------------------- /rpc.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/gochain/gochain/v4/common" 10 | "github.com/gochain/gochain/v4/common/hexutil" 11 | "github.com/gochain/gochain/v4/core/types" 12 | ) 13 | 14 | type rpcBlock struct { 15 | ParentHash *common.Hash `json:"parentHash"` 16 | Sha3Uncles *common.Hash `json:"sha3Uncles"` 17 | Miner *common.Address `json:"miner"` 18 | Signers []common.Address `json:"signers,omitempty"` 19 | Voters []common.Address `json:"voters,omitempty"` 20 | Signer *hexutil.Bytes `json:"signer,omitempty"` 21 | StateRoot *common.Hash `json:"stateRoot"` 22 | TxsRoot *common.Hash `json:"transactionsRoot"` 23 | ReceiptsRoot *common.Hash `json:"receiptsRoot"` 24 | LogsBloom *types.Bloom `json:"logsBloom"` 25 | Difficulty *hexutil.Big `json:"difficulty"` 26 | TotalDifficulty *hexutil.Big `json:"totalDifficulty"` 27 | Number *hexutil.Big `json:"number"` 28 | GasLimit *hexutil.Uint64 `json:"gasLimit"` 29 | GasUsed *hexutil.Uint64 `json:"gasUsed"` 30 | Timestamp *hexutil.Uint64 `json:"timestamp"` 31 | ExtraData *hexutil.Bytes `json:"extraData"` 32 | MixHash *common.Hash `json:"mixHash"` 33 | Nonce *types.BlockNonce `json:"nonce"` 34 | Hash *common.Hash `json:"hash"` 35 | Txs json.RawMessage `json:"transactions,omitempty"` 36 | Uncles []common.Hash `json:"uncles"` 37 | } 38 | 39 | // copyTo copies the fields from r to b. 40 | func (r *rpcBlock) copyTo(b *Block) error { 41 | if r.ParentHash == nil { 42 | return errors.New("missing 'parentHash'") 43 | } 44 | b.ParentHash = *r.ParentHash 45 | if r.Sha3Uncles == nil { 46 | return errors.New("missing 'sha3Uncles'") 47 | } 48 | b.Sha3Uncles = *r.Sha3Uncles 49 | if r.Miner == nil { 50 | return errors.New("missing 'miner'") 51 | } 52 | b.Miner = *r.Miner 53 | b.Signers = r.Signers 54 | b.Voters = r.Voters 55 | if r.Signer != nil { 56 | b.Signer = *r.Signer 57 | } 58 | if r.StateRoot == nil { 59 | return errors.New("missing 'stateRoot'") 60 | } 61 | b.StateRoot = *r.StateRoot 62 | if r.TxsRoot == nil { 63 | return errors.New("missing 'transactionsRoot'") 64 | } 65 | b.TxsRoot = *r.TxsRoot 66 | if r.ReceiptsRoot == nil { 67 | return errors.New("missing 'receiptsRoot'") 68 | } 69 | b.ReceiptsRoot = *r.ReceiptsRoot 70 | if r.LogsBloom == nil { 71 | return errors.New("missing 'logsBloom'") 72 | } 73 | b.LogsBloom = r.LogsBloom 74 | if r.Difficulty == nil { 75 | return errors.New("missing 'difficulty'") 76 | } 77 | b.Difficulty = r.Difficulty.ToInt() 78 | if r.TotalDifficulty != nil { 79 | b.TotalDifficulty = r.TotalDifficulty.ToInt() 80 | } 81 | if r.Number == nil { 82 | return errors.New("missing 'number'") 83 | } 84 | b.Number = r.Number.ToInt() 85 | if r.GasLimit == nil { 86 | return errors.New("missing 'gasLimit'") 87 | } 88 | b.GasLimit = uint64(*r.GasLimit) 89 | if r.GasUsed == nil { 90 | return errors.New("missing 'gasUsed'") 91 | } 92 | b.GasUsed = uint64(*r.GasUsed) 93 | if r.Timestamp == nil { 94 | return errors.New("missing 'timestamp'") 95 | } 96 | b.Timestamp = time.Unix(int64(*r.Timestamp), 0).UTC() 97 | if r.ExtraData == nil { 98 | return errors.New("missing 'extraData") 99 | } 100 | b.ExtraData = *r.ExtraData 101 | if r.MixHash == nil { 102 | return errors.New("missing 'mixHash'") 103 | } 104 | b.MixHash = *r.MixHash 105 | if r.Nonce == nil { 106 | return errors.New("missing 'nonce'") 107 | } 108 | b.Nonce = *r.Nonce 109 | if r.Hash == nil { 110 | return errors.New("missing 'hash'") 111 | } 112 | b.Hash = *r.Hash 113 | 114 | // Try tx hashes first. 115 | var hashes []common.Hash 116 | if err := json.Unmarshal(r.Txs, &hashes); err == nil { 117 | b.TxHashes = hashes 118 | } else { 119 | // Try full transactions. 120 | var details []*Transaction 121 | if err := json.Unmarshal(r.Txs, &details); err != nil { 122 | return fmt.Errorf("failed to unmarshal transactions as either hahes or details %q: %s", err, string(r.Txs)) 123 | } 124 | b.TxDetails = details 125 | } 126 | 127 | b.Uncles = r.Uncles 128 | return nil 129 | } 130 | 131 | // copyFrom copies the fields from b to r. 132 | func (r *rpcBlock) copyFrom(b *Block) error { 133 | r.ParentHash = &b.ParentHash 134 | r.Sha3Uncles = &b.Sha3Uncles 135 | r.Miner = &b.Miner 136 | r.Signers = b.Signers 137 | r.Voters = b.Voters 138 | r.Signer = (*hexutil.Bytes)(&b.Signer) 139 | r.StateRoot = &b.StateRoot 140 | r.TxsRoot = &b.TxsRoot 141 | r.ReceiptsRoot = &b.ReceiptsRoot 142 | r.LogsBloom = b.LogsBloom 143 | r.Difficulty = (*hexutil.Big)(b.Difficulty) 144 | r.TotalDifficulty = (*hexutil.Big)(b.TotalDifficulty) 145 | r.Number = (*hexutil.Big)(b.Number) 146 | r.GasLimit = (*hexutil.Uint64)(&b.GasLimit) 147 | r.GasUsed = (*hexutil.Uint64)(&b.GasUsed) 148 | t := uint64(b.Timestamp.Unix()) 149 | r.Timestamp = (*hexutil.Uint64)(&t) 150 | r.ExtraData = (*hexutil.Bytes)(&b.ExtraData) 151 | r.MixHash = &b.MixHash 152 | r.Nonce = &b.Nonce 153 | r.Hash = &b.Hash 154 | if b.TxHashes != nil { 155 | data, err := json.Marshal(b.TxHashes) 156 | if err != nil { 157 | return fmt.Errorf("failed to marshal tx hashes to json: %v", err) 158 | } 159 | r.Txs = data 160 | } else { 161 | data, err := json.Marshal(b.TxDetails) 162 | if err != nil { 163 | return fmt.Errorf("failed to marshal tx details to json: %v", err) 164 | } 165 | r.Txs = data 166 | } 167 | r.Uncles = b.Uncles 168 | return nil 169 | } 170 | 171 | type rpcTransaction struct { 172 | Nonce *hexutil.Uint64 `json:"nonce"` 173 | GasPrice *hexutil.Big `json:"gasPrice"` 174 | GasLimit *hexutil.Uint64 `json:"gas"` 175 | To *common.Address `json:"to"` 176 | Value *hexutil.Big `json:"value"` 177 | Input *hexutil.Bytes `json:"input"` 178 | From *common.Address `json:"from"` 179 | V *hexutil.Big `json:"v"` 180 | R *hexutil.Big `json:"r"` 181 | S *hexutil.Big `json:"s"` 182 | Hash *common.Hash `json:"hash"` 183 | 184 | BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` 185 | BlockHash *common.Hash `json:"blockHash,omitempty"` 186 | TransactionIndex *hexutil.Uint64 `json:"transactionIndex,omitempty"` 187 | } 188 | 189 | // copyTo copies the fields from r to t. 190 | func (r *rpcTransaction) copyTo(t *Transaction) error { 191 | if r.Nonce == nil { 192 | return errors.New("missing 'nonce'") 193 | } 194 | t.Nonce = uint64(*r.Nonce) 195 | if r.GasPrice == nil { 196 | return errors.New("missing 'gasPrice'") 197 | } 198 | t.GasPrice = r.GasPrice.ToInt() 199 | if r.GasLimit == nil { 200 | return errors.New("missing 'gas'") 201 | } 202 | t.GasLimit = uint64(*r.GasLimit) 203 | if r.To != nil { 204 | t.To = r.To 205 | } 206 | if r.Value == nil { 207 | return errors.New("missing 'value'") 208 | } 209 | t.Value = r.Value.ToInt() 210 | if r.Input != nil { 211 | t.Input = *r.Input 212 | } 213 | if r.V == nil { 214 | return errors.New("missing 'v'") 215 | } 216 | t.V = r.V.ToInt() 217 | if r.R == nil { 218 | return errors.New("missing 'r'") 219 | } 220 | t.R = r.R.ToInt() 221 | if r.S == nil { 222 | return errors.New("missing 's'") 223 | } 224 | t.S = r.S.ToInt() 225 | if r.Hash != nil { 226 | t.Hash = *r.Hash 227 | } 228 | 229 | if r.BlockNumber != nil { 230 | t.BlockNumber = r.BlockNumber.ToInt() 231 | } 232 | if r.BlockHash != nil { 233 | t.BlockHash = *r.BlockHash 234 | } 235 | if r.From != nil { 236 | t.From = *r.From 237 | } 238 | if r.TransactionIndex != nil { 239 | t.TransactionIndex = uint64(*r.TransactionIndex) 240 | } 241 | return nil 242 | } 243 | 244 | // copyFrom copies the fields from t to r. 245 | func (r *rpcTransaction) copyFrom(t *Transaction) { 246 | r.Nonce = (*hexutil.Uint64)(&t.Nonce) 247 | r.GasPrice = (*hexutil.Big)(t.GasPrice) 248 | r.GasLimit = (*hexutil.Uint64)(&t.GasLimit) 249 | r.To = t.To 250 | r.Value = (*hexutil.Big)(t.Value) 251 | r.Input = (*hexutil.Bytes)(&t.Input) 252 | r.Hash = &t.Hash 253 | r.BlockNumber = (*hexutil.Big)(t.BlockNumber) 254 | r.BlockHash = &t.BlockHash 255 | r.From = &t.From 256 | r.TransactionIndex = (*hexutil.Uint64)(&t.TransactionIndex) 257 | r.V = (*hexutil.Big)(t.V) 258 | r.R = (*hexutil.Big)(t.R) 259 | r.S = (*hexutil.Big)(t.S) 260 | } 261 | 262 | type rpcReceipt struct { 263 | PostState *hexutil.Bytes `json:"root"` 264 | Status *hexutil.Uint64 `json:"status"` 265 | CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed"` 266 | Bloom *types.Bloom `json:"logsBloom"` 267 | Logs []*types.Log `json:"logs"` 268 | TxHash *common.Hash `json:"transactionHash"` 269 | TxIndex *hexutil.Uint64 `json:"transactionIndex"` 270 | ContractAddress *common.Address `json:"contractAddress"` 271 | GasUsed *hexutil.Uint64 `json:"gasUsed"` 272 | ParsedLogs *[]Event `json:"parsedLogs"` 273 | BlockHash *common.Hash `json:"blockHash"` 274 | BlockNumber *hexutil.Uint64 `json:"blockNumber"` 275 | From *common.Address `json:"from"` 276 | To *common.Address `json:"to"` 277 | } 278 | 279 | func (rr *rpcReceipt) copyTo(r *Receipt) error { 280 | if rr.PostState != nil { 281 | r.PostState = *rr.PostState 282 | } 283 | if rr.Status != nil { 284 | r.Status = uint64(*rr.Status) 285 | } 286 | if rr.CumulativeGasUsed == nil { 287 | return errors.New("missing 'cumulativeGasUsed'") 288 | } 289 | r.CumulativeGasUsed = uint64(*rr.CumulativeGasUsed) 290 | r.Bloom = *rr.Bloom 291 | if rr.Logs == nil { 292 | return errors.New("missing 'logs'") 293 | } 294 | r.Logs = rr.Logs 295 | if rr.TxHash == nil { 296 | return errors.New("missing 'transactionHash'") 297 | } 298 | r.TxHash = *rr.TxHash 299 | if rr.TxIndex == nil { 300 | return errors.New("missing 'transactionIndex'") 301 | } 302 | r.TxIndex = uint64(*rr.TxIndex) 303 | if rr.ContractAddress != nil { 304 | r.ContractAddress = *rr.ContractAddress 305 | } 306 | if rr.GasUsed == nil { 307 | return errors.New("missing 'gasUsed'") 308 | } 309 | r.GasUsed = uint64(*rr.GasUsed) 310 | if rr.BlockHash == nil { 311 | return errors.New("missing 'blockHash'") 312 | } 313 | r.BlockHash = *rr.BlockHash 314 | if rr.BlockNumber == nil { 315 | return errors.New("missing 'blockNumber'") 316 | } 317 | r.BlockNumber = uint64(*rr.BlockNumber) 318 | if rr.From == nil { 319 | return errors.New("missing 'from'") 320 | } 321 | r.From = *rr.From 322 | if rr.To != nil { 323 | r.To = rr.To 324 | } 325 | return nil 326 | } 327 | 328 | func (rr *rpcReceipt) copyFrom(r *Receipt) { 329 | rr.PostState = (*hexutil.Bytes)(&r.PostState) 330 | rr.Status = (*hexutil.Uint64)(&r.Status) 331 | rr.CumulativeGasUsed = (*hexutil.Uint64)(&r.CumulativeGasUsed) 332 | rr.Bloom = &r.Bloom 333 | rr.Logs = r.Logs 334 | rr.TxHash = &r.TxHash 335 | rr.TxIndex = (*hexutil.Uint64)(&r.TxIndex) 336 | rr.ContractAddress = &r.ContractAddress 337 | rr.GasUsed = (*hexutil.Uint64)(&r.GasUsed) 338 | rr.ParsedLogs = &r.ParsedLogs 339 | rr.BlockHash = &r.BlockHash 340 | rr.BlockNumber = (*hexutil.Uint64)(&r.BlockNumber) 341 | rr.From = &r.From 342 | rr.To = r.To 343 | } 344 | -------------------------------------------------------------------------------- /solc.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "os" 10 | "os/exec" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`) 17 | 18 | // Contract contains information about a compiled contract, alongside its code and runtime code. 19 | type Contract struct { 20 | Code string `json:"code"` 21 | RuntimeCode string `json:"runtime-code"` 22 | Info ContractInfo `json:"info"` 23 | Hashes map[string]string `json:"hashes"` 24 | } 25 | 26 | // ContractInfo contains information about a compiled contract, including access 27 | // to the ABI definition, source mapping, user and developer docs, and metadata. 28 | // 29 | // Depending on the source, language version, compiler version, and compiler 30 | // options will provide information about how the contract was compiled. 31 | type ContractInfo struct { 32 | Source string `json:"source"` 33 | Language string `json:"language"` 34 | LanguageVersion string `json:"languageVersion"` 35 | CompilerVersion string `json:"compilerVersion"` 36 | CompilerOptions string `json:"compilerOptions"` 37 | SrcMap interface{} `json:"srcMap"` 38 | SrcMapRuntime string `json:"srcMapRuntime"` 39 | AbiDefinition interface{} `json:"abiDefinition"` 40 | UserDoc interface{} `json:"userDoc"` 41 | DeveloperDoc interface{} `json:"developerDoc"` 42 | Metadata string `json:"metadata"` 43 | } 44 | 45 | // Solidity specifies the solidity compiler configuration. 46 | type Solidity struct { 47 | Path, Version, EVMVersion string 48 | Major, Minor, Patch int 49 | Optimize bool 50 | } 51 | 52 | // --combined-output format 53 | type solcOutput struct { 54 | Contracts map[string]struct { 55 | BinRuntime string `json:"bin-runtime"` 56 | SrcMapRuntime string `json:"srcmap-runtime"` 57 | Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string 58 | } 59 | Version string 60 | } 61 | type solcOutputV8 struct { 62 | Contracts map[string]struct { 63 | BinRuntime string `json:"bin-runtime"` 64 | SrcMapRuntime string `json:"srcmap-runtime"` 65 | Bin, SrcMap, Metadata string 66 | Abi interface{} 67 | Devdoc interface{} 68 | Userdoc interface{} 69 | Hashes map[string]string 70 | } 71 | Version string 72 | } 73 | 74 | func (s *Solidity) makeArgs() ([]string, error) { 75 | dir, err := os.Getwd() 76 | if err != nil { 77 | return nil, err 78 | } 79 | args := []string{ 80 | "run", "-i", "--rm", "-v", dir + ":/workdir", "-w", "/workdir", "ethereum/solc:" + s.Version, 81 | "--combined-json", 82 | "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata", 83 | "--evm-version", s.EVMVersion, 84 | } 85 | if s.Optimize { 86 | args = append(args, "--optimize") 87 | } 88 | return args, nil 89 | } 90 | 91 | // SolidityVersion runs solc and parses its version output. 92 | func SolidityVersion(source string) (*Solidity, error) { 93 | var err error 94 | matches := versionRegexp.FindStringSubmatch(source) 95 | if len(matches) != 4 { 96 | return nil, fmt.Errorf("can't parse solc version %q", source) 97 | } 98 | s := &Solidity{Path: "docker"} 99 | if s.Major, err = strconv.Atoi(matches[1]); err != nil { 100 | return nil, err 101 | } 102 | if s.Minor, err = strconv.Atoi(matches[2]); err != nil { 103 | return nil, err 104 | } 105 | if s.Patch, err = strconv.Atoi(matches[3]); err != nil { 106 | return nil, err 107 | } 108 | s.Version = strconv.Itoa(s.Major) + "." + strconv.Itoa(s.Minor) + "." + strconv.Itoa(s.Patch) 109 | return s, nil 110 | } 111 | 112 | // CompileSolidityString builds and returns all the contracts contained within a source string. 113 | func CompileSolidityString(ctx context.Context, source, solcVersion, evmVersion string, optimize bool) (map[string]*Contract, error) { 114 | var s *Solidity 115 | var err error 116 | if len(source) == 0 { 117 | return nil, errors.New("solc: empty source string") 118 | } 119 | if solcVersion != "" { 120 | s = &Solidity{Path: "docker", Version: solcVersion} 121 | } else { 122 | s, err = SolidityVersion(source) 123 | if err != nil { 124 | return nil, err 125 | } 126 | } 127 | // fmt.Printf("Building with solidity version %v\n", s.Version) 128 | s.EVMVersion = evmVersion 129 | s.Optimize = optimize 130 | args, err := s.makeArgs() 131 | if err != nil { 132 | return nil, err 133 | } 134 | args = append(args, "--", "-") 135 | cmd := exec.CommandContext(ctx, s.Path, args...) 136 | cmd.Stdin = strings.NewReader(source) 137 | return s.run(cmd, source) 138 | } 139 | 140 | func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) { 141 | var stderr, stdout bytes.Buffer 142 | cmd.Stderr = &stderr 143 | cmd.Stdout = &stdout 144 | if err := cmd.Run(); err != nil { 145 | return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) 146 | } 147 | args, err := s.makeArgs() 148 | if err != nil { 149 | return nil, err 150 | } 151 | return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(args, " ")) 152 | } 153 | 154 | func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { 155 | var output solcOutput 156 | if err := json.Unmarshal(combinedJSON, &output); err != nil { 157 | // Try to parse the output with the new solidity v.0.8.0 rules 158 | return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) 159 | } 160 | 161 | // Compilation succeeded, assemble and return the contracts. 162 | contracts := make(map[string]*Contract) 163 | for name, info := range output.Contracts { 164 | // Parse the individual compilation results. 165 | var abi interface{} 166 | if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { 167 | return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) 168 | } 169 | var userdoc interface{} 170 | if err := json.Unmarshal([]byte(info.Userdoc), &userdoc); err != nil { 171 | return nil, fmt.Errorf("solc: error reading user doc: %v", err) 172 | } 173 | var devdoc interface{} 174 | if err := json.Unmarshal([]byte(info.Devdoc), &devdoc); err != nil { 175 | return nil, fmt.Errorf("solc: error reading dev doc: %v", err) 176 | } 177 | contracts[name] = &Contract{ 178 | Code: "0x" + info.Bin, 179 | RuntimeCode: "0x" + info.BinRuntime, 180 | Info: ContractInfo{ 181 | Source: source, 182 | Language: "Solidity", 183 | LanguageVersion: languageVersion, 184 | CompilerVersion: compilerVersion, 185 | CompilerOptions: compilerOptions, 186 | SrcMap: info.SrcMap, 187 | SrcMapRuntime: info.SrcMapRuntime, 188 | AbiDefinition: abi, 189 | UserDoc: userdoc, 190 | DeveloperDoc: devdoc, 191 | Metadata: info.Metadata, 192 | }, 193 | } 194 | } 195 | return contracts, nil 196 | } 197 | 198 | // parseCombinedJSONV8 parses the direct output of solc --combined-output 199 | // and parses it using the rules from solidity v.0.8.0 and later. 200 | func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { 201 | var output solcOutputV8 202 | if err := json.Unmarshal(combinedJSON, &output); err != nil { 203 | return nil, err 204 | } 205 | // Compilation succeeded, assemble and return the contracts. 206 | contracts := make(map[string]*Contract) 207 | for name, info := range output.Contracts { 208 | contracts[name] = &Contract{ 209 | Code: "0x" + info.Bin, 210 | RuntimeCode: "0x" + info.BinRuntime, 211 | Hashes: info.Hashes, 212 | Info: ContractInfo{ 213 | Source: source, 214 | Language: "Solidity", 215 | LanguageVersion: languageVersion, 216 | CompilerVersion: compilerVersion, 217 | CompilerOptions: compilerOptions, 218 | SrcMap: info.SrcMap, 219 | SrcMapRuntime: info.SrcMapRuntime, 220 | AbiDefinition: info.Abi, 221 | UserDoc: info.Userdoc, 222 | DeveloperDoc: info.Devdoc, 223 | Metadata: info.Metadata, 224 | }, 225 | } 226 | } 227 | return contracts, nil 228 | } 229 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | "time" 7 | 8 | "github.com/gochain/gochain/v4/common" 9 | "github.com/gochain/gochain/v4/core/types" 10 | ) 11 | 12 | type CallMsg struct { 13 | From *common.Address // the sender of the 'transaction' 14 | To *common.Address // the destination contract (nil for contract creation) 15 | Gas uint64 // if 0, the call executes with near-infinite gas 16 | GasPrice *big.Int // wei <-> gas exchange ratio 17 | Value *big.Int // amount of wei sent along with the call 18 | Data []byte // input data, usually an ABI-encoded contract method invocation 19 | } 20 | 21 | type Snapshot struct { 22 | Number uint64 `json:"number"` 23 | Hash common.Hash `json:"hash"` 24 | Signers map[common.Address]uint64 `json:"signers"` 25 | Voters map[common.Address]struct{} `json:"voters"` 26 | Votes []*Vote `json:"votes"` 27 | Tally map[common.Address]Tally `json:"tally"` 28 | } 29 | 30 | type Vote struct { 31 | Signer common.Address `json:"signer"` 32 | Block uint64 `json:"block"` 33 | Address common.Address `json:"address"` 34 | Authorize bool `json:"authorize"` 35 | } 36 | 37 | type Tally struct { 38 | Authorize bool `json:"authorize"` 39 | Votes int `json:"votes"` 40 | } 41 | 42 | type ID struct { 43 | NetworkID *big.Int `json:"network_id"` 44 | ChainID *big.Int `json:"chain_id"` 45 | GenesisHash common.Hash `json:"genesis_hash"` 46 | } 47 | 48 | type Receipt struct { 49 | PostState []byte 50 | Status uint64 51 | CumulativeGasUsed uint64 52 | Bloom types.Bloom 53 | Logs []*types.Log 54 | TxHash common.Hash 55 | TxIndex uint64 56 | ContractAddress common.Address 57 | GasUsed uint64 58 | ParsedLogs []Event 59 | BlockHash common.Hash 60 | BlockNumber uint64 61 | From common.Address 62 | To *common.Address 63 | } 64 | 65 | func (r *Receipt) UnmarshalJSON(data []byte) error { 66 | var rr rpcReceipt 67 | err := json.Unmarshal(data, &rr) 68 | if err != nil { 69 | return err 70 | } 71 | return rr.copyTo(r) 72 | } 73 | 74 | func (r *Receipt) MarshalJSON() ([]byte, error) { 75 | var rr rpcReceipt 76 | rr.copyFrom(r) 77 | return json.Marshal(&rr) 78 | } 79 | 80 | type Block struct { 81 | ParentHash common.Hash 82 | Sha3Uncles common.Hash 83 | Miner common.Address 84 | Signers []common.Address 85 | Voters []common.Address 86 | Signer []byte 87 | StateRoot common.Hash 88 | TxsRoot common.Hash 89 | ReceiptsRoot common.Hash 90 | LogsBloom *types.Bloom 91 | Difficulty *big.Int 92 | TotalDifficulty *big.Int 93 | Number *big.Int 94 | GasLimit uint64 95 | GasUsed uint64 96 | Timestamp time.Time 97 | ExtraData []byte 98 | MixHash common.Hash 99 | Nonce types.BlockNonce 100 | Hash common.Hash 101 | 102 | // Only one of TxHashes or TxDetails will be populated. 103 | TxHashes []common.Hash 104 | TxDetails []*Transaction 105 | 106 | Uncles []common.Hash 107 | } 108 | 109 | func (b *Block) UnmarshalJSON(data []byte) error { 110 | var r rpcBlock 111 | err := json.Unmarshal(data, &r) 112 | if err != nil { 113 | return err 114 | } 115 | return r.copyTo(b) 116 | } 117 | 118 | func (b *Block) MarshalJSON() ([]byte, error) { 119 | var r rpcBlock 120 | if err := r.copyFrom(b); err != nil { 121 | return nil, err 122 | } 123 | return json.Marshal(&r) 124 | } 125 | 126 | func (b *Block) ExtraVanity() string { 127 | l := len(b.ExtraData) 128 | if l > 32 { 129 | l = 32 130 | } 131 | return string(b.ExtraData[:l]) 132 | } 133 | 134 | func (b *Block) TxCount() int { 135 | if b.TxHashes != nil { 136 | return len(b.TxHashes) 137 | } 138 | return len(b.TxDetails) 139 | } 140 | 141 | type Transaction struct { 142 | Nonce uint64 143 | GasPrice *big.Int // wei 144 | GasLimit uint64 145 | To *common.Address 146 | Value *big.Int // wei 147 | Input []byte 148 | From common.Address 149 | V *big.Int 150 | R *big.Int 151 | S *big.Int 152 | Hash common.Hash 153 | 154 | BlockNumber *big.Int 155 | BlockHash common.Hash 156 | TransactionIndex uint64 157 | } 158 | type Event struct { 159 | Name string `json:"name"` 160 | Fields map[string]interface{} `json:"fields"` 161 | } 162 | 163 | func (t *Transaction) UnmarshalJSON(data []byte) error { 164 | var r rpcTransaction 165 | err := json.Unmarshal(data, &r) 166 | if err != nil { 167 | return err 168 | } 169 | return r.copyTo(t) 170 | } 171 | 172 | func (t *Transaction) MarshalJSON() ([]byte, error) { 173 | var r rpcTransaction 174 | r.copyFrom(t) 175 | return json.Marshal(&r) 176 | } 177 | -------------------------------------------------------------------------------- /vc/vc.go: -------------------------------------------------------------------------------- 1 | // package vc contains an implementation of the W3 Verifiable Credentials data model. 2 | // 3 | // https://www.w3.org/TR/verifiable-claims-data-model 4 | package vc 5 | 6 | import ( 7 | "time" 8 | ) 9 | 10 | // VerifiableCredential represents one or more claims made by the same entity. 11 | // 12 | // https://www.w3.org/TR/verifiable-claims-data-model/#credentials 13 | type VerifiableCredential struct { 14 | Context []string `json:"@context,omitempty"` 15 | ID string `json:"id,omitempty"` 16 | Type []string `json:"type,omitempty"` 17 | Issuer string `json:"issuer,omitempty"` 18 | IssuanceDate *time.Time `json:"issuanceDate,omitempty"` 19 | CredentialSubject map[string]interface{} `json:"credentialSubject,omitempty"` 20 | Proof *Proof `json:"proof,omitempty"` 21 | } 22 | 23 | // NewVerifiableCredential returns a new instance of VerifiableCredential 24 | // with the default context and type assigned. 25 | func NewVerifiableCredential() *VerifiableCredential { 26 | return &VerifiableCredential{ 27 | Context: []string{"https://www.w3.org/2018/credentials/v1"}, 28 | Type: []string{"VerifiableCredential"}, 29 | } 30 | } 31 | 32 | // VerifiablePresentation combines one or more credentials. 33 | // 34 | // https://www.w3.org/TR/verifiable-claims-data-model/#presentations 35 | type VerifiablePresentation struct { 36 | Context []string `json:"@context,omitempty"` 37 | ID string `json:"id,omitempty"` 38 | Type []string `json:"type,omitempty"` 39 | VerifiableCredential []*VerifiableCredential `json:"verifiableCredential,omitempty"` 40 | Proof *Proof `json:"proof,omitempty"` 41 | } 42 | 43 | // NewVerifiablePresentation returns a new instance of VerifiablePresentation 44 | // with the default context and type assigned. 45 | func NewVerifiablePresentation() *VerifiablePresentation { 46 | return &VerifiablePresentation{ 47 | Context: []string{"https://www.w3.org/2018/credentials/v1"}, 48 | Type: []string{"VerifiablePresentation"}, 49 | } 50 | } 51 | 52 | // Proof represents a proof for a verifiable credential or presentation. 53 | // 54 | // https://www.w3.org/TR/verifiable-claims-data-model/#proofs-signatures 55 | type Proof struct { 56 | Type string `json:"type,omitempty"` 57 | Created *time.Time `json:"created,omitempty"` 58 | Creator string `json:"creator,omitempty"` 59 | ProofValue string `json:"proofValue,omitempty"` 60 | } 61 | -------------------------------------------------------------------------------- /vyper/ERC20.vy: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | ## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR! 3 | ########################################################################### 4 | 5 | # @dev example implementation of an ERC20 token 6 | # @author Takayuki Jimba (@yudetamago) 7 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 8 | 9 | from vyper.interfaces import ERC20 10 | from vyper.interfaces import ERC20Detailed 11 | 12 | implements: ERC20 13 | implements: ERC20Detailed 14 | 15 | event Transfer: 16 | sender: indexed(address) 17 | receiver: indexed(address) 18 | value: uint256 19 | 20 | event Approval: 21 | owner: indexed(address) 22 | spender: indexed(address) 23 | value: uint256 24 | 25 | name: public(String[32]) 26 | symbol: public(String[32]) 27 | decimals: public(uint8) 28 | 29 | # NOTE: By declaring `balanceOf` as public, vyper automatically generates a 'balanceOf()' getter 30 | # method to allow access to account balances. 31 | # The _KeyType will become a required parameter for the getter and it will return _ValueType. 32 | # See: https://vyper.readthedocs.io/en/v0.1.0-beta.8/types.html?highlight=getter#mappings 33 | balanceOf: public(HashMap[address, uint256]) 34 | # By declaring `allowance` as public, vyper automatically generates the `allowance()` getter 35 | allowance: public(HashMap[address, HashMap[address, uint256]]) 36 | # By declaring `totalSupply` as public, we automatically create the `totalSupply()` getter 37 | totalSupply: public(uint256) 38 | minter: address 39 | 40 | 41 | @external 42 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 43 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 44 | self.name = _name 45 | self.symbol = _symbol 46 | self.decimals = _decimals 47 | self.balanceOf[msg.sender] = init_supply 48 | self.totalSupply = init_supply 49 | self.minter = msg.sender 50 | log Transfer(empty(address), msg.sender, init_supply) 51 | 52 | 53 | 54 | @external 55 | def transfer(_to : address, _value : uint256) -> bool: 56 | """ 57 | @dev Transfer token for a specified address 58 | @param _to The address to transfer to. 59 | @param _value The amount to be transferred. 60 | """ 61 | # NOTE: vyper does not allow underflows 62 | # so the following subtraction would revert on insufficient balance 63 | self.balanceOf[msg.sender] -= _value 64 | self.balanceOf[_to] += _value 65 | log Transfer(msg.sender, _to, _value) 66 | return True 67 | 68 | 69 | @external 70 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 71 | """ 72 | @dev Transfer tokens from one address to another. 73 | @param _from address The address which you want to send tokens from 74 | @param _to address The address which you want to transfer to 75 | @param _value uint256 the amount of tokens to be transferred 76 | """ 77 | # NOTE: vyper does not allow underflows 78 | # so the following subtraction would revert on insufficient balance 79 | self.balanceOf[_from] -= _value 80 | self.balanceOf[_to] += _value 81 | # NOTE: vyper does not allow underflows 82 | # so the following subtraction would revert on insufficient allowance 83 | self.allowance[_from][msg.sender] -= _value 84 | log Transfer(_from, _to, _value) 85 | return True 86 | 87 | 88 | @external 89 | def approve(_spender : address, _value : uint256) -> bool: 90 | """ 91 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 92 | Beware that changing an allowance with this method brings the risk that someone may use both the old 93 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 94 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 95 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 96 | @param _spender The address which will spend the funds. 97 | @param _value The amount of tokens to be spent. 98 | """ 99 | self.allowance[msg.sender][_spender] = _value 100 | log Approval(msg.sender, _spender, _value) 101 | return True 102 | 103 | 104 | @external 105 | def mint(_to: address, _value: uint256): 106 | """ 107 | @dev Mint an amount of the token and assigns it to an account. 108 | This encapsulates the modification of balances such that the 109 | proper events are emitted. 110 | @param _to The account that will receive the created tokens. 111 | @param _value The amount that will be created. 112 | """ 113 | assert msg.sender == self.minter 114 | assert _to != empty(address) 115 | self.totalSupply += _value 116 | self.balanceOf[_to] += _value 117 | log Transfer(empty(address), _to, _value) 118 | 119 | 120 | @internal 121 | def _burn(_to: address, _value: uint256): 122 | """ 123 | @dev Internal function that burns an amount of the token of a given 124 | account. 125 | @param _to The account whose tokens will be burned. 126 | @param _value The amount that will be burned. 127 | """ 128 | assert _to != empty(address) 129 | self.totalSupply -= _value 130 | self.balanceOf[_to] -= _value 131 | log Transfer(_to, empty(address), _value) 132 | 133 | 134 | @external 135 | def burn(_value: uint256): 136 | """ 137 | @dev Burn an amount of the token of msg.sender. 138 | @param _value The amount that will be burned. 139 | """ 140 | self._burn(msg.sender, _value) 141 | 142 | 143 | @external 144 | def burnFrom(_to: address, _value: uint256): 145 | """ 146 | @dev Burn an amount of the token from a given account. 147 | @param _to The account whose tokens will be burned. 148 | @param _value The amount that will be burned. 149 | """ 150 | self.allowance[_to][msg.sender] -= _value 151 | self._burn(_to, _value) 152 | -------------------------------------------------------------------------------- /vyper/compiler.go: -------------------------------------------------------------------------------- 1 | package vyper 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | ) 7 | 8 | // TODO 9 | func InstallVyper(contractName string) { 10 | cmd := exec.Command("pip", "install", "vyper") 11 | output, err := cmd.Output() 12 | fmt.Println(output) 13 | if err != nil { 14 | fmt.Println("*Web3.go Vyper Engine Error* Unable to install vyper compiler") 15 | } 16 | } 17 | 18 | // TODO 19 | // NOTE this function does not work, it should call the actual local compiler function 20 | // located in /vyper-go 21 | func SubProcessCompilerLocal(fileName string) { 22 | cmd := exec.Command("python3", fileName) 23 | output, err := cmd.Output() 24 | fmt.Println(output) 25 | if err != nil { 26 | // add error handling file 27 | } 28 | } 29 | 30 | func DetectVyper(langOption string) bool { 31 | fmt.Println(langOption) 32 | return true 33 | } 34 | 35 | func VyperVersion() string { 36 | cmd := exec.Command("vyper", "--version") 37 | output, err := cmd.Output() 38 | if err != nil { 39 | 40 | } 41 | return ConvertByteArray(output) 42 | 43 | } 44 | 45 | func Compile(contractNamePath string) string { 46 | cmd := exec.Command("vyper", contractNamePath) 47 | output, err := cmd.Output() 48 | if err != nil { 49 | 50 | } 51 | 52 | return ConvertByteArray(output) 53 | } 54 | 55 | // TODO add switch statement 56 | // Current Options: 57 | // abi, bytecode, ir, asm, source_map 58 | func CompileWithOptions(contractNamePath string, option string) { 59 | cmd := exec.Command("vyper", "-f", option, contractNamePath) 60 | output, err := cmd.Output() 61 | if err != nil { 62 | 63 | } 64 | fmt.Println(ConvertByteArray(output)) 65 | } 66 | 67 | var localVyperVersion string = VyperVersion() 68 | 69 | func CompileFromCLI(filepath string, option string) { 70 | fmt.Println("Vyper Compiler v", localVyperVersion) 71 | fmt.Println("Building", filepath, "\t", "with output options") 72 | CompileWithOptions(filepath, option) 73 | 74 | } 75 | 76 | //func main() { 77 | 78 | // fmt.Println("test") 79 | // fmt.Println("mod") 80 | // subProcessCompile() 81 | 82 | //} 83 | -------------------------------------------------------------------------------- /vyper/examples/ERC20.vy: -------------------------------------------------------------------------------- 1 | # @dev Implementation of ERC-20 token standard. 2 | # @author Takayuki Jimba (@yudetamago) 3 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 4 | 5 | from vyper.interfaces import ERC20 6 | from vyper.interfaces import ERC20Detailed 7 | 8 | implements: ERC20 9 | implements: ERC20Detailed 10 | 11 | event Transfer: 12 | sender: indexed(address) 13 | receiver: indexed(address) 14 | value: uint256 15 | 16 | event Approval: 17 | owner: indexed(address) 18 | spender: indexed(address) 19 | value: uint256 20 | 21 | name: public(String[32]) 22 | symbol: public(String[32]) 23 | decimals: public(uint8) 24 | 25 | # NOTE: By declaring `balanceOf` as public, vyper automatically generates a 'balanceOf()' getter 26 | # method to allow access to account balances. 27 | # The _KeyType will become a required parameter for the getter and it will return _ValueType. 28 | # See: https://vyper.readthedocs.io/en/v0.1.0-beta.8/types.html?highlight=getter#mappings 29 | balanceOf: public(HashMap[address, uint256]) 30 | # By declaring `allowance` as public, vyper automatically generates the `allowance()` getter 31 | allowance: public(HashMap[address, HashMap[address, uint256]]) 32 | # By declaring `totalSupply` as public, we automatically create the `totalSupply()` getter 33 | totalSupply: public(uint256) 34 | minter: address 35 | 36 | 37 | @external 38 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 39 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 40 | self.name = _name 41 | self.symbol = _symbol 42 | self.decimals = _decimals 43 | self.balanceOf[msg.sender] = init_supply 44 | self.totalSupply = init_supply 45 | self.minter = msg.sender 46 | log Transfer(empty(address), msg.sender, init_supply) 47 | 48 | 49 | 50 | @external 51 | def transfer(_to : address, _value : uint256) -> bool: 52 | """ 53 | @dev Transfer token for a specified address 54 | @param _to The address to transfer to. 55 | @param _value The amount to be transferred. 56 | """ 57 | # NOTE: vyper does not allow underflows 58 | # so the following subtraction would revert on insufficient balance 59 | self.balanceOf[msg.sender] -= _value 60 | self.balanceOf[_to] += _value 61 | log Transfer(msg.sender, _to, _value) 62 | return True 63 | 64 | 65 | @external 66 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 67 | """ 68 | @dev Transfer tokens from one address to another. 69 | @param _from address The address which you want to send tokens from 70 | @param _to address The address which you want to transfer to 71 | @param _value uint256 the amount of tokens to be transferred 72 | """ 73 | # NOTE: vyper does not allow underflows 74 | # so the following subtraction would revert on insufficient balance 75 | self.balanceOf[_from] -= _value 76 | self.balanceOf[_to] += _value 77 | # NOTE: vyper does not allow underflows 78 | # so the following subtraction would revert on insufficient allowance 79 | self.allowance[_from][msg.sender] -= _value 80 | log Transfer(_from, _to, _value) 81 | return True 82 | 83 | 84 | @external 85 | def approve(_spender : address, _value : uint256) -> bool: 86 | """ 87 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 88 | Beware that changing an allowance with this method brings the risk that someone may use both the old 89 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 90 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 91 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 92 | @param _spender The address which will spend the funds. 93 | @param _value The amount of tokens to be spent. 94 | """ 95 | self.allowance[msg.sender][_spender] = _value 96 | log Approval(msg.sender, _spender, _value) 97 | return True 98 | 99 | 100 | @external 101 | def mint(_to: address, _value: uint256): 102 | """ 103 | @dev Mint an amount of the token and assigns it to an account. 104 | This encapsulates the modification of balances such that the 105 | proper events are emitted. 106 | @param _to The account that will receive the created tokens. 107 | @param _value The amount that will be created. 108 | """ 109 | assert msg.sender == self.minter 110 | assert _to != empty(address) 111 | self.totalSupply += _value 112 | self.balanceOf[_to] += _value 113 | log Transfer(empty(address), _to, _value) 114 | 115 | 116 | @internal 117 | def _burn(_to: address, _value: uint256): 118 | """ 119 | @dev Internal function that burns an amount of the token of a given 120 | account. 121 | @param _to The account whose tokens will be burned. 122 | @param _value The amount that will be burned. 123 | """ 124 | assert _to != empty(address) 125 | self.totalSupply -= _value 126 | self.balanceOf[_to] -= _value 127 | log Transfer(_to, empty(address), _value) 128 | 129 | 130 | @external 131 | def burn(_value: uint256): 132 | """ 133 | @dev Burn an amount of the token of msg.sender. 134 | @param _value The amount that will be burned. 135 | """ 136 | self._burn(msg.sender, _value) 137 | 138 | 139 | @external 140 | def burnFrom(_to: address, _value: uint256): 141 | """ 142 | @dev Burn an amount of the token from a given account. 143 | @param _to The account whose tokens will be burned. 144 | @param _value The amount that will be burned. 145 | """ 146 | self.allowance[_to][msg.sender] -= _value 147 | self._burn(_to, _value) 148 | -------------------------------------------------------------------------------- /vyper/examples/voting.vy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gochain/web3/204046cc87e4f1da9c3c2185f99a928bc63b8aaa/vyper/examples/voting.vy -------------------------------------------------------------------------------- /vyper/generate.go: -------------------------------------------------------------------------------- 1 | package vyper 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | ) 8 | 9 | func GenerateVyperERC20Token() string { 10 | vyperCode := (`# @dev Implementation of ERC-20 token standard. 11 | # @author Takayuki Jimba (@yudetamago) 12 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 13 | 14 | from vyper.interfaces import ERC20 15 | from vyper.interfaces import ERC20Detailed 16 | 17 | implements: ERC20 18 | implements: ERC20Detailed 19 | 20 | event Transfer: 21 | sender: indexed(address) 22 | receiver: indexed(address) 23 | value: uint256 24 | 25 | event Approval: 26 | owner: indexed(address) 27 | spender: indexed(address) 28 | value: uint256 29 | 30 | name: public(String[32]) 31 | symbol: public(String[32]) 32 | decimals: public(uint8) 33 | 34 | balanceOf: public(HashMap[address, uint256]) 35 | allowance: public(HashMap[address, HashMap[address, uint256]]) 36 | totalSupply: public(uint256) 37 | minter: address 38 | 39 | @external 40 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 41 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 42 | self.name = _name 43 | self.symbol = _symbol 44 | self.decimals = _decimals 45 | self.balanceOf[msg.sender] = init_supply 46 | self.totalSupply = init_supply 47 | self.minter = msg.sender 48 | log Transfer(empty(address), msg.sender, init_supply) 49 | 50 | @external 51 | def transfer(_to : address, _value : uint256) -> bool: 52 | self.balanceOf[msg.sender] -= _value 53 | self.balanceOf[_to] += _value 54 | log Transfer(msg.sender, _to, _value) 55 | return True 56 | 57 | @external 58 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 59 | self.balanceOf[_from] -= _value 60 | self.balanceOf[_to] += _value 61 | self.allowance[_from][msg.sender] -= _value 62 | log Transfer(_from, _to, _value) 63 | return True 64 | 65 | @external 66 | def approve(_spender : address, _value : uint256) -> bool: 67 | self.allowance[msg.sender][_spender] = _value 68 | log Approval(msg.sender, _spender, _value) 69 | return True 70 | 71 | @external 72 | def mint(_to: address, _value: uint256): 73 | assert msg.sender == self.minter 74 | assert _to != empty(address) 75 | self.totalSupply += _value 76 | self.balanceOf[_to] += _value 77 | log Transfer(empty(address), _to, _value) 78 | 79 | @internal 80 | def _burn(_to: address, _value: uint256): 81 | assert _to != empty(address) 82 | self.totalSupply -= _value 83 | self.balanceOf[_to] -= _value 84 | log Transfer(_to, empty(address), _value) 85 | 86 | @external 87 | def burn(_value: uint256): 88 | self._burn(msg.sender, _value) 89 | 90 | @external 91 | def burnFrom(_to: address, _value: uint256): 92 | self.allowance[_to][msg.sender] -= _value 93 | self._burn(_to, _value)`) 94 | 95 | return vyperCode 96 | } 97 | 98 | func WriteToFile(name string, code string) { 99 | filePath := (name+".vy") 100 | 101 | // Specify the string to write to the file 102 | 103 | // Create a new file or truncate an existing file 104 | file, err := os.Create(filePath) 105 | if err != nil { 106 | fmt.Println("Error creating file:", err) 107 | } 108 | defer file.Close() 109 | 110 | // Write the string to the file 111 | _, err = file.WriteString(code) 112 | if err != nil { 113 | fmt.Println("Error writing to file:", err) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /vyper/utils.go: -------------------------------------------------------------------------------- 1 | package vyper 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | func ConvertByteArray(byteArray []byte) string { 9 | var result string 10 | for len(byteArray) > 0 { 11 | r, size := utf8.DecodeRune(byteArray) 12 | if r == utf8.RuneError && size == 1 { 13 | fmt.Println("Error decoding rune.") 14 | break 15 | } 16 | // Add the rune to the result string 17 | result += string(r) 18 | // Move to the next rune in the byte array 19 | byteArray = byteArray[size:] 20 | } 21 | return result 22 | } 23 | -------------------------------------------------------------------------------- /web3_test.go: -------------------------------------------------------------------------------- 1 | package web3 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/gochain/gochain/v4/accounts/abi" 10 | "github.com/gochain/gochain/v4/common" 11 | ) 12 | 13 | func Test_parseParam(t *testing.T) { 14 | const addr = "0xa25b5e2d2d63dad7fa940e239925f29320f5103d" 15 | const hash = "0x0123456789012345678901234567890101234567890123456789012345678901" 16 | var bytes4 = hash[:10] 17 | var bytes4Array [4]byte 18 | copy(bytes4Array[:], common.FromHex(bytes4)) 19 | tests := []struct { 20 | name string 21 | t byte 22 | s int 23 | param interface{} 24 | 25 | want interface{} 26 | wantErr bool 27 | }{ 28 | {"int256<-int", abi.IntTy, 256, 1, big.NewInt(1), false}, 29 | {"int256<-big.Int", abi.IntTy, 256, big.NewInt(1), big.NewInt(1), false}, 30 | 31 | {"uint256<-int", abi.UintTy, 256, 1, big.NewInt(1), false}, 32 | {"uint256<-big.Int", abi.UintTy, 256, big.NewInt(1), big.NewInt(1), false}, 33 | 34 | {"int8<-int", abi.IntTy, 8, 1, int8(1), false}, 35 | {"int8<-big.Int", abi.IntTy, 8, big.NewInt(1), int8(1), false}, 36 | 37 | {"uint8<-int", abi.UintTy, 8, 1, uint8(1), false}, 38 | {"uint8<-big.Int", abi.UintTy, 8, big.NewInt(1), uint8(1), false}, 39 | 40 | {"int256<-hex", abi.IntTy, 256, "0x1", big.NewInt(1), false}, 41 | {"int256<-string", abi.IntTy, 256, "1", big.NewInt(1), false}, 42 | 43 | {"uint256<-zero", abi.UintTy, 256, "0", big.NewInt(0), false}, 44 | {"uint256<-json", abi.UintTy, 64, json.Number("10000000000000001"), uint64(10000000000000001), false}, 45 | 46 | {"address<-address", abi.AddressTy, 0, common.HexToAddress(addr), common.HexToAddress(addr), false}, 47 | {"address<-hex", abi.AddressTy, 0, addr, common.HexToAddress(addr), false}, 48 | 49 | {"hash<-hash", abi.FixedBytesTy, 32, common.HexToHash(hash), common.HexToHash(hash), false}, 50 | {"hash<-hex", abi.FixedBytesTy, 32, hash, common.HexToHash(hash), false}, 51 | 52 | {"[4]byte<-[4]bytes", abi.FixedBytesTy, 4, bytes4Array, bytes4Array, false}, 53 | {"[4]byte<-hex", abi.FixedBytesTy, 4, bytes4, bytes4Array, false}, 54 | 55 | {"bytes<-bytes", abi.BytesTy, 0, common.Hex2Bytes("1234"), common.Hex2Bytes("1234"), false}, 56 | {"bytes<-hex", abi.BytesTy, 0, "0x1234", common.Hex2Bytes("1234"), false}, 57 | 58 | // Error cases: 59 | {"uint256<-negative", abi.UintTy, 256, -1, nil, true}, 60 | {"uint8<-negative", abi.UintTy, 8, -1, nil, true}, 61 | {"int256<-float64", abi.IntTy, 256, float64(1), nil, true}, 62 | {"uint8<-float", abi.UintTy, 8, 1.1, nil, true}, 63 | {"uint8<-negative-float", abi.UintTy, 8, -1.1, nil, true}, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | got, err := ConvertArgument(tt.t, tt.s, tt.param) 68 | if (err != nil) != tt.wantErr { 69 | t.Errorf("wantErr %v; error = %v", tt.wantErr, err) 70 | return 71 | } 72 | if !reflect.DeepEqual(got, tt.want) { 73 | t.Errorf("got (%T): %v; want (%T): %v", got, got, tt.want, tt.want) 74 | } 75 | }) 76 | } 77 | } 78 | 79 | func TestParseGwei(t *testing.T) { 80 | for _, tt := range []struct { 81 | val string 82 | exp *big.Int 83 | expErr bool 84 | }{ 85 | {val: "1", exp: weiPerGwei}, 86 | {val: "10", exp: Gwei(10)}, 87 | {val: "1.1", exp: new(big.Int).Add(Gwei(1), big.NewInt(100000000))}, 88 | {val: "100000", exp: Gwei(100000)}, 89 | {val: "1.000000001", exp: new(big.Int).Add(Gwei(1), big.NewInt(1))}, 90 | {val: "1.0000000001", expErr: true}, 91 | } { 92 | t.Run(tt.val, func(t *testing.T) { 93 | got, err := ParseGwei(tt.val) 94 | if err != nil { 95 | if !tt.expErr { 96 | t.Errorf("unexpected error: %v", err) 97 | } 98 | return 99 | } 100 | if tt.expErr { 101 | t.Errorf("expected error, but got: %s", got) 102 | return 103 | } 104 | if got.Cmp(tt.exp) != 0 { 105 | t.Errorf("expected %s but got %s", tt.exp, got) 106 | } 107 | }) 108 | } 109 | } 110 | --------------------------------------------------------------------------------