├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum └── multicall ├── multicall.go ├── multicall_test.go ├── options.go ├── types.go ├── viewcall.go └── viewcall_test.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 # use CircleCI 2.0 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.12 6 | 7 | environment: # environment variables for the build itself 8 | TEST_RESULTS: /tmp/test-results # path to where test results will be saved 9 | 10 | steps: 11 | - checkout 12 | - run: mkdir -p $TEST_RESULTS 13 | 14 | - restore_cache: 15 | keys: 16 | - go-mod-v0-{{ checksum "go.sum" }} 17 | 18 | 19 | - run: 20 | name: Run unit tests 21 | command: | 22 | PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) 23 | gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES 24 | 25 | - save_cache: 26 | key: go-mod-v0-{{ checksum "go.sum" }} 27 | paths: 28 | - "/go/pkg/mod" 29 | 30 | - store_artifacts: 31 | path: /tmp/test-results 32 | destination: raw-test-output 33 | 34 | - store_test_results: 35 | path: /tmp/test-results 36 | 37 | workflows: 38 | version: 2 39 | build-workflow: 40 | jobs: 41 | - build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | vendor/ 16 | 17 | # Env files 18 | .env 19 | 20 | # IDE local settings 21 | .idea/ 22 | .vscode/ 23 | 24 | # Debug binaries 25 | debug 26 | 27 | # OSX 28 | .DS_Store 29 | .DS_Store? 30 | ._* 31 | .Spotlight-V100 32 | .Trashes 33 | ehthumbs.db 34 | Thumbs.db 35 | 36 | # SublimeText 37 | *.sublime-project 38 | *.sublime-workspace 39 | 40 | # Binaries 41 | multicall-go 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alethio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/Alethio/multicall-go.svg?style=svg)](https://circleci.com/gh/Alethio/multicall-go) 2 | 3 | ### Multicall 4 | 5 | Wrapper for [Multicall](https://github.com/bowd/multicall) which batches calls to contract 6 | view functions into a single call and reads all the state in one EVM round-trip. 7 | 8 | ### Usage 9 | 10 | The library is used in conjunction with [web3-go](https://github.com/Alethio/web3-go), and the first parameter to `multicall.New` is an `ethrpc.ETHInterface` as defined in the package. 11 | 12 | #### Initialization 13 | 14 | The library requires the [Multicall](https://github.com/bowd/multicall) contract to pe deployed on the target chain. 15 | We have deployed two variants on Mainnet and Ropsten so far which can be used by using the provided configs. 16 | 17 | 18 | ```go 19 | // Mainnet 20 | mc, err := multicall.New(eth, multicall.ContractAddress(multicall.MainnetAddress)) 21 | // Ropsten 22 | mc, err := multicall.New(eth, multicall.ContractAddress(multicall.RopstenAddress)) 23 | ``` 24 | 25 | 26 | You can also set the gas used for the read transaction: 27 | 28 | ```go 29 | mc, err := multicall.New(eth, multicall.ContractAddress(multicall.RopstenAddress), multicall.SetGas(40000)) 30 | ``` 31 | 32 | In this case the contract deployed has to maintain the same function signature as the original one. 33 | 34 | #### Calling 35 | 36 | ```go 37 | vcs := ViewCalls{ 38 | multicall.NewViewCall( 39 | "key-1", 40 | "0x5eb3fa2dfecdde21c950813c665e9364fa609bd2", 41 | "getLastBlockHash()(bytes32)", 42 | []interface{}{}, 43 | ), 44 | multicall.NewViewCall( 45 | "key-2", 46 | "0x6b175474e89094c44da98b954eedeac495271d0f", 47 | "balanceOf(address)(uint256)", 48 | []interface{}{"0x8134d518e0cef5388136c0de43d7e12278701ac5"}, 49 | ), 50 | } 51 | block := "latest" // default block parameter 52 | res, err := mc.Call(vcs, block) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | lastBlockHashSuccess = res.Calls["key-1"].Success; 58 | lastBlockHash := res.Calls["key-1"].Decoded[0].([32]byte); 59 | 60 | someBalanceSuccess := res.Calls["key-2"].Success; 61 | someBalance := res.Calls["key-2"].Decoded[0].(*multicall.BigIntJSONString); 62 | someBalanceInt := big.Int(*someBalance); 63 | ``` 64 | 65 | In the example above we batch two calls to two different contracts and get back a map of `CallResults` which contain the exit value an array of returned values (`[]interface{}`) which are decoded by the `go-ethereum` package. 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alethio/web3-multicall-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/alethio/web3-go v0.0.6 7 | github.com/ethereum/go-ethereum v1.9.25 8 | github.com/stretchr/testify v1.4.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= 2 | github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= 3 | github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= 4 | github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= 5 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 6 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 7 | github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= 8 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 9 | github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= 10 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 11 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 12 | github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= 13 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 14 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 18 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 19 | github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= 20 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 21 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 22 | github.com/alethio/ethmock v0.0.0-20190607140831-ce4476424f5c h1:XA7Gxh8x+2pSB+7wi0FPT3Y0PdQJXHkxskEYS6Zewpk= 23 | github.com/alethio/ethmock v0.0.0-20190607140831-ce4476424f5c/go.mod h1:Lpp9nEVo/nKuq0uf/Le6r1zjaiW4/tu0maG47HBE65A= 24 | github.com/alethio/web3-go v0.0.6 h1:ZEDJY57OKq9tCmdkxMyy8M11mthX33GmFCzZ8kYuMY0= 25 | github.com/alethio/web3-go v0.0.6/go.mod h1:tnrqWtLdde8ttsGwiiJ6VwmA9WQwI3sFbmyNjMQVT8M= 26 | github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= 27 | github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= 28 | github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 29 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 30 | github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= 31 | github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= 32 | github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= 33 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 34 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 35 | github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= 36 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 37 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 39 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 41 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 42 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 43 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 44 | github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 45 | github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= 46 | github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= 47 | github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 48 | github.com/ethereum/go-ethereum v1.9.25 h1:mMiw/zOOtCLdGLWfcekua0qPrJTe7FVIiHJ4IKNTfR0= 49 | github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= 50 | github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 51 | github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= 52 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 53 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 54 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= 55 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 56 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 57 | github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= 58 | github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 59 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 60 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 61 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 62 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 63 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 64 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 65 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 66 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 67 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 68 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 69 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 70 | github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 71 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 72 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 73 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 74 | github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 75 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 76 | github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= 77 | github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 78 | github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= 79 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 80 | github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= 81 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 82 | github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= 83 | github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 84 | github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= 85 | github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= 86 | github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= 87 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 88 | github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 89 | github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= 90 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 91 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 92 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 93 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 94 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 95 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 96 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 97 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 98 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 99 | github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 100 | github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= 101 | github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= 102 | github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 103 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 104 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 105 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 106 | github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= 107 | github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= 108 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 109 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 110 | github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 111 | github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 112 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 113 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 114 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 115 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 116 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 117 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 118 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 119 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 120 | github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= 121 | github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= 122 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 123 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 124 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 125 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 126 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 127 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 128 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 129 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 130 | github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 131 | github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= 132 | github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 133 | github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= 134 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 135 | github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 136 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 137 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 138 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 139 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 140 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= 141 | github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= 142 | github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= 143 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 144 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 145 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 146 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 147 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 148 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 149 | github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= 150 | github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= 151 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 152 | github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= 153 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 154 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 155 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 156 | golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 157 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 158 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 159 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 160 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 161 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 162 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 163 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 164 | golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= 165 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 166 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 167 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 168 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 169 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 171 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 172 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 173 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 174 | golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 175 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 176 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 178 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 179 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 180 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 181 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 182 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 183 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 184 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 185 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= 191 | golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 193 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 194 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 195 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 196 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 197 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 198 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 199 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 200 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 201 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 202 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 203 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 204 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 205 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 206 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 207 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 208 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 209 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 210 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 211 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 212 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 213 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 214 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 215 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 216 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 217 | gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= 218 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 219 | gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= 220 | gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8/go.mod h1:cKXr3E0k4aosgycml1b5z33BVV6hai1Kh7uDgFOkbcs= 221 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 222 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 223 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 224 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 225 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 226 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 227 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 228 | -------------------------------------------------------------------------------- /multicall/multicall.go: -------------------------------------------------------------------------------- 1 | package multicall 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/alethio/web3-go/ethrpc" 6 | ) 7 | 8 | type Multicall interface { 9 | CallRaw(calls ViewCalls, block string) (*Result, error) 10 | Call(calls ViewCalls, block string) (*Result, error) 11 | Contract() string 12 | } 13 | 14 | type multicall struct { 15 | eth ethrpc.ETHInterface 16 | config *Config 17 | } 18 | 19 | func New(eth ethrpc.ETHInterface, opts ...Option) (Multicall, error) { 20 | config := &Config{ 21 | MulticallAddress: MainnetAddress, 22 | Gas: "0x400000000", 23 | } 24 | 25 | for _, opt := range opts { 26 | opt(config) 27 | } 28 | 29 | return &multicall{ 30 | eth: eth, 31 | config: config, 32 | }, nil 33 | } 34 | 35 | type CallResult struct { 36 | Success bool 37 | Raw []byte 38 | Decoded []interface{} 39 | } 40 | 41 | type Result struct { 42 | BlockNumber uint64 43 | Calls map[string]CallResult 44 | } 45 | 46 | const AggregateMethod = "0x17352e13" 47 | 48 | func (mc multicall) CallRaw(calls ViewCalls, block string) (*Result, error) { 49 | resultRaw, err := mc.makeRequest(calls, block) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return calls.decodeRaw(resultRaw) 54 | } 55 | 56 | func (mc multicall) Call(calls ViewCalls, block string) (*Result, error) { 57 | resultRaw, err := mc.makeRequest(calls, block) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return calls.decode(resultRaw) 62 | } 63 | 64 | func (mc multicall) makeRequest(calls ViewCalls, block string) (string, error) { 65 | payloadArgs, err := calls.callData() 66 | if err != nil { 67 | return "", err 68 | } 69 | payload := make(map[string]string) 70 | payload["to"] = mc.config.MulticallAddress 71 | payload["data"] = AggregateMethod + hex.EncodeToString(payloadArgs) 72 | payload["gas"] = mc.config.Gas 73 | var resultRaw string 74 | err = mc.eth.MakeRequest(&resultRaw, ethrpc.ETHCall, payload, block) 75 | return resultRaw, err 76 | } 77 | 78 | func (mc multicall) Contract() string { 79 | return mc.config.MulticallAddress 80 | } 81 | -------------------------------------------------------------------------------- /multicall/multicall_test.go: -------------------------------------------------------------------------------- 1 | package multicall_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/alethio/web3-go/ethrpc" 7 | "github.com/alethio/web3-go/ethrpc/provider/httprpc" 8 | "github.com/alethio/web3-multicall-go/multicall" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestExampleViwCall(t *testing.T) { 14 | eth, err := getETH("https://mainnet.infura.io/v3/17ed7fe26d014e5b9be7dfff5368c69d") 15 | vc := multicall.NewViewCall( 16 | "key.1", 17 | "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643", 18 | "totalReserves()(uint256)", 19 | []interface{}{}, 20 | ) 21 | vcs := multicall.ViewCalls{vc} 22 | mc, _ := multicall.New(eth) 23 | block := "latest" 24 | res, err := mc.Call(vcs, block) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | resJson, _ := json.Marshal(res) 30 | fmt.Println(string(resJson)) 31 | fmt.Println(res) 32 | fmt.Println(err) 33 | 34 | } 35 | 36 | func getETH(url string) (ethrpc.ETHInterface, error) { 37 | provider, err := httprpc.New(url) 38 | if err != nil { 39 | return nil, err 40 | } 41 | provider.SetHTTPTimeout(5 * time.Second) 42 | return ethrpc.New(provider) 43 | } 44 | -------------------------------------------------------------------------------- /multicall/options.go: -------------------------------------------------------------------------------- 1 | package multicall 2 | 3 | import "fmt" 4 | 5 | type Option func(*Config) 6 | 7 | type Config struct { 8 | MulticallAddress string 9 | Gas string 10 | } 11 | 12 | const ( 13 | // MainnetMulticall : Multicall contract address on mainnet 14 | MainnetAddress = "0x5eb3fa2dfecdde21c950813c665e9364fa609bd2" 15 | // RopstenMulticall : Multicall contract address on Ropsten 16 | RopstenAddress = "0xf3ad7e31b052ff96566eedd218a823430e74b406" 17 | ) 18 | 19 | 20 | func ContractAddress(address string) Option { 21 | return func(c *Config) { 22 | c.MulticallAddress = address 23 | } 24 | } 25 | 26 | func SetGas(gas uint64) Option { 27 | return func(c *Config) { 28 | c.Gas = fmt.Sprintf("0x%x", gas) 29 | } 30 | } 31 | 32 | func SetGasHex(gas string) Option { 33 | return func(c *Config) { 34 | c.Gas = gas 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /multicall/types.go: -------------------------------------------------------------------------------- 1 | package multicall 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | ) 7 | 8 | type BigIntJSONString big.Int 9 | 10 | func (i BigIntJSONString) MarshalJSON() ([]byte, error) { 11 | backToInt := big.Int(i) 12 | return []byte(fmt.Sprintf(`"%s"`, backToInt.String())), nil 13 | } 14 | -------------------------------------------------------------------------------- /multicall/viewcall.go: -------------------------------------------------------------------------------- 1 | package multicall 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | "math/big" 10 | "reflect" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | type ViewCall struct { 16 | id string 17 | target string 18 | method string 19 | arguments []interface{} 20 | } 21 | 22 | type ViewCalls []ViewCall 23 | 24 | func NewViewCall(id, target, method string, arguments []interface{}) ViewCall { 25 | return ViewCall{ 26 | id: id, 27 | target: target, 28 | method: method, 29 | arguments: arguments, 30 | } 31 | 32 | } 33 | 34 | func (call ViewCall) Validate() error { 35 | if _, err := call.argsCallData(); err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | var insideParens = regexp.MustCompile("\\(.*?\\)") 42 | var numericArg = regexp.MustCompile("u?int(256)|(8)") 43 | 44 | func (call ViewCall) argumentTypes() []string { 45 | rawArgs := insideParens.FindAllString(call.method, -1)[0] 46 | rawArgs = strings.Replace(rawArgs, "(", "", -1) 47 | rawArgs = strings.Replace(rawArgs, ")", "", -1) 48 | if rawArgs == "" { 49 | return []string{} 50 | } 51 | args := strings.Split(rawArgs, ",") 52 | for index, arg := range args { 53 | args[index] = strings.Trim(arg, " ") 54 | } 55 | return args 56 | } 57 | 58 | func (call ViewCall) returnTypes() []string { 59 | rawArgs := insideParens.FindAllString(call.method, -1)[1] 60 | rawArgs = strings.Replace(rawArgs, "(", "", -1) 61 | rawArgs = strings.Replace(rawArgs, ")", "", -1) 62 | args := strings.Split(rawArgs, ",") 63 | for index, arg := range args { 64 | args[index] = strings.Trim(arg, " ") 65 | } 66 | return args 67 | } 68 | 69 | func (call ViewCall) callData() ([]byte, error) { 70 | argsSuffix, err := call.argsCallData() 71 | if err != nil { 72 | return nil, err 73 | } 74 | methodPrefix, err := call.methodCallData() 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | payload := make([]byte, 0) 80 | payload = append(payload, methodPrefix...) 81 | payload = append(payload, argsSuffix...) 82 | 83 | return payload, nil 84 | } 85 | 86 | func (call ViewCall) methodCallData() ([]byte, error) { 87 | methodParts := strings.Split(call.method, ")(") 88 | var method string 89 | if len(methodParts) > 1 { 90 | method = fmt.Sprintf("%s)", methodParts[0]) 91 | } else { 92 | method = methodParts[0] 93 | } 94 | hash := crypto.Keccak256([]byte(method)) 95 | return hash[0:4], nil 96 | } 97 | 98 | func (call ViewCall) argsCallData() ([]byte, error) { 99 | argTypes := call.argumentTypes() 100 | if len(argTypes) != len(call.arguments) { 101 | return nil, fmt.Errorf("number of argument types doesn't match with number of arguments for %s with method %s", call.id, call.method) 102 | } 103 | argumentValues := make([]interface{}, len(call.arguments)) 104 | arguments := make(abi.Arguments, len(call.arguments)) 105 | 106 | for index, argTypeStr := range argTypes { 107 | argType, err := abi.NewType(argTypeStr, "", nil) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | arguments[index] = abi.Argument{Type: argType} 113 | argumentValues[index], err = call.getArgument(index, argTypeStr) 114 | if err != nil { 115 | return nil, err 116 | } 117 | } 118 | 119 | return arguments.Pack(argumentValues...) 120 | } 121 | 122 | func (call ViewCall) getArgument(index int, argumentType string) (interface{}, error) { 123 | arg := call.arguments[index] 124 | if argumentType == "address" { 125 | address, ok := arg.(string) 126 | if !ok { 127 | return nil, fmt.Errorf("expected address argument to be a string") 128 | } 129 | return toByteArray(address) 130 | } else if numericArg.MatchString(argumentType) { 131 | if num, ok := arg.(json.Number); ok { 132 | if v, err := num.Int64(); err != nil { 133 | return big.NewInt(v), nil 134 | } else if v, err := num.Float64(); err != nil { 135 | return big.NewInt(int64(v)), nil 136 | } else { 137 | } 138 | } else { 139 | int64 := reflect.TypeOf(int64(0)) 140 | argType := reflect.TypeOf(arg) 141 | kind := argType.Kind() 142 | if kind == reflect.String { 143 | if val, ok := new(big.Int).SetString(call.arguments[index].(string), 10); !ok { 144 | return nil, fmt.Errorf("could not parse %s as a base 10 number", call.arguments[index]) 145 | } else { 146 | return val, nil 147 | } 148 | } else if argType.ConvertibleTo(int64) { 149 | return big.NewInt(reflect.ValueOf(arg).Convert(int64).Int()), nil 150 | } 151 | } 152 | } 153 | return arg, nil 154 | } 155 | 156 | func (call ViewCall) decode(raw []byte) ([]interface{}, error) { 157 | retTypes := call.returnTypes() 158 | args := make(abi.Arguments, 0, 0) 159 | for index, retTypeStr := range retTypes { 160 | retType, err := abi.NewType(retTypeStr, "", nil) 161 | if err != nil { 162 | return nil, err 163 | } 164 | args = append(args, abi.Argument{Name: fmt.Sprintf("ret%d", index), Type: retType}) 165 | } 166 | decoded := make(map[string]interface{}) 167 | err := args.UnpackIntoMap(decoded, raw) 168 | if err != nil { 169 | return nil, err 170 | } 171 | returns := make([]interface{}, len(retTypes)) 172 | for index := range retTypes { 173 | key := fmt.Sprintf("ret%d", index) 174 | item := decoded[key] 175 | if bigint, ok := item.(*big.Int); ok { 176 | returns[index] = (*BigIntJSONString)(bigint) 177 | } else { 178 | returns[index] = decoded[key] 179 | } 180 | } 181 | return returns, nil 182 | } 183 | 184 | type callArgs struct { 185 | Target [20]byte 186 | CallData []byte 187 | } 188 | 189 | func (calls ViewCalls) callData() ([]byte, error) { 190 | payloadArgs := make([]callArgs, 0, len(calls)) 191 | for _, call := range calls { 192 | callData, err := call.callData() 193 | if err != nil { 194 | return nil, err 195 | } 196 | targetBytes, err := toByteArray(call.target) 197 | if err != nil { 198 | return nil, err 199 | } 200 | payloadArgs = append(payloadArgs, callArgs{targetBytes, callData}) 201 | } 202 | 203 | tupleArray, err := abi.NewType("tuple[]", "", []abi.ArgumentMarshaling{ 204 | {Type: "address", Name: "Target"}, 205 | {Type: "bytes", Name: "CallData"}, 206 | }) 207 | if err != nil { 208 | return nil, err 209 | } 210 | boolean, err := abi.NewType("bool", "", nil) 211 | if err != nil { 212 | return nil, err 213 | } 214 | args := abi.Arguments{ 215 | {Type: tupleArray, Name: "calls"}, 216 | {Type: boolean, Name: "strict"}, 217 | } 218 | return args.Pack(payloadArgs, false) 219 | } 220 | 221 | type retType struct { 222 | Success bool 223 | Data []byte 224 | } 225 | 226 | type wrapperRet struct { 227 | BlockNumber *big.Int 228 | Returns []retType 229 | } 230 | 231 | func (calls ViewCalls) decodeWrapper(raw string) (*wrapperRet, error) { 232 | rawBytes, err := hex.DecodeString(strings.Replace(raw, "0x", "", -1)) 233 | if err != nil { 234 | return nil, err 235 | } 236 | 237 | uint256Type, err := abi.NewType("uint256", "", nil) 238 | if err != nil { 239 | return nil, err 240 | } 241 | returnType, err := abi.NewType("tuple[]", "", []abi.ArgumentMarshaling{ 242 | {Name: "Success", Type: "bool"}, 243 | {Name: "Data", Type: "bytes"}, 244 | }) 245 | if err != nil { 246 | return nil, err 247 | } 248 | wrapperArgs := abi.Arguments{ 249 | { 250 | Name: "BlockNumber", 251 | Type: uint256Type, 252 | }, 253 | { 254 | Name: "Returns", 255 | Type: returnType, 256 | }, 257 | } 258 | data, err := wrapperArgs.Unpack(rawBytes) 259 | if err != nil { 260 | return nil, err 261 | } 262 | decoded := &wrapperRet{ 263 | BlockNumber: data[0].(*big.Int), 264 | } 265 | returns := reflect.ValueOf(data[1]) 266 | for i := 0; i < returns.Len(); i++ { 267 | elem := returns.Index(i) 268 | ret := retType{ 269 | Success: elem.FieldByName("Success").Bool(), 270 | Data: elem.FieldByName("Data").Bytes(), 271 | } 272 | decoded.Returns = append(decoded.Returns, ret) 273 | } 274 | return decoded, err 275 | } 276 | 277 | func (calls ViewCalls) decodeRaw(raw string) (*Result, error) { 278 | decoded, err := calls.decodeWrapper(raw) 279 | if err != nil { 280 | return nil, err 281 | } 282 | result := &Result{} 283 | result.BlockNumber = decoded.BlockNumber.Uint64() 284 | result.Calls = make(map[string]CallResult) 285 | 286 | for index, call := range calls { 287 | callResult := CallResult{ 288 | Success: decoded.Returns[index].Success, 289 | Raw: decoded.Returns[index].Data, 290 | Decoded: []interface{}{}, 291 | } 292 | result.Calls[call.id] = callResult 293 | } 294 | 295 | return result, nil 296 | } 297 | 298 | func (calls ViewCalls) decode(raw string) (*Result, error) { 299 | decoded, err := calls.decodeWrapper(raw) 300 | if err != nil { 301 | return nil, err 302 | } 303 | result := &Result{} 304 | result.BlockNumber = decoded.BlockNumber.Uint64() 305 | result.Calls = make(map[string]CallResult) 306 | for index, call := range calls { 307 | callResult := CallResult{ 308 | Success: decoded.Returns[index].Success, 309 | Raw: decoded.Returns[index].Data, 310 | } 311 | if decoded.Returns[index].Success { 312 | returnValues, err := call.decode(decoded.Returns[index].Data) 313 | if err != nil { 314 | return nil, err 315 | } 316 | callResult.Decoded = returnValues 317 | } 318 | result.Calls[call.id] = callResult 319 | } 320 | 321 | return result, nil 322 | } 323 | 324 | func toByteArray(address string) ([20]byte, error) { 325 | var addressBytes [20]byte 326 | address = strings.Replace(address, "0x", "", -1) 327 | addressBytesSlice, err := hex.DecodeString(address) 328 | if err != nil { 329 | return addressBytes, err 330 | } 331 | 332 | copy(addressBytes[:], addressBytesSlice[:]) 333 | return addressBytes, nil 334 | } 335 | -------------------------------------------------------------------------------- /multicall/viewcall_test.go: -------------------------------------------------------------------------------- 1 | package multicall 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestViewCall(t *testing.T) { 9 | vc := ViewCall{ 10 | id: "key", 11 | target: "0x0", 12 | method: "balanceOf(address, uint64)(int256)", 13 | arguments: []interface{}{"0x1234", uint64(12)}, 14 | } 15 | expectedArgTypes := []string{"address", "uint64"} 16 | expectedCallData := []byte{ 17 | 0x29, 0x5e, 0xaa, 0xdf, 0x0, 0x0, 0x0, 0x0, 18 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 19 | 0x12, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 20 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 21 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 22 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 23 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 24 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 25 | 0x0, 0x0, 0x0, 0xc} 26 | assert.Equal(t, expectedArgTypes, vc.argumentTypes()) 27 | callData, err := vc.callData() 28 | assert.Nil(t, err) 29 | assert.Equal(t, expectedCallData, callData) 30 | } 31 | 32 | func TestCatchPanicOnInterfaceIssue(t *testing.T) { 33 | vc := ViewCall{ 34 | id: "key", 35 | target: "0x0", 36 | method: "balanceOf(address)(int256)", 37 | arguments: []interface{}{1234}, 38 | } 39 | 40 | err := vc.Validate() 41 | assert.NotNil(t, err) 42 | assert.Error(t, err, "expected address argument to be a string") 43 | } 44 | 45 | func TestEncodeNumericArgument(t *testing.T) { 46 | vc1 := ViewCall{ 47 | id: "key", 48 | target: "0x0", 49 | method: "balanceOf(uint256)(int256)", 50 | arguments: []interface{}{"12312312312313"}, 51 | } 52 | vc2 := ViewCall{ 53 | id: "key", 54 | target: "0x0", 55 | method: "balanceOf(uint256)(int256)", 56 | arguments: []interface{}{12312312312313}, 57 | } 58 | 59 | data1, err1 := vc1.argsCallData() 60 | data2, err2 := vc2.argsCallData() 61 | assert.Nil(t, err1) 62 | assert.Nil(t, err2) 63 | assert.Equal(t, data1, data2) 64 | } 65 | 66 | func TestEncodeBytes32Argument(t *testing.T) { 67 | var bytes32Array = [32]uint8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} 68 | 69 | vc1 := ViewCall{ 70 | id: "key", 71 | target: "0x0", 72 | method: "balanceOfPartition(bytes32, uint256)(int256)", 73 | arguments: []interface{}{bytes32Array, "12312312312313"}, 74 | } 75 | 76 | _, err1 := vc1.argsCallData() 77 | assert.Nil(t, err1) 78 | } 79 | --------------------------------------------------------------------------------