├── website ├── pages │ ├── jsonrpc │ │ ├── meta.json │ │ ├── net.mdx │ │ └── index.mdx │ ├── integrations │ │ ├── meta.json │ │ ├── 4byte.mdx │ │ ├── ens.mdx │ │ └── etherscan.mdx │ ├── cli │ │ ├── meta.json │ │ ├── version.mdx │ │ ├── 4byte.mdx │ │ ├── ens_resolve.mdx │ │ └── abigen.mdx │ ├── index.mdx │ ├── signers │ │ ├── signer.mdx │ │ └── wallet.mdx │ ├── meta.json │ ├── _app.js │ ├── abi.mdx │ └── contract.mdx ├── README.md ├── next.config.js ├── components │ ├── eip.jsx │ ├── godoc.jsx │ └── primitives.jsx ├── package.json └── theme.config.js ├── .gitignore ├── Makefile ├── keccak.go ├── tracker ├── store │ ├── inmem │ │ ├── inmem_store_test.go │ │ └── inmem_store.go │ ├── boltdb │ │ └── bolt_store_test.go │ ├── store.go │ └── postgresql │ │ └── postgresql_store_test.go └── README.md ├── ens ├── address_mapping.go ├── ens_test.go └── ens.go ├── scripts ├── setup-ci.sh ├── setup-geth.sh └── build-artifacts.sh ├── networks.go ├── testcases ├── package.json ├── util.go ├── accounts_test.go ├── eip712_test.go ├── contract_test.go └── transaction_test.go ├── keystore ├── v3_test.go ├── v4_test.go ├── utils.go ├── v3.go └── v4.go ├── wallet ├── wallet_priv_test.go ├── key_test.go ├── wallet_priv.go ├── wallet_json.go ├── wallet_json_test.go ├── wallet_hd_test.go ├── fixtures │ └── wallet_json.json ├── wallet_hd.go ├── signer_test.go ├── key.go └── signer.go ├── testutil ├── server_test.go └── util.go ├── units.go ├── abi ├── revert.go ├── revert_test.go ├── decode_test.go ├── topics_test.go └── topics.go ├── .goreleaser.yaml ├── 4byte ├── 4byte_test.go └── 4byte.go ├── examples ├── contract-deploy.go ├── contract-call-basic.go ├── contract-call-from.go └── contract-transaction.go ├── cmd ├── main.go ├── commands │ ├── ens.go │ ├── version.go │ ├── 4byte.go │ ├── commands.go │ ├── abigen.go │ └── ens_resolve.go ├── version │ └── version.go ├── abigen │ ├── testdata │ │ ├── testdata.abi │ │ ├── testdata.go │ │ └── testdata_artifacts.go │ └── abigen.go └── go.mod ├── README.md ├── testsuite ├── transaction-pending.json ├── transaction-contract-creation.json ├── transaction-call.json ├── transaction-eip2930.json ├── transaction-eip1559-notype.json ├── transaction-eip1159.json ├── block-txn-hashes.json ├── block-full.json └── receipts.json ├── .github └── workflows │ ├── pr.yaml │ └── release.yaml ├── builtin ├── ens │ ├── utils_test.go │ ├── utils.go │ ├── ens_resolver_test.go │ ├── ens_resolver.go │ ├── artifacts │ │ ├── ENS.abi │ │ ├── ENS.bin │ │ └── Resolver.abi │ ├── ens.go │ └── ens_artifacts.go └── erc20 │ ├── erc20_test.go │ ├── artifacts │ └── ERC20.abi │ ├── erc20_artifacts.go │ └── erc20.go ├── jsonrpc ├── subscribe.go ├── web3.go ├── debug_test.go ├── web3_test.go ├── transport │ ├── ipc.go │ ├── transport.go │ └── http.go ├── net.go ├── net_test.go ├── codec │ └── codec.go ├── debug.go ├── util.go ├── subscribe_test.go └── client.go ├── structs_marshal_rlp_test.go ├── signing └── eip712_test.go ├── encoding.go ├── etherscan └── etherscan_test.go ├── e2e └── transaction_test.go ├── structs_encoding_test.go ├── go.mod ├── structs_marshal_test.go ├── CHANGELOG.md ├── structs_test.go └── compiler ├── solidity_test.go ├── solidity.go └── fixtures └── simple_auction.sol /website/pages/jsonrpc/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "Overview", 3 | "eth": "Eth", 4 | "net": "Net" 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | pkg/ 3 | 4 | .idea 5 | 6 | # website 7 | node_modules 8 | .next 9 | 10 | dist/ 11 | -------------------------------------------------------------------------------- /website/pages/integrations/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ens": "Ethereum Name Service", 3 | "etherscan": "Etherscan" 4 | } -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Website 3 | 4 | ## Usage 5 | 6 | ``` 7 | $ npm install 8 | $ npm run dev 9 | ``` 10 | -------------------------------------------------------------------------------- /website/pages/cli/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "abigen": "Abigen", 3 | "version": "Version", 4 | "ens_resolve": "Ens Resolve" 5 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: build-artifacts 3 | build-artifacts: 4 | @echo "--> Build Artifacts" 5 | @sh -c ./scripts/build-artifacts.sh 6 | -------------------------------------------------------------------------------- /website/pages/index.mdx: -------------------------------------------------------------------------------- 1 | 2 | # Introduction 3 | 4 | `Ethgo` is a lightweight SDK in Go to interact with Ethereum compatible blockchains. 5 | -------------------------------------------------------------------------------- /website/pages/cli/version.mdx: -------------------------------------------------------------------------------- 1 | 2 | # Version 3 | 4 | The `version` command outputs the version of the Ethgo binary. 5 | 6 | ## Usage 7 | 8 | ```shell 9 | $ ethgo version 10 | ``` 11 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | // next.config.js 2 | const withNextra = require('nextra')({ 3 | theme: 'nextra-theme-docs', 4 | themeConfig: './theme.config.js', 5 | }) 6 | module.exports = withNextra() -------------------------------------------------------------------------------- /website/components/eip.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Link from 'next/link' 3 | 4 | export default function EIPLink({children, num}) { 5 | return {children} 6 | } 7 | -------------------------------------------------------------------------------- /website/pages/cli/4byte.mdx: -------------------------------------------------------------------------------- 1 | 2 | # 4byte 3 | 4 | The `4byte` command resolve a function or event bytes signature to its full name using the [4byte](https://www.4byte.directory/) API. 5 | 6 | ## Usage 7 | 8 | ```shell 9 | $ ethgo 4byte 0xddf252ad 10 | ``` -------------------------------------------------------------------------------- /website/pages/signers/signer.mdx: -------------------------------------------------------------------------------- 1 | 2 | # Signers 3 | 4 | The `Signer` represents an abstract entity that can sign arbitrary messages. 5 | 6 | ```go 7 | type Key interface { 8 | Address() Address 9 | Sign(hash []byte) ([]byte, error) 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /website/components/godoc.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Link from 'next/link' 3 | 4 | const goDocRef = "https://pkg.go.dev/github.com/umbracle/ethgo/" 5 | 6 | export default function GodocLink({children, href}) { 7 | return {children} 8 | } 9 | -------------------------------------------------------------------------------- /website/pages/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "Introduction", 3 | "jsonrpc": "JsonRPC", 4 | "abi": "Application Binary Interface", 5 | "signers": "Signers", 6 | "contract": "Contract", 7 | "integrations": "Integrations", 8 | "cli": "Command Line Interface" 9 | } -------------------------------------------------------------------------------- /keccak.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import "golang.org/x/crypto/sha3" 4 | 5 | // Keccak256 calculates the Keccak256 6 | func Keccak256(v ...[]byte) []byte { 7 | h := sha3.NewLegacyKeccak256() 8 | for _, i := range v { 9 | h.Write(i) 10 | } 11 | return h.Sum(nil) 12 | } 13 | -------------------------------------------------------------------------------- /tracker/store/inmem/inmem_store_test.go: -------------------------------------------------------------------------------- 1 | package inmem 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/umbracle/ethgo/tracker/store" 7 | ) 8 | 9 | func TestInMemoryStore(t *testing.T) { 10 | store.TestStore(t, func(t *testing.T) (store.Store, func()) { 11 | return NewInmemStore(), func() {} 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /ens/address_mapping.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import "github.com/umbracle/ethgo" 4 | 5 | var defaultEnsAddr = ethgo.HexToAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e") 6 | 7 | var builtinEnsAddr = map[uint64]ethgo.Address{ 8 | 1: defaultEnsAddr, 9 | 3: defaultEnsAddr, 10 | 4: defaultEnsAddr, 11 | 5: defaultEnsAddr, 12 | } 13 | -------------------------------------------------------------------------------- /scripts/setup-ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # set -o errexit 4 | 5 | install_solidity() { 6 | VERSION="0.5.5" 7 | DOWNLOAD=https://github.com/ethereum/solidity/releases/download/v${VERSION}/solc-static-linux 8 | 9 | curl -L $DOWNLOAD > /tmp/solc 10 | chmod +x /tmp/solc 11 | mv /tmp/solc /usr/local/bin/solc 12 | } 13 | 14 | install_solidity 15 | -------------------------------------------------------------------------------- /networks.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | // Network is a chain id 4 | type Network uint64 5 | 6 | const ( 7 | // Mainnet is the mainnet network 8 | Mainnet Network = 1 9 | 10 | // Ropsten is the POW testnet 11 | Ropsten Network = 3 12 | 13 | // Rinkeby is a POW testnet 14 | Rinkeby Network = 4 15 | 16 | // Goerli is the Clique testnet 17 | Goerli Network = 5 18 | ) 19 | -------------------------------------------------------------------------------- /testcases/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testsuite", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@ethersproject/testcases": "^5.6.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "next": "^12.0.9", 4 | "nextra": "^2.0.0-alpha.23", 5 | "nextra-theme-docs": "^2.0.0-alpha.23", 6 | "prism-react-renderer": "^1.2.1", 7 | "prismjs": "^1.26.0", 8 | "react": "^17.0.2", 9 | "react-dom": "^17.0.2" 10 | }, 11 | "scripts": { 12 | "dev": "next dev", 13 | "build": "next build", 14 | "start": "next start" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /keystore/v3_test.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestV3_EncodeDecode(t *testing.T) { 10 | data := []byte{0x1, 0x2} 11 | password := "abcd" 12 | 13 | encrypted, err := EncryptV3(data, password) 14 | assert.NoError(t, err) 15 | 16 | found, err := DecryptV3(encrypted, password) 17 | assert.NoError(t, err) 18 | 19 | assert.Equal(t, data, found) 20 | } 21 | -------------------------------------------------------------------------------- /wallet/wallet_priv_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestWallet_Priv(t *testing.T) { 10 | key, err := GenerateKey() 11 | assert.NoError(t, err) 12 | 13 | raw, err := key.MarshallPrivateKey() 14 | assert.NoError(t, err) 15 | 16 | key1, err := NewWalletFromPrivKey(raw) 17 | assert.NoError(t, err) 18 | 19 | assert.Equal(t, key.addr, key1.addr) 20 | } 21 | -------------------------------------------------------------------------------- /testutil/server_test.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "github.com/umbracle/ethgo" 8 | ) 9 | 10 | func TestDeployServer(t *testing.T) { 11 | srv := DeployTestServer(t, nil) 12 | require.NotEmpty(t, srv.accounts) 13 | 14 | clt := ðClient{srv.HTTPAddr()} 15 | account := []ethgo.Address{} 16 | 17 | err := clt.call("eth_accounts", &account) 18 | require.NoError(t, err) 19 | } 20 | -------------------------------------------------------------------------------- /wallet/key_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestKeySign(t *testing.T) { 10 | key, err := GenerateKey() 11 | assert.NoError(t, err) 12 | 13 | msg := []byte("hello world") 14 | signature, err := key.SignMsg(msg) 15 | assert.NoError(t, err) 16 | 17 | addr, err := EcrecoverMsg(msg, signature) 18 | assert.NoError(t, err) 19 | assert.Equal(t, addr, key.addr) 20 | } 21 | -------------------------------------------------------------------------------- /website/pages/cli/ens_resolve.mdx: -------------------------------------------------------------------------------- 1 | 2 | # Ens resolve 3 | 4 | The `ens resolve ` command resolves an ENS name to its Ethereum address. 5 | 6 | ## Usage 7 | 8 | ```shell 9 | $ ethgo ens resolve umbracle.eth [--provider https://mainnet.infura.io...] 10 | ``` 11 | 12 | ## Options 13 | 14 | - `provider`: URL of the JSON-RPC endpoint to resolve queries (default=`http://localhost:8545`). It can also be set with the `JSONRPC_PROVIDER` environment variable. 15 | -------------------------------------------------------------------------------- /wallet/wallet_priv.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | 6 | "github.com/btcsuite/btcd/btcec/v2" 7 | ) 8 | 9 | func ParsePrivateKey(buf []byte) (*ecdsa.PrivateKey, error) { 10 | prv, _ := btcec.PrivKeyFromBytes(buf) 11 | return prv.ToECDSA(), nil 12 | } 13 | 14 | func NewWalletFromPrivKey(p []byte) (*Key, error) { 15 | priv, err := ParsePrivateKey(p) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return NewKey(priv) 20 | } 21 | -------------------------------------------------------------------------------- /website/pages/jsonrpc/net.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../../components/godoc' 3 | import {Address, Hash, Blocktag, Block, Transaction, Receipt} from '../../components/primitives' 4 | 5 | # Net 6 | 7 | ## PeerCount 8 | 9 | PeerCount returns number of peers currently connected to the client. 10 | 11 | ```go 12 | count, err := client.Net().PeerCount() 13 | ``` 14 | 15 | Output: 16 | 17 | - `count` `(uint64)`: Number of peers connected. 18 | -------------------------------------------------------------------------------- /units.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import "math/big" 4 | 5 | func convert(val uint64, decimals int64) *big.Int { 6 | v := big.NewInt(int64(val)) 7 | exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(decimals), nil) 8 | return v.Mul(v, exp) 9 | } 10 | 11 | // Ether converts a value to the ether unit with 18 decimals 12 | func Ether(i uint64) *big.Int { 13 | return convert(i, 18) 14 | } 15 | 16 | // Gwei converts a value to the gwei unit with 9 decimals 17 | func Gwei(i uint64) *big.Int { 18 | return convert(i, 9) 19 | } 20 | -------------------------------------------------------------------------------- /abi/revert.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | var revertId = []byte{0x8, 0xC3, 0x79, 0xA0} 9 | 10 | func UnpackRevertError(b []byte) (string, error) { 11 | if !bytes.HasPrefix(b, revertId) { 12 | return "", fmt.Errorf("revert error prefix not found") 13 | } 14 | 15 | b = b[4:] 16 | tt := MustNewType("tuple(string)") 17 | vals, err := tt.Decode(b) 18 | if err != nil { 19 | return "", err 20 | } 21 | revVal := vals.(map[string]interface{})["0"].(string) 22 | return revVal, nil 23 | } 24 | -------------------------------------------------------------------------------- /scripts/setup-geth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start geth test server 4 | docker run --name geth-test -d -p 8545:8545 -p 8546:8546 ethereum/client-go:v1.10.15 \ 5 | --dev \ 6 | --datadir /eth1data \ 7 | --ipcpath /eth1data/geth.ipc \ 8 | --http --http.addr 0.0.0.0 --http.vhosts=* --http.api eth,net,web3,debug \ 9 | --ws --ws.addr 0.0.0.0 \ 10 | --verbosity 4 11 | 12 | # Wait for geth to be running 13 | while ! nc -z localhost 8545; do 14 | sleep 0.1 # wait for 1/10 of the second before check again 15 | done 16 | -------------------------------------------------------------------------------- /abi/revert_test.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUnpackRevertError(t *testing.T) { 10 | data := "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000" 11 | 12 | raw, err := decodeHex(data) 13 | assert.NoError(t, err) 14 | 15 | reason, err := UnpackRevertError(raw) 16 | assert.NoError(t, err) 17 | assert.Equal(t, "revert reason", reason) 18 | } 19 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | builds: 5 | - dir: cmd 6 | env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - windows 11 | - darwin 12 | archives: 13 | - replacements: 14 | darwin: Darwin 15 | linux: Linux 16 | windows: Windows 17 | 386: i386 18 | amd64: x86_64 19 | checksum: 20 | name_template: 'checksums.txt' 21 | snapshot: 22 | name_template: "{{ incpatch .Version }}-next" 23 | changelog: 24 | sort: asc 25 | filters: 26 | exclude: 27 | - '^docs:' 28 | - '^test:' 29 | -------------------------------------------------------------------------------- /4byte/4byte_test.go: -------------------------------------------------------------------------------- 1 | package fourbyte 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test4Byte(t *testing.T) { 10 | t.Skip("http api not stable, skip for now") 11 | 12 | var cases = []struct { 13 | in, out string 14 | }{ 15 | { 16 | "0xddf252ad", 17 | "Transfer(address,address,uint256)", 18 | }, 19 | { 20 | "0x42842e0e", 21 | "safeTransferFrom(address,address,uint256)", 22 | }, 23 | } 24 | for _, i := range cases { 25 | found, err := Resolve(i.in) 26 | assert.NoError(t, err) 27 | assert.Equal(t, i.out, found) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/contract-deploy.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/umbracle/ethgo/abi" 7 | "github.com/umbracle/ethgo/contract" 8 | ) 9 | 10 | func contractDeploy() { 11 | abiContract, err := abi.NewABIFromList([]string{}) 12 | handleErr(err) 13 | 14 | // bytecode of the contract 15 | bin := []byte{} 16 | 17 | txn, err := contract.DeployContract(abiContract, bin, []interface{}{}) 18 | handleErr(err) 19 | 20 | err = txn.Do() 21 | handleErr(err) 22 | 23 | receipt, err := txn.Wait() 24 | handleErr(err) 25 | 26 | fmt.Printf("Contract: %s", receipt.ContractAddress) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/mitchellh/cli" 8 | "github.com/umbracle/ethgo/cmd/commands" 9 | ) 10 | 11 | func main() { 12 | os.Exit(Run(os.Args[1:])) 13 | } 14 | 15 | // Run starts the cli 16 | func Run(args []string) int { 17 | commands := commands.Commands() 18 | 19 | cli := &cli.CLI{ 20 | Name: "ethgo", 21 | Args: args, 22 | Commands: commands, 23 | } 24 | 25 | exitCode, err := cli.Run() 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) 28 | return 1 29 | } 30 | 31 | return exitCode 32 | } 33 | -------------------------------------------------------------------------------- /scripts/build-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | cd cmd 5 | 6 | echo "--> Build ENS" 7 | 8 | ENS_ARTIFACTS=../builtin/ens/artifacts 9 | go run main.go abigen --source ${ENS_ARTIFACTS}/ENS.abi,${ENS_ARTIFACTS}/Resolver.abi --output ../builtin/ens --package ens 10 | 11 | echo "--> Build ERC20" 12 | 13 | ERC20_ARTIFACTS=../builtin/erc20/artifacts 14 | go run main.go abigen --source ${ERC20_ARTIFACTS}/ERC20.abi --output ../builtin/erc20 --package erc20 15 | 16 | echo "--> Build Testdata" 17 | go run main.go abigen --source ./abigen/testdata/testdata.abi --output ./abigen/testdata --package testdata 18 | -------------------------------------------------------------------------------- /cmd/commands/ens.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/mitchellh/cli" 5 | ) 6 | 7 | // EnsCommand is the group command for ens 8 | type EnsCommand struct { 9 | UI cli.Ui 10 | } 11 | 12 | // Help implements the cli.Command interface 13 | func (c *EnsCommand) Help() string { 14 | return `Usage: ethgo ens 15 | 16 | Interact with ens` 17 | } 18 | 19 | // Synopsis implements the cli.Command interface 20 | func (c *EnsCommand) Synopsis() string { 21 | return "Interact with ens" 22 | } 23 | 24 | // Run implements the cli.Command interface 25 | func (c *EnsCommand) Run(args []string) int { 26 | return 0 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Eth-Go 3 | 4 | [![Chat Badge]][chat link] 5 | 6 | [chat badge]: https://img.shields.io/badge/chat-discord-%237289da 7 | [chat link]: https://discord.gg/5A6Qm2u4yK 8 | 9 | Ethgo is a lightweight SDK in Go to interact with Ethereum compatible blockchains. 10 | 11 | - Website: https://www.ethgoproject.io 12 | 13 | Ethgo provides the next key features: 14 | 15 | - **Simple**: Light and with a small number of direct dependencies. 16 | 17 | - **Ethereum ecosystem**: Native integration with other tools from the ecosystem like `ens` and `etherscan`. 18 | 19 | - **Command-line-interface**: Ethgo is both a Golang SDK library and a CLI. 20 | -------------------------------------------------------------------------------- /website/pages/integrations/4byte.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../../components/godoc' 3 | 4 | # 4Byte 5 | 6 | [4Byte](https://www.4byte.directory/) is a public directory that indexes signatures for Ethereum functions and events. 7 | 8 | Run Resolve to resolve a signature in string format to its name: 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | fourbyte "github.com/umbracle/ethgo/4byte" 15 | ) 16 | 17 | func main() { 18 | found, err := fourbyte.Resolve("0xddf252ad") 19 | if err != nil { 20 | panic(err) 21 | } 22 | // Transfer(address,address,uint256) 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /website/pages/_app.js: -------------------------------------------------------------------------------- 1 | import 'nextra-theme-docs/style.css' 2 | 3 | import Prism from 'prism-react-renderer/prism' 4 | (typeof global !== "undefined" ? global : window).Prism = Prism 5 | require("prismjs/components/prism-go") 6 | 7 | import Head from 'next/head'; 8 | 9 | const prod = process.env.NODE_ENV === 'production' 10 | 11 | export default function Nextra({ Component, pageProps }) { 12 | return ( 13 | <> 14 | 15 | {prod && 16 | 17 | } 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /abi/decode_test.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestDecode_BytesBound(t *testing.T) { 10 | typ := MustNewType("tuple(string)") 11 | decodeTuple(typ, nil) // it should not panic 12 | } 13 | 14 | func TestDecode_DynamicLengthOutOfBounds(t *testing.T) { 15 | input := []byte("00000000000000000000000000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 00000000000000000000000000") 16 | typ := MustNewType("tuple(bytes32, bytes, bytes)") 17 | 18 | _, err := Decode(typ, input) 19 | require.Error(t, err) 20 | } 21 | -------------------------------------------------------------------------------- /testsuite/transaction-pending.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "0x0", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "from": "0x0000000000000000000000000000000000000001", 5 | "input": "0x00", 6 | "value": "0x0", 7 | "gasPrice": "0x0", 8 | "gas": "0x10", 9 | "nonce": "0x10", 10 | "to": "0x0000000000000000000000000000000000000001", 11 | "v": "0x25", 12 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 13 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 14 | "blockHash": null, 15 | "blockNumber": null, 16 | "transactionIndex": null 17 | } -------------------------------------------------------------------------------- /wallet/wallet_json.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "github.com/umbracle/ethgo/keystore" 7 | ) 8 | 9 | func NewJSONWalletFromFile(path string, password string) (*Key, error) { 10 | data, err := ioutil.ReadFile(path) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return NewJSONWalletFromContent(data, password) 15 | } 16 | 17 | func NewJSONWalletFromContent(content []byte, password string) (*Key, error) { 18 | dst, err := keystore.DecryptV3(content, password) 19 | if err != nil { 20 | return nil, err 21 | } 22 | key, err := NewWalletFromPrivKey(dst) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return key, nil 27 | } 28 | -------------------------------------------------------------------------------- /wallet/wallet_json_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestWallet_JSON(t *testing.T) { 12 | raw, err := ioutil.ReadFile("./fixtures/wallet_json.json") 13 | assert.NoError(t, err) 14 | 15 | var cases []struct { 16 | Wallet json.RawMessage 17 | Password string 18 | Address string 19 | } 20 | assert.NoError(t, json.Unmarshal(raw, &cases)) 21 | 22 | for _, c := range cases { 23 | key, err := NewJSONWalletFromContent(c.Wallet, c.Password) 24 | assert.NoError(t, err) 25 | assert.Equal(t, key.Address().String(), c.Address) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ens/ens_test.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/umbracle/ethgo" 8 | "github.com/umbracle/ethgo/testutil" 9 | ) 10 | 11 | func TestENS_Resolve(t *testing.T) { 12 | ens, err := NewENS(WithAddress(testutil.TestInfuraEndpoint(t))) 13 | assert.NoError(t, err) 14 | 15 | addr, err := ens.Resolve("nick.eth") 16 | assert.NoError(t, err) 17 | assert.Equal(t, ethgo.HexToAddress("0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"), addr) 18 | 19 | name, err := ens.ReverseResolve(ethgo.HexToAddress("0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5")) 20 | assert.NoError(t, err) 21 | assert.Equal(t, "nick.eth", name) 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | on: [pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | name: Go test 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-node@v2 10 | with: 11 | node-version: "14" 12 | - name: Install ethers testcases 13 | run: cd ./testcases && npm install 14 | - name: Setup go 15 | uses: actions/setup-go@v1 16 | with: 17 | go-version: "1.18.1" 18 | - name: "Setup" 19 | run: ./scripts/setup-ci.sh 20 | - name: "Setup geth" 21 | run: ./scripts/setup-geth.sh 22 | - name: Go test 23 | run: go test -v ./... 24 | -------------------------------------------------------------------------------- /cmd/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import "fmt" 4 | 5 | var ( 6 | // GitCommit is the git commit that was compiled. 7 | GitCommit string 8 | 9 | // Version is the main version at the moment. 10 | Version = "0.1.3" 11 | 12 | // VersionPrerelease is a marker for the version. 13 | VersionPrerelease = "" 14 | ) 15 | 16 | // GetVersion returns a string representation of the version 17 | func GetVersion() string { 18 | version := Version 19 | 20 | release := VersionPrerelease 21 | if release != "" { 22 | version += fmt.Sprintf("-%s", release) 23 | 24 | if GitCommit != "" { 25 | version += fmt.Sprintf(" (%s)", GitCommit) 26 | } 27 | } 28 | 29 | return version 30 | } 31 | -------------------------------------------------------------------------------- /builtin/ens/utils_test.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNameHash(t *testing.T) { 10 | cases := []struct { 11 | Name string 12 | Expected string 13 | }{ 14 | { 15 | Name: "eth", 16 | Expected: "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", 17 | }, 18 | { 19 | Name: "foo.eth", 20 | Expected: "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f", 21 | }, 22 | } 23 | 24 | for _, c := range cases { 25 | t.Run("", func(t *testing.T) { 26 | found := NameHash(c.Name) 27 | assert.Equal(t, c.Expected, found.String()) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /testsuite/transaction-contract-creation.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "0x0", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "from": "0x0000000000000000000000000000000000000001", 5 | "input": "0x00", 6 | "value": "0x0", 7 | "gasPrice": "0x0", 8 | "gas": "0x10", 9 | "nonce": "0x10", 10 | "to": null, 11 | "v": "0x25", 12 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 13 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 14 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000001", 15 | "blockNumber": "0x0", 16 | "transactionIndex": "0x0" 17 | } -------------------------------------------------------------------------------- /jsonrpc/subscribe.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/umbracle/ethgo/jsonrpc/transport" 7 | ) 8 | 9 | // SubscriptionEnabled returns true if the subscription endpoints are enabled 10 | func (c *Client) SubscriptionEnabled() bool { 11 | _, ok := c.transport.(transport.PubSubTransport) 12 | return ok 13 | } 14 | 15 | // Subscribe starts a new subscription 16 | func (c *Client) Subscribe(method string, callback func(b []byte)) (func() error, error) { 17 | pub, ok := c.transport.(transport.PubSubTransport) 18 | if !ok { 19 | return nil, fmt.Errorf("transport does not support the subscribe method") 20 | } 21 | close, err := pub.Subscribe(method, callback) 22 | return close, err 23 | } 24 | -------------------------------------------------------------------------------- /testsuite/transaction-call.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "0x0", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "from": "0x0000000000000000000000000000000000000001", 5 | "input": "0x00", 6 | "value": "0x0", 7 | "gasPrice": "0x0", 8 | "gas": "0x10", 9 | "nonce": "0x10", 10 | "to": "0x0000000000000000000000000000000000000001", 11 | "v": "0x25", 12 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 13 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 14 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000001", 15 | "blockNumber": "0x0", 16 | "transactionIndex": "0x0" 17 | } -------------------------------------------------------------------------------- /tracker/store/boltdb/bolt_store_test.go: -------------------------------------------------------------------------------- 1 | package trackerboltdb 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/umbracle/ethgo/tracker/store" 10 | ) 11 | 12 | func setupDB(t *testing.T) (store.Store, func()) { 13 | dir, err := ioutil.TempDir("/tmp", "boltdb-test") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | path := filepath.Join(dir, "test.db") 19 | store, err := New(path) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | close := func() { 25 | if err := os.RemoveAll(dir); err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | return store, close 30 | } 31 | 32 | func TestBoltDBStore(t *testing.T) { 33 | store.TestStore(t, setupDB) 34 | } 35 | -------------------------------------------------------------------------------- /website/pages/cli/abigen.mdx: -------------------------------------------------------------------------------- 1 | 2 | # Abigen 3 | 4 | The `abigen` command generates bindings to interact with smart contracts using Go. It requires as input the ABI specification of the smart contract in JSON format. 5 | 6 | ## Usage 7 | 8 | ```shell 9 | $ ethgo abigen --source ./erc20.json --package erc20 10 | ``` 11 | 12 | ## Options 13 | 14 | - `source`: Path of the ABI contract file. 15 | - `package`: Name of the Go package. 16 | - `output`: Output directory. It defaults to the current location. 17 | 18 | ## Output 19 | 20 | It generates two output files. 21 | 22 | - `[name].go`: It contains the bindings for the contract. 23 | - `[name]_artifacts.go`. It contains the artifacts for the contract (ABI and deploy binary). 24 | -------------------------------------------------------------------------------- /builtin/ens/utils.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/umbracle/ethgo" 7 | "golang.org/x/crypto/sha3" 8 | ) 9 | 10 | // NameHash returns the hash of an ENS name 11 | func NameHash(str string) (node ethgo.Hash) { 12 | if str == "" { 13 | return 14 | } 15 | 16 | aux := make([]byte, 32) 17 | hash := sha3.NewLegacyKeccak256() 18 | 19 | labels := strings.Split(str, ".") 20 | for i := len(labels) - 1; i >= 0; i-- { 21 | label := labels[i] 22 | 23 | hash.Write([]byte(label)) 24 | aux = hash.Sum(aux) // append the hash of the label to node 25 | hash.Reset() 26 | 27 | hash.Write(aux) 28 | aux = hash.Sum(aux[:0]) 29 | hash.Reset() 30 | } 31 | 32 | copy(node[:], aux) 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /jsonrpc/web3.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | // Web3 is the web3 namespace 4 | type Web3 struct { 5 | c *Client 6 | } 7 | 8 | // Web3 returns the reference to the web3 namespace 9 | func (c *Client) Web3() *Web3 { 10 | return c.endpoints.w 11 | } 12 | 13 | // ClientVersion returns the current client version 14 | func (w *Web3) ClientVersion() (string, error) { 15 | var out string 16 | err := w.c.Call("web3_clientVersion", &out) 17 | return out, err 18 | } 19 | 20 | // Sha3 returns Keccak-256 (not the standardized SHA3-256) of the given data 21 | func (w *Web3) Sha3(val []byte) ([]byte, error) { 22 | var out string 23 | if err := w.c.Call("web3_sha3", &out, encodeToHex(val)); err != nil { 24 | return nil, err 25 | } 26 | return parseHexBytes(out) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/mitchellh/cli" 5 | "github.com/umbracle/ethgo/cmd/version" 6 | ) 7 | 8 | // VersionCommand is the command to show the version of the agent 9 | type VersionCommand struct { 10 | UI cli.Ui 11 | } 12 | 13 | // Help implements the cli.Command interface 14 | func (c *VersionCommand) Help() string { 15 | return `Usage: ethgo version 16 | 17 | Display the Ethgo version` 18 | } 19 | 20 | // Synopsis implements the cli.Command interface 21 | func (c *VersionCommand) Synopsis() string { 22 | return "Display the Ethgo version" 23 | } 24 | 25 | // Run implements the cli.Command interface 26 | func (c *VersionCommand) Run(args []string) int { 27 | c.UI.Output(version.GetVersion()) 28 | return 0 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches-ignore: 7 | - '**' 8 | tags: 9 | - 'v*.*.*' 10 | # to be used by fork patch-releases ^^ 11 | - 'v*.*.*-*' 12 | 13 | jobs: 14 | goreleaser: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Fetch all tags 19 | run: git fetch --force --tags 20 | - name: Setup go 21 | uses: actions/setup-go@v1 22 | with: 23 | go-version: '1.18.1' 24 | - name: Run GoReleaser 25 | uses: goreleaser/goreleaser-action@v2 26 | with: 27 | distribution: goreleaser 28 | version: latest 29 | args: release --rm-dist 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /jsonrpc/debug_test.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | "github.com/umbracle/ethgo/testutil" 9 | ) 10 | 11 | func TestDebug_TraceTransaction(t *testing.T) { 12 | s := testutil.NewTestServer(t) 13 | c, _ := NewClient(s.HTTPAddr()) 14 | 15 | cc := &testutil.Contract{} 16 | cc.AddEvent(testutil.NewEvent("A").Add("address", true)) 17 | cc.EmitEvent("setA", "A", addr0.String()) 18 | 19 | _, addr, err := s.DeployContract(cc) 20 | require.NoError(t, err) 21 | 22 | r, err := s.TxnTo(addr, "setA2") 23 | require.NoError(t, err) 24 | 25 | trace, err := c.Debug().TraceTransaction(r.TransactionHash, TraceTransactionOptions{}) 26 | assert.NoError(t, err) 27 | assert.Greater(t, trace.Gas, uint64(20000)) 28 | assert.NotEmpty(t, trace.StructLogs) 29 | } 30 | -------------------------------------------------------------------------------- /testcases/util.go: -------------------------------------------------------------------------------- 1 | package testcases 2 | 3 | import ( 4 | "compress/gzip" 5 | "encoding/json" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "runtime" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func ReadTestCase(t *testing.T, name string, target interface{}) { 16 | _, b, _, _ := runtime.Caller(0) 17 | d := path.Join(path.Dir(b)) 18 | 19 | testsuiteDir := filepath.Join(filepath.Dir(d), "testcases", "node_modules") 20 | if _, err := os.Stat(testsuiteDir); os.IsNotExist(err) { 21 | t.Skip("testcases not downloaded") 22 | } 23 | 24 | path := filepath.Join(testsuiteDir, "@ethersproject/testcases/testcases", name+".json.gz") 25 | 26 | f, err := os.Open(path) 27 | assert.NoError(t, err) 28 | 29 | zr, err := gzip.NewReader(f) 30 | assert.NoError(t, err) 31 | 32 | decoder := json.NewDecoder(zr) 33 | assert.NoError(t, decoder.Decode(target)) 34 | } 35 | -------------------------------------------------------------------------------- /jsonrpc/web3_test.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/umbracle/ethgo/testutil" 8 | "golang.org/x/crypto/sha3" 9 | ) 10 | 11 | func TestWeb3ClientVersion(t *testing.T) { 12 | testutil.MultiAddr(t, func(s *testutil.TestServer, addr string) { 13 | c, _ := NewClient(addr) 14 | defer c.Close() 15 | 16 | _, err := c.Web3().ClientVersion() 17 | assert.NoError(t, err) 18 | }) 19 | } 20 | 21 | func TestWeb3Sha3(t *testing.T) { 22 | testutil.MultiAddr(t, func(s *testutil.TestServer, addr string) { 23 | c, _ := NewClient(addr) 24 | defer c.Close() 25 | 26 | src := []byte{0x1, 0x2, 0x3} 27 | 28 | found, err := c.Web3().Sha3(src) 29 | assert.NoError(t, err) 30 | 31 | k := sha3.NewLegacyKeccak256() 32 | k.Write(src) 33 | expected := k.Sum(nil) 34 | 35 | assert.Equal(t, expected, found) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /jsonrpc/transport/ipc.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | ) 7 | 8 | func newIPC(addr string) (Transport, error) { 9 | conn, err := net.Dial("unix", addr) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | codec := &ipcCodec{ 15 | buf: json.RawMessage{}, 16 | conn: conn, 17 | dec: json.NewDecoder(conn), 18 | } 19 | 20 | return newStream(codec) 21 | } 22 | 23 | type ipcCodec struct { 24 | buf json.RawMessage 25 | conn net.Conn 26 | dec *json.Decoder 27 | } 28 | 29 | func (i *ipcCodec) Close() error { 30 | return i.conn.Close() 31 | } 32 | 33 | func (i *ipcCodec) Read(b []byte) ([]byte, error) { 34 | i.buf = i.buf[:0] 35 | if err := i.dec.Decode(&i.buf); err != nil { 36 | return nil, err 37 | } 38 | b = append(b, i.buf...) 39 | return b, nil 40 | } 41 | 42 | func (i *ipcCodec) Write(b []byte) error { 43 | _, err := i.conn.Write(b) 44 | return err 45 | } 46 | -------------------------------------------------------------------------------- /wallet/wallet_hd_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestWallet_Mnemonic(t *testing.T) { 10 | _, err := NewWalletFromMnemonic("sound practice disease erupt basket pumpkin truck file gorilla behave find exchange napkin boy congress address city net prosper crop chair marine chase seven") 11 | assert.NoError(t, err) 12 | } 13 | 14 | func TestWallet_MnemonicDerivationPath(t *testing.T) { 15 | cases := []struct { 16 | path string 17 | derivation DerivationPath 18 | }{ 19 | {"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, 20 | {"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, 21 | } 22 | 23 | for _, c := range cases { 24 | path, err := parseDerivationPath(c.path) 25 | assert.NoError(t, err) 26 | assert.Equal(t, *path, c.derivation) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /testutil/util.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "math/big" 5 | "reflect" 6 | 7 | "github.com/umbracle/ethgo" 8 | ) 9 | 10 | func CompareLogs(one, two []*ethgo.Log) bool { 11 | if len(one) != len(two) { 12 | return false 13 | } 14 | if len(one) == 0 { 15 | return true 16 | } 17 | return reflect.DeepEqual(one, two) 18 | } 19 | 20 | func CompareBlocks(one, two []*ethgo.Block) bool { 21 | if len(one) != len(two) { 22 | return false 23 | } 24 | if len(one) == 0 { 25 | return true 26 | } 27 | // difficulty is hard to check, set the values to zero 28 | for _, i := range one { 29 | if i.Transactions == nil { 30 | i.Transactions = []*ethgo.Transaction{} 31 | } 32 | i.Difficulty = big.NewInt(0) 33 | } 34 | for _, i := range two { 35 | if i.Transactions == nil { 36 | i.Transactions = []*ethgo.Transaction{} 37 | } 38 | i.Difficulty = big.NewInt(0) 39 | } 40 | return reflect.DeepEqual(one, two) 41 | } 42 | -------------------------------------------------------------------------------- /keystore/v4_test.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestV4_EncodeDecode(t *testing.T) { 10 | data := []byte{0x1, 0x2} 11 | password := "abcd" 12 | 13 | encrypted, err := EncryptV4(data, password) 14 | assert.NoError(t, err) 15 | 16 | found, err := DecryptV4(encrypted, password) 17 | assert.NoError(t, err) 18 | 19 | assert.Equal(t, data, found) 20 | } 21 | 22 | func TestV4_NormalizePassword(t *testing.T) { 23 | cases := []struct { 24 | input string 25 | output string 26 | }{ 27 | { 28 | "𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑", 29 | "testpassword🔑", 30 | }, 31 | { 32 | string([]byte{ 33 | 0x00, 0x1F, 34 | 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 35 | }), 36 | "password", 37 | }, 38 | } 39 | 40 | for _, c := range cases { 41 | found := normalizePassword(c.input) 42 | assert.Equal(t, c.output, found) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /builtin/ens/ens_resolver_test.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/umbracle/ethgo" 9 | "github.com/umbracle/ethgo/jsonrpc" 10 | "github.com/umbracle/ethgo/testutil" 11 | ) 12 | 13 | var ( 14 | mainnetAddr = ethgo.HexToAddress("0x314159265dD8dbb310642f98f50C066173C1259b") 15 | ) 16 | 17 | func TestResolveAddr(t *testing.T) { 18 | c, _ := jsonrpc.NewClient(testutil.TestInfuraEndpoint(t)) 19 | r := NewENSResolver(mainnetAddr, c) 20 | 21 | cases := []struct { 22 | Addr string 23 | Expected string 24 | }{ 25 | { 26 | Addr: "arachnid.eth", 27 | Expected: "0xfdb33f8ac7ce72d7d4795dd8610e323b4c122fbb", 28 | }, 29 | } 30 | 31 | for _, c := range cases { 32 | t.Run("", func(t *testing.T) { 33 | found, err := r.Resolve(c.Addr) 34 | assert.NoError(t, err) 35 | assert.Equal(t, "0x"+hex.EncodeToString(found[:]), c.Expected) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tracker/store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import "github.com/umbracle/ethgo" 4 | 5 | // Store is a datastore for the tracker 6 | type Store interface { 7 | // Get gets a value 8 | Get(k string) (string, error) 9 | 10 | // ListPrefix lists values by prefix 11 | ListPrefix(prefix string) ([]string, error) 12 | 13 | // Set sets a value 14 | Set(k, v string) error 15 | 16 | // Close closes the store 17 | Close() error 18 | 19 | // GetEntry returns a specific entry 20 | GetEntry(hash string) (Entry, error) 21 | } 22 | 23 | // Entry is a filter entry in the store 24 | type Entry interface { 25 | // LastIndex returns index of the last stored event 26 | LastIndex() (uint64, error) 27 | 28 | // StoreLogs stores the web3 logs of the event 29 | StoreLogs(logs []*ethgo.Log) error 30 | 31 | // RemoveLogs all the logs starting at index 'indx' 32 | RemoveLogs(indx uint64) error 33 | 34 | // GetLog returns the log at indx 35 | GetLog(indx uint64, log *ethgo.Log) error 36 | } 37 | -------------------------------------------------------------------------------- /jsonrpc/net.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | // Net is the net namespace 4 | type Net struct { 5 | c *Client 6 | } 7 | 8 | // Net returns the reference to the net namespace 9 | func (c *Client) Net() *Net { 10 | return c.endpoints.n 11 | } 12 | 13 | // Version returns the current network id 14 | func (n *Net) Version() (uint64, error) { 15 | var out string 16 | if err := n.c.Call("net_version", &out); err != nil { 17 | return 0, err 18 | } 19 | return parseUint64orHex(out) 20 | } 21 | 22 | // Listening returns true if client is actively listening for network connections 23 | func (n *Net) Listening() (bool, error) { 24 | var out bool 25 | err := n.c.Call("net_listening", &out) 26 | return out, err 27 | } 28 | 29 | // PeerCount returns number of peers currently connected to the client 30 | func (n *Net) PeerCount() (uint64, error) { 31 | var out string 32 | if err := n.c.Call("net_peerCount", &out); err != nil { 33 | return 0, err 34 | } 35 | return parseUint64orHex(out) 36 | } 37 | -------------------------------------------------------------------------------- /testsuite/transaction-eip2930.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "0x1", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "from": "0x0000000000000000000000000000000000000001", 5 | "input": "0x00", 6 | "value": "0x0", 7 | "gasPrice": "0x0", 8 | "gas": "0x10", 9 | "nonce": "0x10", 10 | "to": null, 11 | "v": "0x25", 12 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 13 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 14 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000001", 15 | "blockNumber": "0x0", 16 | "transactionIndex": "0x0", 17 | "chainId": "0x1", 18 | "accessList": [ 19 | { 20 | "address": "0x0000000000000000000000000000000000000001", 21 | "storageKeys": [ 22 | "0x0000000000000000000000000000000000000000000000000000000000000001" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /jsonrpc/net_test.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/umbracle/ethgo/testutil" 8 | ) 9 | 10 | func TestNetVersion(t *testing.T) { 11 | testutil.MultiAddr(t, func(s *testutil.TestServer, addr string) { 12 | c, _ := NewClient(addr) 13 | defer c.Close() 14 | 15 | _, err := c.Net().Version() 16 | assert.NoError(t, err) 17 | }) 18 | } 19 | 20 | func TestNetListening(t *testing.T) { 21 | testutil.MultiAddr(t, func(s *testutil.TestServer, addr string) { 22 | c, _ := NewClient(addr) 23 | defer c.Close() 24 | 25 | ok, err := c.Net().Listening() 26 | assert.NoError(t, err) 27 | assert.True(t, ok) 28 | }) 29 | } 30 | 31 | func TestNetPeerCount(t *testing.T) { 32 | testutil.MultiAddr(t, func(s *testutil.TestServer, addr string) { 33 | c, _ := NewClient(addr) 34 | defer c.Close() 35 | 36 | count, err := c.Net().PeerCount() 37 | assert.NoError(t, err) 38 | assert.Equal(t, count, uint64(0)) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /examples/contract-call-basic.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/umbracle/ethgo" 8 | "github.com/umbracle/ethgo/abi" 9 | "github.com/umbracle/ethgo/contract" 10 | "github.com/umbracle/ethgo/jsonrpc" 11 | ) 12 | 13 | func handleErr(err error) { 14 | if err != nil { 15 | panic(err) 16 | } 17 | } 18 | 19 | // call a contract 20 | func contractCall() { 21 | var functions = []string{ 22 | "function totalSupply() view returns (uint256)", 23 | } 24 | 25 | abiContract, err := abi.NewABIFromList(functions) 26 | handleErr(err) 27 | 28 | // Matic token 29 | addr := ethgo.HexToAddress("0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0") 30 | 31 | client, err := jsonrpc.NewClient("https://mainnet.infura.io") 32 | handleErr(err) 33 | 34 | c := contract.NewContract(addr, abiContract, contract.WithJsonRPC(client.Eth())) 35 | res, err := c.Call("totalSupply", ethgo.Latest) 36 | handleErr(err) 37 | 38 | fmt.Printf("TotalSupply: %s", res["totalSupply"].(*big.Int)) 39 | } 40 | -------------------------------------------------------------------------------- /website/pages/signers/wallet.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../../components/godoc' 3 | 4 | # Wallet 5 | 6 | The `wallet` package is an implementation of the [Signer](./signer) interface for an ECDSA private key and it represents an Ethereum account. It supports loading the private key from different mediums. 7 | 8 | ## Random 9 | 10 | Create a random key: 11 | 12 | ```go 13 | key, err := wallet.GenerateKey() 14 | ``` 15 | 16 | ## Mnemonic 17 | 18 | Create the key from a mnemonic phrase: 19 | 20 | ```go 21 | mnemonic := "" 22 | key, err := wallet.NewWalletFromMnemonic(mnemonic) 23 | ``` 24 | 25 | ## PrivateKey 26 | 27 | Create the key from a private key (in bytes): 28 | 29 | ```go 30 | key, err := wallet.NewWalletFromPrivKey([]byte{}) 31 | ``` 32 | 33 | ## Hardware file 34 | 35 | Create the key from an encrypted JSON wallet following the [keystore](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) format. 36 | 37 | ```go 38 | key, err := wallet.NewJSONWalletFromFile("./file.json") 39 | ``` 40 | -------------------------------------------------------------------------------- /testsuite/transaction-eip1559-notype.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 3 | "from": "0x0000000000000000000000000000000000000001", 4 | "input": "0x00", 5 | "value": "0x0", 6 | "maxPriorityFeePerGas": "0x10", 7 | "maxFeePerGas": "0x10", 8 | "gas": "0x10", 9 | "nonce": "0x10", 10 | "to": null, 11 | "v": "0x25", 12 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 13 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 14 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000001", 15 | "blockNumber": "0x0", 16 | "transactionIndex": "0x0", 17 | "chainId": "0x1", 18 | "accessList": [ 19 | { 20 | "address": "0x0000000000000000000000000000000000000001", 21 | "storageKeys": [ 22 | "0x0000000000000000000000000000000000000000000000000000000000000001" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /testsuite/transaction-eip1159.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "0x2", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "from": "0x0000000000000000000000000000000000000001", 5 | "input": "0x00", 6 | "value": "0x0", 7 | "maxPriorityFeePerGas": "0x10", 8 | "maxFeePerGas": "0x10", 9 | "gas": "0x10", 10 | "nonce": "0x10", 11 | "to": null, 12 | "v": "0x25", 13 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 14 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 15 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000001", 16 | "blockNumber": "0x0", 17 | "transactionIndex": "0x0", 18 | "chainId": "0x1", 19 | "accessList": [ 20 | { 21 | "address": "0x0000000000000000000000000000000000000001", 22 | "storageKeys": [ 23 | "0x0000000000000000000000000000000000000000000000000000000000000001" 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /examples/contract-call-from.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/umbracle/ethgo" 8 | "github.com/umbracle/ethgo/abi" 9 | "github.com/umbracle/ethgo/contract" 10 | "github.com/umbracle/ethgo/jsonrpc" 11 | ) 12 | 13 | // call a contract and use a custom `from` address 14 | func contractCallFrom() { 15 | var functions = []string{ 16 | "function totalSupply() view returns (uint256)", 17 | } 18 | 19 | abiContract, err := abi.NewABIFromList(functions) 20 | handleErr(err) 21 | 22 | // Matic token 23 | addr := ethgo.HexToAddress("0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0") 24 | 25 | client, err := jsonrpc.NewClient("https://mainnet.infura.io") 26 | handleErr(err) 27 | 28 | // from address (msg.sender in solidity) 29 | from := ethgo.Address{0x1} 30 | 31 | c := contract.NewContract(addr, abiContract, contract.WithSender(from), contract.WithJsonRPC(client.Eth())) 32 | res, err := c.Call("totalSupply", ethgo.Latest) 33 | handleErr(err) 34 | 35 | fmt.Printf("TotalSupply: %s", res["totalSupply"].(*big.Int)) 36 | } 37 | -------------------------------------------------------------------------------- /cmd/commands/4byte.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/mitchellh/cli" 5 | fourbyte "github.com/umbracle/ethgo/4byte" 6 | ) 7 | 8 | // FourByteCommand is the command to resolve 4byte actions 9 | type FourByteCommand struct { 10 | UI cli.Ui 11 | } 12 | 13 | // Help implements the cli.Command interface 14 | func (c *FourByteCommand) Help() string { 15 | return `Usage: ethgo 4byte [signature] 16 | 17 | Resolve a 4byte signature` 18 | } 19 | 20 | // Synopsis implements the cli.Command interface 21 | func (c *FourByteCommand) Synopsis() string { 22 | return "Resolve a 4byte signature" 23 | } 24 | 25 | // Run implements the cli.Command interface 26 | func (c *FourByteCommand) Run(args []string) int { 27 | if len(args) == 0 { 28 | c.UI.Output("No arguments provided") 29 | return 1 30 | } 31 | 32 | found, err := fourbyte.Resolve(args[0]) 33 | if err != nil { 34 | c.UI.Output(err.Error()) 35 | return 1 36 | } 37 | if found == "" { 38 | c.UI.Output("Resolve not found") 39 | return 1 40 | } 41 | 42 | c.UI.Output(found) 43 | return 0 44 | } 45 | -------------------------------------------------------------------------------- /4byte/4byte.go: -------------------------------------------------------------------------------- 1 | package fourbyte 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | const ( 11 | fourByteURL = "https://www.4byte.directory" 12 | ) 13 | 14 | // Resolve resolves a method/event signature 15 | func Resolve(str string) (string, error) { 16 | return get("/api/v1/signatures/?hex_signature=" + str) 17 | } 18 | 19 | // ResolveBytes resolves a method/event signature in bytes 20 | func ResolveBytes(b []byte) (string, error) { 21 | return Resolve(hex.EncodeToString(b)) 22 | } 23 | 24 | func get(path string) (string, error) { 25 | req, err := http.Get(fourByteURL + path) 26 | if err != nil { 27 | return "", err 28 | } 29 | defer req.Body.Close() 30 | 31 | data, err := ioutil.ReadAll(req.Body) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | var result struct { 37 | Results []signatureResult 38 | } 39 | if err := json.Unmarshal(data, &result); err != nil { 40 | return "", err 41 | } 42 | 43 | if len(result.Results) == 0 { 44 | return "", nil 45 | } 46 | return result.Results[0].TextSignature, nil 47 | } 48 | 49 | type signatureResult struct { 50 | TextSignature string `json:"text_signature"` 51 | } 52 | -------------------------------------------------------------------------------- /jsonrpc/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // Request is a jsonrpc request 9 | type Request struct { 10 | JsonRPC string `json:"jsonrpc"` 11 | ID uint64 `json:"id"` 12 | Method string `json:"method"` 13 | Params json.RawMessage `json:"params"` 14 | } 15 | 16 | // Response is a jsonrpc response 17 | type Response struct { 18 | ID uint64 `json:"id"` 19 | Result json.RawMessage `json:"result"` 20 | Error *ErrorObject `json:"error,omitempty"` 21 | } 22 | 23 | // ErrorObject is a jsonrpc error 24 | type ErrorObject struct { 25 | Code int `json:"code"` 26 | Message string `json:"message"` 27 | Data interface{} `json:"data,omitempty"` 28 | } 29 | 30 | // Subscription is a jsonrpc subscription 31 | type Subscription struct { 32 | ID string `json:"subscription"` 33 | Result json.RawMessage `json:"result"` 34 | } 35 | 36 | // Error implements error interface 37 | func (e *ErrorObject) Error() string { 38 | data, err := json.Marshal(e) 39 | if err != nil { 40 | return fmt.Sprintf("jsonrpc.internal marshal error: %v", err) 41 | } 42 | return string(data) 43 | } 44 | -------------------------------------------------------------------------------- /examples/contract-transaction.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/umbracle/ethgo" 7 | "github.com/umbracle/ethgo/abi" 8 | "github.com/umbracle/ethgo/contract" 9 | "github.com/umbracle/ethgo/jsonrpc" 10 | "github.com/umbracle/ethgo/wallet" 11 | ) 12 | 13 | func contractTransaction() { 14 | var functions = []string{ 15 | "function transferFrom(address from, address to, uint256 value)", 16 | } 17 | 18 | abiContract, err := abi.NewABIFromList(functions) 19 | handleErr(err) 20 | 21 | // Matic token 22 | addr := ethgo.HexToAddress("0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0") 23 | 24 | client, err := jsonrpc.NewClient("https://mainnet.infura.io") 25 | handleErr(err) 26 | 27 | // wallet signer 28 | key, err := wallet.GenerateKey() 29 | handleErr(err) 30 | 31 | opts := []contract.ContractOption{ 32 | contract.WithJsonRPC(client.Eth()), 33 | contract.WithSender(key), 34 | } 35 | c := contract.NewContract(addr, abiContract, opts...) 36 | txn, err := c.Txn("transferFrom", ethgo.Latest) 37 | handleErr(err) 38 | 39 | err = txn.Do() 40 | handleErr(err) 41 | 42 | receipt, err := txn.Wait() 43 | handleErr(err) 44 | 45 | fmt.Printf("Transaction mined at: %s", receipt.TransactionHash) 46 | } 47 | -------------------------------------------------------------------------------- /testsuite/block-txn-hashes.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": "0x1", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000002", 5 | "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000003", 6 | "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000001", 7 | "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000003", 8 | "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000002", 9 | "miner": "0x0000000000000000000000000000000000000001", 10 | "gasLimit": "0x2", 11 | "gasUsed": "0x3", 12 | "timestamp": "0x4", 13 | "difficulty": "0x5", 14 | "extraData": "0x01", 15 | "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000003", 16 | "nonce": "0x0a00000000000000", 17 | "uncles": [ 18 | "0x0000000000000000000000000000000000000000000000000000000000000001", 19 | "0x0000000000000000000000000000000000000000000000000000000000000002" 20 | ], 21 | "transactions": [ 22 | "0x0000000000000000000000000000000000000000000000000000000000000001" 23 | ] 24 | } -------------------------------------------------------------------------------- /cmd/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/mitchellh/cli" 7 | flag "github.com/spf13/pflag" 8 | ) 9 | 10 | func Commands() map[string]cli.CommandFactory { 11 | ui := &cli.BasicUi{ 12 | Reader: os.Stdin, 13 | Writer: os.Stdout, 14 | ErrorWriter: os.Stderr, 15 | } 16 | 17 | baseCommand := &baseCommand{ 18 | UI: ui, 19 | } 20 | 21 | return map[string]cli.CommandFactory{ 22 | "abigen": func() (cli.Command, error) { 23 | return &AbigenCommand{ 24 | baseCommand: baseCommand, 25 | }, nil 26 | }, 27 | "4byte": func() (cli.Command, error) { 28 | return &FourByteCommand{ 29 | UI: ui, 30 | }, nil 31 | }, 32 | "ens": func() (cli.Command, error) { 33 | return &EnsCommand{ 34 | UI: ui, 35 | }, nil 36 | }, 37 | "ens resolve": func() (cli.Command, error) { 38 | return &EnsResolveCommand{ 39 | UI: ui, 40 | }, nil 41 | }, 42 | "version": func() (cli.Command, error) { 43 | return &VersionCommand{ 44 | UI: ui, 45 | }, nil 46 | }, 47 | } 48 | } 49 | 50 | type baseCommand struct { 51 | UI cli.Ui 52 | } 53 | 54 | func (b *baseCommand) Flags(name string) *flag.FlagSet { 55 | flags := flag.NewFlagSet(name, 0) 56 | return flags 57 | } 58 | -------------------------------------------------------------------------------- /website/components/primitives.jsx: -------------------------------------------------------------------------------- 1 | 2 | import Link from 'next/link' 3 | import GodocLink from "./godoc" 4 | 5 | function Address({text='Address'}) { 6 | return {`(${text})`} 7 | } 8 | 9 | function Hash({text='Hash'}) { 10 | return {`(${text})`} 11 | } 12 | 13 | function Block() { 14 | return {'(Block)'} 15 | } 16 | 17 | function Blocktag() { 18 | return {'(BlockTag)'} 19 | } 20 | 21 | function ABI() { 22 | return {'(ABI)'} 23 | } 24 | 25 | function Transaction() { 26 | return {'(Transaction)'} 27 | } 28 | 29 | function Receipt() { 30 | return {'(Receipt)'} 31 | } 32 | 33 | function LogFilter() { 34 | return {'(LogFilter)'} 35 | } 36 | 37 | function Log({text='Log'}) { 38 | return {`(${text})`} 39 | } 40 | 41 | export { 42 | Address, 43 | Hash, 44 | Block, 45 | Blocktag, 46 | Transaction, 47 | Receipt, 48 | ABI, 49 | Log, 50 | LogFilter, 51 | } 52 | -------------------------------------------------------------------------------- /website/theme.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | projectLink: 'https://github.com/umbracle/ethgo', // GitHub link in the navbar 3 | docsRepositoryBase: 'https://github.com/umbracle/ethgo/tree/master/website/pages', // base URL for the docs repository 4 | titleSuffix: ' – Ethgo', 5 | nextLinks: true, 6 | prevLinks: true, 7 | search: false, 8 | customSearch: null, // customizable, you can use algolia for example 9 | darkMode: true, 10 | footer: true, 11 | footerText: ( 12 | <> 13 | Powered by Umbracle 14 | 15 | ), 16 | footerEditLink: `Edit this page on GitHub`, 17 | floatTOC: true, 18 | logo: ( 19 | <> 20 | Ethgo 21 | 22 | Go Ethereum SDK 23 | 24 | 25 | ), 26 | head: ( 27 | <> 28 | 29 | 30 | 31 | 32 | ), 33 | } -------------------------------------------------------------------------------- /tracker/store/postgresql/postgresql_store_test.go: -------------------------------------------------------------------------------- 1 | package trackerpostgresql 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/ory/dockertest" 9 | "github.com/umbracle/ethgo/tracker/store" 10 | ) 11 | 12 | func setupDB(t *testing.T) (store.Store, func()) { 13 | pool, err := dockertest.NewPool("") 14 | if err != nil { 15 | t.Fatalf("Could not connect to docker: %s", err) 16 | } 17 | 18 | resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_HOST_AUTH_METHOD=trust"}) 19 | if err != nil { 20 | t.Fatalf("Could not start resource: %s", err) 21 | } 22 | 23 | endpoint := fmt.Sprintf("postgres://postgres@localhost:%s/postgres?sslmode=disable", resource.GetPort("5432/tcp")) 24 | 25 | // wait for the db to be running 26 | if err := pool.Retry(func() error { 27 | db, err := sql.Open("postgres", endpoint) 28 | if err != nil { 29 | return err 30 | } 31 | return db.Ping() 32 | }); err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | cleanup := func() { 37 | if err := pool.Purge(resource); err != nil { 38 | t.Fatalf("Could not purge resource: %s", err) 39 | } 40 | } 41 | 42 | store, err := NewPostgreSQLStore(endpoint) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | return store, cleanup 47 | } 48 | 49 | func TestPostgreSQLStore(t *testing.T) { 50 | store.TestStore(t, setupDB) 51 | } 52 | -------------------------------------------------------------------------------- /jsonrpc/debug.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import "github.com/umbracle/ethgo" 4 | 5 | type Debug struct { 6 | c *Client 7 | } 8 | 9 | // Debug returns the reference to the debug namespace 10 | func (c *Client) Debug() *Debug { 11 | return c.endpoints.d 12 | } 13 | 14 | type TraceTransactionOptions struct { 15 | EnableMemory bool `json:"enableMemory"` 16 | DisableStack bool `json:"disableStack"` 17 | DisableStorage bool `json:"disableStorage"` 18 | EnableReturnData bool `json:"enableReturnData"` 19 | Timeout string `json:"timeout,omitempty"` 20 | Tracer string `json:"tracer,omitempty"` 21 | TracerConfig map[string]interface{} `json:"tracerConfig,omitempty"` 22 | } 23 | 24 | type TransactionTrace struct { 25 | Gas uint64 26 | ReturnValue string 27 | StructLogs []*StructLogs 28 | } 29 | 30 | type StructLogs struct { 31 | Depth int 32 | Gas int 33 | GasCost int 34 | Op string 35 | Pc int 36 | Memory []string 37 | Stack []string 38 | Storage map[string]string 39 | } 40 | 41 | func (d *Debug) TraceTransaction(hash ethgo.Hash, opts TraceTransactionOptions) (*TransactionTrace, error) { 42 | var res *TransactionTrace 43 | err := d.c.Call("debug_traceTransaction", &res, hash, opts) 44 | return res, err 45 | } 46 | -------------------------------------------------------------------------------- /structs_marshal_rlp_test.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/umbracle/fastrlp" 9 | ) 10 | 11 | func TestEncodingRLP_Transaction_Fuzz(t *testing.T) { 12 | testTransaction := func(t *testing.T, typ TransactionType) { 13 | obj := &Transaction{} 14 | err := fastrlp.Fuzz(100, obj, 15 | fastrlp.WithDefaults(func(obj fastrlp.FuzzObject) { 16 | obj.(*Transaction).Type = typ 17 | }), 18 | fastrlp.WithPostHook(func(obj fastrlp.FuzzObject) error { 19 | // Test that the hash from unmarshal is the same as the one computed 20 | txn := obj.(*Transaction) 21 | cHash, err := txn.GetHash() 22 | if err != nil { 23 | return err 24 | } 25 | if cHash != txn.Hash { 26 | return fmt.Errorf("hash not equal") 27 | } 28 | return nil 29 | }), 30 | ) 31 | assert.NoError(t, err) 32 | } 33 | 34 | t.Run("legacy", func(t *testing.T) { 35 | testTransaction(t, TransactionLegacy) 36 | }) 37 | t.Run("accesslist", func(t *testing.T) { 38 | testTransaction(t, TransactionAccessList) 39 | }) 40 | t.Run("dynamicfee", func(t *testing.T) { 41 | testTransaction(t, TransactionDynamicFee) 42 | }) 43 | } 44 | 45 | func TestEncodingRLP_AccessList_Fuzz(t *testing.T) { 46 | obj := &AccessList{} 47 | if err := fastrlp.Fuzz(100, obj); err != nil { 48 | t.Fatal(err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cmd/commands/abigen.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | flag "github.com/spf13/pflag" 5 | "github.com/umbracle/ethgo/cmd/abigen" 6 | ) 7 | 8 | // VersionCommand is the command to show the version of the agent 9 | type AbigenCommand struct { 10 | *baseCommand 11 | 12 | source string 13 | pckg string 14 | output string 15 | } 16 | 17 | // Help implements the cli.Command interface 18 | func (c *AbigenCommand) Help() string { 19 | return `Usage: ethgo abigen 20 | 21 | Compute the abigen. 22 | ` + c.Flags().FlagUsages() 23 | } 24 | 25 | // Synopsis implements the cli.Command interface 26 | func (c *AbigenCommand) Synopsis() string { 27 | return "Compute the abigen" 28 | } 29 | 30 | func (c *AbigenCommand) Flags() *flag.FlagSet { 31 | flags := c.baseCommand.Flags("abigen") 32 | 33 | flags.StringVar(&c.source, "source", "", "Source data") 34 | flags.StringVar(&c.pckg, "package", "main", "Name of the package") 35 | flags.StringVar(&c.output, "output", ".", "Output directory") 36 | 37 | return flags 38 | } 39 | 40 | // Run implements the cli.Command interface 41 | func (c *AbigenCommand) Run(args []string) int { 42 | flags := c.Flags() 43 | if err := flags.Parse(args); err != nil { 44 | c.UI.Error(err.Error()) 45 | return 1 46 | } 47 | 48 | if err := abigen.Parse(c.source, c.pckg, c.output); err != nil { 49 | c.UI.Error(err.Error()) 50 | return 1 51 | } 52 | return 0 53 | } 54 | -------------------------------------------------------------------------------- /jsonrpc/util.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type ArgBig big.Int 12 | 13 | func (a *ArgBig) UnmarshalText(input []byte) error { 14 | buf, err := parseHexBytes(string(input)) 15 | if err != nil { 16 | return err 17 | } 18 | b := new(big.Int) 19 | b.SetBytes(buf) 20 | *a = ArgBig(*b) 21 | return nil 22 | } 23 | 24 | func (a *ArgBig) Big() *big.Int { 25 | b := big.Int(*a) 26 | return &b 27 | } 28 | 29 | func encodeUintToHex(i uint64) string { 30 | return fmt.Sprintf("0x%x", i) 31 | } 32 | 33 | func parseBigInt(str string) *big.Int { 34 | str = strings.TrimPrefix(str, "0x") 35 | num := new(big.Int) 36 | num.SetString(str, 16) 37 | return num 38 | } 39 | 40 | func parseUint64orHex(str string) (uint64, error) { 41 | base := 10 42 | if strings.HasPrefix(str, "0x") { 43 | str = str[2:] 44 | base = 16 45 | } 46 | return strconv.ParseUint(str, base, 64) 47 | } 48 | 49 | func encodeToHex(b []byte) string { 50 | return "0x" + hex.EncodeToString(b) 51 | } 52 | 53 | func parseHexBytes(str string) ([]byte, error) { 54 | if !strings.HasPrefix(str, "0x") { 55 | return nil, fmt.Errorf("it does not have 0x prefix") 56 | } 57 | str = strings.TrimPrefix(str, "0x") 58 | if len(str)%2 != 0 { 59 | str = "0" + str 60 | } 61 | buf, err := hex.DecodeString(str) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return buf, nil 66 | } 67 | -------------------------------------------------------------------------------- /jsonrpc/transport/transport.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | // Transport is an inteface for transport methods to send jsonrpc requests 9 | type Transport interface { 10 | // Call makes a jsonrpc request 11 | Call(method string, out interface{}, params ...interface{}) error 12 | 13 | // SetMaxConnsPerHost sets the maximum number of connections that can be established with a host 14 | SetMaxConnsPerHost(count int) 15 | 16 | // Close closes the transport connection if necessary 17 | Close() error 18 | } 19 | 20 | // PubSubTransport is a transport that allows subscriptions 21 | type PubSubTransport interface { 22 | // Subscribe starts a subscription to a new event 23 | Subscribe(method string, callback func(b []byte)) (func() error, error) 24 | } 25 | 26 | const ( 27 | wsPrefix = "ws://" 28 | wssPrefix = "wss://" 29 | ) 30 | 31 | // NewTransport creates a new transport object 32 | func NewTransport(url string, headers map[string]string) (Transport, error) { 33 | if strings.HasPrefix(url, wsPrefix) || strings.HasPrefix(url, wssPrefix) { 34 | t, err := newWebsocket(url, headers) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return t, nil 39 | } 40 | if _, err := os.Stat(url); err == nil { 41 | // path exists, it could be an ipc path 42 | t, err := newIPC(url) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return t, nil 47 | } 48 | return newHTTP(url, headers), nil 49 | } 50 | -------------------------------------------------------------------------------- /testcases/accounts_test.go: -------------------------------------------------------------------------------- 1 | package testcases 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | "github.com/umbracle/ethgo" 11 | "github.com/umbracle/ethgo/wallet" 12 | ) 13 | 14 | func TestAccounts(t *testing.T) { 15 | var walletSpec []struct { 16 | Address string `json:"address"` 17 | Checksum string `json:"checksumAddress"` 18 | Name string `json:"name"` 19 | PrivateKey *string `json:"privateKey,omitempty"` 20 | } 21 | ReadTestCase(t, "accounts", &walletSpec) 22 | 23 | msg := []byte("msg") 24 | 25 | for _, spec := range walletSpec { 26 | // test that an string address can be checksumed 27 | addr := ethgo.HexToAddress(spec.Address) 28 | assert.Equal(t, addr.String(), spec.Checksum) 29 | 30 | if spec.PrivateKey != nil { 31 | // test that we can decode the private key 32 | priv, err := hex.DecodeString(strings.TrimPrefix(*spec.PrivateKey, "0x")) 33 | assert.NoError(t, err) 34 | 35 | key, err := wallet.NewWalletFromPrivKey(priv) 36 | assert.NoError(t, err) 37 | 38 | assert.Equal(t, key.Address().String(), spec.Checksum) 39 | 40 | // test that we can sign and recover address 41 | sig, err := key.SignMsg(msg) 42 | require.NoError(t, err) 43 | 44 | recoveredAddr, err := wallet.EcrecoverMsg(msg, sig) 45 | require.NoError(t, err) 46 | require.Equal(t, recoveredAddr, addr) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /jsonrpc/subscribe_test.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/umbracle/ethgo" 10 | "github.com/umbracle/ethgo/testutil" 11 | ) 12 | 13 | func TestSubscribeNewHead(t *testing.T) { 14 | testutil.MultiAddr(t, func(s *testutil.TestServer, addr string) { 15 | if strings.HasPrefix(addr, "http") { 16 | return 17 | } 18 | 19 | c, _ := NewClient(addr) 20 | defer c.Close() 21 | 22 | data := make(chan []byte) 23 | cancel, err := c.Subscribe("newHeads", func(b []byte) { 24 | data <- b 25 | }) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | var lastBlock *ethgo.Block 31 | recv := func(ok bool) { 32 | select { 33 | case buf := <-data: 34 | if !ok { 35 | t.Fatal("unexpected value") 36 | } 37 | 38 | var block ethgo.Block 39 | if err := block.UnmarshalJSON(buf); err != nil { 40 | t.Fatal(err) 41 | } 42 | if lastBlock != nil { 43 | if lastBlock.Number+1 != block.Number { 44 | t.Fatalf("bad sequence %d %d", lastBlock.Number, block.Number) 45 | } 46 | } 47 | lastBlock = &block 48 | 49 | case <-time.After(1 * time.Second): 50 | if ok { 51 | t.Fatal("timeout for new head") 52 | } 53 | } 54 | } 55 | 56 | s.ProcessBlock() 57 | recv(true) 58 | 59 | s.ProcessBlock() 60 | recv(true) 61 | 62 | assert.NoError(t, cancel()) 63 | 64 | s.ProcessBlock() 65 | recv(false) 66 | 67 | // subscription already closed 68 | assert.Error(t, cancel()) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /builtin/erc20/erc20_test.go: -------------------------------------------------------------------------------- 1 | package erc20 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/umbracle/ethgo" 8 | "github.com/umbracle/ethgo/contract" 9 | "github.com/umbracle/ethgo/jsonrpc" 10 | "github.com/umbracle/ethgo/testutil" 11 | ) 12 | 13 | var ( 14 | zeroX = ethgo.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 15 | ) 16 | 17 | func TestERC20Decimals(t *testing.T) { 18 | c, _ := jsonrpc.NewClient(testutil.TestInfuraEndpoint(t)) 19 | erc20 := NewERC20(zeroX, contract.WithJsonRPC(c.Eth())) 20 | 21 | decimals, err := erc20.Decimals() 22 | assert.NoError(t, err) 23 | if decimals != 18 { 24 | t.Fatal("bad") 25 | } 26 | } 27 | 28 | func TestERC20Name(t *testing.T) { 29 | c, _ := jsonrpc.NewClient(testutil.TestInfuraEndpoint(t)) 30 | erc20 := NewERC20(zeroX, contract.WithJsonRPC(c.Eth())) 31 | 32 | name, err := erc20.Name() 33 | assert.NoError(t, err) 34 | assert.Equal(t, name, "0x Protocol Token") 35 | } 36 | 37 | func TestERC20Symbol(t *testing.T) { 38 | c, _ := jsonrpc.NewClient(testutil.TestInfuraEndpoint(t)) 39 | erc20 := NewERC20(zeroX, contract.WithJsonRPC(c.Eth())) 40 | 41 | symbol, err := erc20.Symbol() 42 | assert.NoError(t, err) 43 | assert.Equal(t, symbol, "ZRX") 44 | } 45 | 46 | func TestTotalSupply(t *testing.T) { 47 | c, _ := jsonrpc.NewClient(testutil.TestInfuraEndpoint(t)) 48 | erc20 := NewERC20(zeroX, contract.WithJsonRPC(c.Eth())) 49 | 50 | supply, err := erc20.TotalSupply() 51 | assert.NoError(t, err) 52 | assert.Equal(t, supply.String(), "1000000000000000000000000000") 53 | } 54 | -------------------------------------------------------------------------------- /jsonrpc/client.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "github.com/umbracle/ethgo/jsonrpc/transport" 5 | ) 6 | 7 | // Client is the jsonrpc client 8 | type Client struct { 9 | transport transport.Transport 10 | endpoints endpoints 11 | } 12 | 13 | type endpoints struct { 14 | w *Web3 15 | e *Eth 16 | n *Net 17 | d *Debug 18 | } 19 | 20 | type Config struct { 21 | headers map[string]string 22 | } 23 | 24 | type ConfigOption func(*Config) 25 | 26 | func WithHeaders(headers map[string]string) ConfigOption { 27 | return func(c *Config) { 28 | for k, v := range headers { 29 | c.headers[k] = v 30 | } 31 | } 32 | } 33 | 34 | func NewClient(addr string, opts ...ConfigOption) (*Client, error) { 35 | config := &Config{headers: map[string]string{}} 36 | for _, opt := range opts { 37 | opt(config) 38 | } 39 | 40 | c := &Client{} 41 | c.endpoints.w = &Web3{c} 42 | c.endpoints.e = &Eth{c} 43 | c.endpoints.n = &Net{c} 44 | c.endpoints.d = &Debug{c} 45 | 46 | t, err := transport.NewTransport(addr, config.headers) 47 | if err != nil { 48 | return nil, err 49 | } 50 | c.transport = t 51 | return c, nil 52 | } 53 | 54 | // Close closes the transport 55 | func (c *Client) Close() error { 56 | return c.transport.Close() 57 | } 58 | 59 | // Call makes a jsonrpc call 60 | func (c *Client) Call(method string, out interface{}, params ...interface{}) error { 61 | return c.transport.Call(method, out, params...) 62 | } 63 | 64 | // SetMaxConnsLimit sets the maximum number of connections that can be established with a host 65 | func (c *Client) SetMaxConnsLimit(count int) { 66 | c.transport.SetMaxConnsPerHost(count) 67 | } 68 | -------------------------------------------------------------------------------- /builtin/ens/ens_resolver.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "github.com/umbracle/ethgo" 5 | "github.com/umbracle/ethgo/contract" 6 | "github.com/umbracle/ethgo/jsonrpc" 7 | "strings" 8 | ) 9 | 10 | type ENSResolver struct { 11 | e *ENS 12 | provider *jsonrpc.Eth 13 | } 14 | 15 | func NewENSResolver(addr ethgo.Address, provider *jsonrpc.Client) *ENSResolver { 16 | return &ENSResolver{NewENS(addr, contract.WithJsonRPC(provider.Eth())), provider.Eth()} 17 | } 18 | 19 | func (e *ENSResolver) Resolve(addr string, block ...ethgo.BlockNumber) (res ethgo.Address, err error) { 20 | addrHash := NameHash(addr) 21 | resolver, err := e.prepareResolver(addrHash, block...) 22 | if err != nil { 23 | return 24 | } 25 | res, err = resolver.Addr(addrHash, block...) 26 | return 27 | } 28 | 29 | func addressToReverseDomain(addr ethgo.Address) string { 30 | return strings.ToLower(strings.TrimPrefix(addr.String(), "0x")) + ".addr.reverse" 31 | } 32 | 33 | func (e *ENSResolver) ReverseResolve(addr ethgo.Address, block ...ethgo.BlockNumber) (res string, err error) { 34 | addrHash := NameHash(addressToReverseDomain(addr)) 35 | 36 | resolver, err := e.prepareResolver(addrHash, block...) 37 | if err != nil { 38 | return 39 | } 40 | res, err = resolver.Name(addrHash, block...) 41 | return 42 | } 43 | 44 | func (e *ENSResolver) prepareResolver(inputHash ethgo.Hash, block ...ethgo.BlockNumber) (*Resolver, error) { 45 | resolverAddr, err := e.e.Resolver(inputHash, block...) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | resolver := NewResolver(resolverAddr, contract.WithJsonRPC(e.provider)) 51 | return resolver, nil 52 | } 53 | -------------------------------------------------------------------------------- /cmd/abigen/testdata/testdata.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "name": "_val1", 7 | "type": "address" 8 | }, 9 | { 10 | "name": "_val2", 11 | "type": "uint256" 12 | } 13 | ], 14 | "name": "txnBasicInput", 15 | "outputs": [ 16 | { 17 | "name": "", 18 | "type": "bool" 19 | } 20 | ], 21 | "payable": false, 22 | "stateMutability": "nonpayable", 23 | "type": "function" 24 | }, 25 | { 26 | "constant": true, 27 | "inputs": [], 28 | "name": "callBasicInput", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "uint256" 33 | }, 34 | { 35 | "name": "", 36 | "type": "address" 37 | } 38 | ], 39 | "payable": false, 40 | "stateMutability": "view", 41 | "type": "function" 42 | }, 43 | { 44 | "anonymous": false, 45 | "inputs": [ 46 | { 47 | "indexed": true, 48 | "name": "owner", 49 | "type": "address" 50 | }, 51 | { 52 | "indexed": true, 53 | "name": "spender", 54 | "type": "address" 55 | }, 56 | { 57 | "indexed": false, 58 | "name": "value", 59 | "type": "uint256" 60 | } 61 | ], 62 | "name": "eventBasic", 63 | "type": "event" 64 | } 65 | ] -------------------------------------------------------------------------------- /signing/eip712_test.go: -------------------------------------------------------------------------------- 1 | package signing 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | "github.com/umbracle/ethgo" 9 | ) 10 | 11 | type Message struct { 12 | A uint64 `eip712:"a"` 13 | C *big.Int `eip712:"c"` 14 | Msg1 *Message2 `eip712:"msg1"` 15 | Msg2 []Message2 `eip712:"msg2"` 16 | Msg3 [3]Message2 `eip712:"msg3"` 17 | } 18 | 19 | type Message2 struct { 20 | B uint64 `eip712:"b"` 21 | Addr ethgo.Address `eip712:"addr"` 22 | } 23 | 24 | func TestBuildMessage_Encode(t *testing.T) { 25 | domain := &EIP712Domain{ 26 | Name: "name1", 27 | } 28 | 29 | b := NewEIP712MessageBuilder[Message](domain) 30 | require.Equal(t, "Message(uint64 a,uint256 c,Message2 msg1,Message2[] msg2,Message2[3] msg3)Message2(uint64 b,address addr)", b.GetEncodedType()) 31 | 32 | msg := &Message{ 33 | C: big.NewInt(1), 34 | Msg1: &Message2{}, 35 | Msg2: []Message2{ 36 | {B: 1}, 37 | }, 38 | } 39 | typedMsg := b.Build(msg) 40 | 41 | _, ok := typedMsg.Message["msg1"].(interface{}) 42 | require.True(t, ok) 43 | 44 | _, ok = typedMsg.Message["msg2"].([]interface{}) 45 | require.True(t, ok) 46 | 47 | _, ok = typedMsg.Message["msg3"].([3]interface{}) 48 | require.True(t, ok) 49 | 50 | _, err := typedMsg.Hash() 51 | require.NoError(t, err) 52 | } 53 | 54 | func TestBuildMessage_BasicTypes(t *testing.T) { 55 | domain := &EIP712Domain{ 56 | Name: "name1", 57 | } 58 | 59 | type Message struct { 60 | A uint64 61 | B uint32 62 | C uint16 63 | D uint8 64 | E [32]byte 65 | F string 66 | } 67 | 68 | b := NewEIP712MessageBuilder[Message](domain) 69 | require.Equal(t, "Message(uint64 A,uint32 B,uint16 C,uint8 D,[32]byte E,string F)", b.GetEncodedType()) 70 | } 71 | -------------------------------------------------------------------------------- /website/pages/jsonrpc/index.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../../components/godoc' 3 | import {Address, Hash, Blocktag, Block, Transaction, Receipt} from '../../components/primitives' 4 | 5 | # JsonRPC 6 | 7 | Ethereum uses `JsonRPC` as the main interface to interact with the client and the network. 8 | 9 | ## Overview 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "github.com/umbracle/ethgo/jsonrpc" 16 | ) 17 | 18 | func main() { 19 | client, err := jsonrpc.NewClient("https://mainnet.infura.io") 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | ``` 25 | 26 | `Ethgo` supports different transport protocols besides `http` depending on the endpoint: 27 | 28 | Use the endpoint with `wss://` prefix to connect with [`websockets`](https://en.wikipedia.org/wiki/WebSocket): 29 | 30 | ```go 31 | client, err := jsonrpc.NewClient("wss://mainnet.infura.io") 32 | ``` 33 | 34 | or the endpoint with `ipc://` prefix to use [`ipc`](https://en.wikipedia.org/wiki/Inter-process_communication): 35 | 36 | ```go 37 | client, err := jsonrpc.NewClient("ipc://path/geth.ipc") 38 | ``` 39 | 40 | ## Endpoints 41 | 42 | Once the JsonRPC client has been created, the endpoints are available on different namespaces following the spec: 43 | 44 | ```go 45 | eth := client.Eth() 46 | ``` 47 | 48 | The available namespaces are: 49 | 50 | - [Eth](./jsonrpc/eth): Ethereum network endpoints. 51 | - [Net](./jsonrpc/net): Client information. 52 | 53 | ## Block tag 54 | 55 | Some endpoints of the `eth` namespace can be queried at a specific block. There exists three ways to specify this block: 56 | 57 | - `number` or `tag` (BlockNumber): integer block number or the tag `latest`, `pending` or `earliest`. 58 | 59 | - `hash` : hash of the block. 60 | -------------------------------------------------------------------------------- /cmd/commands/ens_resolve.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/mitchellh/cli" 7 | flag "github.com/spf13/pflag" 8 | "github.com/umbracle/ethgo/ens" 9 | ) 10 | 11 | func defaultJsonRPCProvider() string { 12 | if provider := os.Getenv("JSONRPC_PROVIDER"); provider != "" { 13 | return provider 14 | } 15 | return "http://localhost:8545" 16 | } 17 | 18 | // EnsResolveCommand is the command to resolve an ens name 19 | type EnsResolveCommand struct { 20 | UI cli.Ui 21 | 22 | provider string 23 | } 24 | 25 | // Help implements the cli.Command interface 26 | func (c *EnsResolveCommand) Help() string { 27 | return `Usage: ethgo ens resolve 28 | 29 | Resolve an ens name 30 | ` + c.Flags().FlagUsages() 31 | } 32 | 33 | // Synopsis implements the cli.Command interface 34 | func (c *EnsResolveCommand) Synopsis() string { 35 | return "Resolve an ens name" 36 | } 37 | 38 | func (c *EnsResolveCommand) Flags() *flag.FlagSet { 39 | flags := flag.NewFlagSet("ens resolve", flag.PanicOnError) 40 | flags.StringVar(&c.provider, "provider", defaultJsonRPCProvider(), "") 41 | 42 | return flags 43 | } 44 | 45 | // Run implements the cli.Command interface 46 | func (c *EnsResolveCommand) Run(args []string) int { 47 | flags := c.Flags() 48 | if err := flags.Parse(args); err != nil { 49 | c.UI.Error(err.Error()) 50 | return 1 51 | } 52 | 53 | args = flags.Args() 54 | if len(args) != 1 { 55 | c.UI.Error("one argument expected") 56 | return 1 57 | } 58 | 59 | e, err := ens.NewENS(ens.WithAddress(c.provider)) 60 | if err != nil { 61 | c.UI.Error(err.Error()) 62 | return 1 63 | } 64 | 65 | addr, err := e.Resolve(args[0]) 66 | if err != nil { 67 | c.UI.Error(err.Error()) 68 | return 1 69 | } 70 | 71 | c.UI.Output(addr.String()) 72 | return 0 73 | } 74 | -------------------------------------------------------------------------------- /builtin/ens/artifacts/ENS.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}] -------------------------------------------------------------------------------- /website/pages/abi.mdx: -------------------------------------------------------------------------------- 1 | 2 | # Application Binary interface 3 | 4 | To use the library import: 5 | 6 | ```go 7 | "github.com/umbracle/ethgo/abi" 8 | ``` 9 | 10 | Declare basic objects: 11 | 12 | ```go 13 | typ, err := abi.NewType("uint256") 14 | ``` 15 | 16 | or 17 | 18 | ```go 19 | typ = abi.MustNewType("uint256") 20 | ``` 21 | 22 | and use it to encode/decode the data: 23 | 24 | ```go 25 | num := big.NewInt(1) 26 | 27 | encoded, err := typ.Encode(num) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | decoded, err := typ.Decode(encoded) // decoded as interface 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | num2 := decoded.(*big.Int) 38 | fmt.Println(num.Cmp(num2) == 0) // num == num2 39 | ``` 40 | 41 | You can also codify structs as Solidity tuples: 42 | 43 | ```go 44 | import ( 45 | "fmt" 46 | 47 | "github.com/umbracle/ethgo" 48 | "github.com/umbracle/ethgo/abi" 49 | "math/big" 50 | ) 51 | 52 | func main() { 53 | typ := abi.MustNewType("tuple(address a, uint256 b)") 54 | 55 | type Obj struct { 56 | A ethgo.Address 57 | B *big.Int 58 | } 59 | obj := &Obj{ 60 | A: ethgo.Address{0x1}, 61 | B: big.NewInt(1), 62 | } 63 | 64 | // Encode 65 | encoded, err := typ.Encode(obj) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | // Decode output into a map 71 | res, err := typ.Decode(encoded) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | // Decode into a struct 77 | var obj2 Obj 78 | if err := typ.DecodeStruct(encoded, &obj2); err != nil { 79 | panic(err) 80 | } 81 | 82 | fmt.Println(res) 83 | fmt.Println(obj) 84 | } 85 | ``` 86 | 87 | ## Testing 88 | 89 | The ABI codifier uses randomized tests with e2e integration tests with a real Geth client to ensure that the codification is correct and provides the same results as the AbiEncoder from Solidity. 90 | -------------------------------------------------------------------------------- /testcases/eip712_test.go: -------------------------------------------------------------------------------- 1 | package testcases 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | "github.com/umbracle/ethgo/signing" 10 | ) 11 | 12 | type eip712Testcase struct { 13 | Name string 14 | Domain struct { 15 | Name *string 16 | Version *string 17 | VerifyingContract *string 18 | ChainId *uint64 19 | Salt *string 20 | } 21 | Type string 22 | Seed string 23 | PrimaryType string 24 | Types map[string][]*signing.EIP712Type 25 | Data map[string]interface{} 26 | Encoded string 27 | Digest string 28 | } 29 | 30 | func (e *eip712Testcase) getDomain() *signing.EIP712Domain { 31 | d := &signing.EIP712Domain{} 32 | 33 | if name := e.Domain.Name; name != nil { 34 | d.Name = *name 35 | } 36 | if version := e.Domain.Version; version != nil { 37 | d.Version = *version 38 | } 39 | if contract := e.Domain.VerifyingContract; contract != nil { 40 | d.VerifyingContract = *contract 41 | } 42 | if chain := e.Domain.ChainId; chain != nil { 43 | d.ChainId = new(big.Int).SetUint64(*chain) 44 | } 45 | if salt := e.Domain.Salt; salt != nil { 46 | buf, _ := hex.DecodeString((*salt)[2:]) 47 | d.Salt = buf 48 | } 49 | 50 | return d 51 | } 52 | 53 | func TestEIP712(t *testing.T) { 54 | var cases []eip712Testcase 55 | ReadTestCase(t, "eip712", &cases) 56 | 57 | for indx, c := range cases { 58 | typedData := &signing.EIP712TypedData{ 59 | Types: c.Types, 60 | PrimaryType: c.PrimaryType, 61 | Domain: c.getDomain(), 62 | Message: c.Data, 63 | } 64 | 65 | digest, err := typedData.Hash() 66 | require.NoError(t, err) 67 | 68 | if c.Digest != "0x"+hex.EncodeToString(digest) { 69 | t.Fatalf("wrong digest: %d", indx) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /testcases/contract_test.go: -------------------------------------------------------------------------------- 1 | package testcases 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/umbracle/ethgo" 9 | "github.com/umbracle/ethgo/abi" 10 | "github.com/umbracle/ethgo/testutil" 11 | ) 12 | 13 | func TestContract_Signatures(t *testing.T) { 14 | var signatures []struct { 15 | Name string `json:"name"` 16 | Signature string `json:"signature"` 17 | SigHash string `json:"sigHash"` 18 | Abi string `json:"abi"` 19 | } 20 | ReadTestCase(t, "contract-signatures", &signatures) 21 | 22 | for _, c := range signatures { 23 | m, err := abi.NewMethod(c.Signature) 24 | assert.NoError(t, err) 25 | 26 | sigHash := "0x" + hex.EncodeToString(m.ID()) 27 | assert.Equal(t, sigHash, c.SigHash) 28 | } 29 | } 30 | 31 | func TestContract_Interface(t *testing.T) { 32 | t.Skip() 33 | 34 | server := testutil.NewTestServer(t) 35 | 36 | var calls []struct { 37 | Name string `json:"name"` 38 | Interface string `json:"interface"` 39 | Bytecode ethgo.ArgBytes `json:"bytecode"` 40 | Result ethgo.ArgBytes `json:"result"` 41 | Values string `json:"values"` 42 | } 43 | ReadTestCase(t, "contract-interface", &calls) 44 | 45 | for _, c := range calls { 46 | a, err := abi.NewABI(c.Interface) 47 | assert.NoError(t, err) 48 | 49 | method := a.GetMethod("test") 50 | 51 | receipt, err := server.SendTxn(ðgo.Transaction{ 52 | Input: c.Bytecode.Bytes(), 53 | }) 54 | assert.NoError(t, err) 55 | 56 | outputRaw, err := server.Call(ðgo.CallMsg{ 57 | To: &receipt.ContractAddress, 58 | Data: method.ID(), 59 | }) 60 | assert.NoError(t, err) 61 | 62 | output, err := hex.DecodeString(outputRaw[2:]) 63 | assert.NoError(t, err) 64 | 65 | _, err = method.Decode(output) 66 | assert.NoError(t, err) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /website/pages/integrations/ens.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../../components/godoc' 3 | import {Address, Hash, Blocktag, Block, Transaction, Receipt} from '../../components/primitives' 4 | 5 | # Ethereum Name Service (ENS) 6 | 7 | The Ethereum Name Service ([ENS](https://ens.domains/)) is a distributed, open, and extensible naming system based on the Ethereum blockchain. 8 | 9 | The ENS object on the `ens` package is a module that abstracts the interaction with the ENS registry. 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "github.com/umbracle/ethgo/ens" 16 | ) 17 | 18 | func main() { 19 | ensMod, err := ens.NewENS(ens.WithAddress("https://mainnet.infura.io")) 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | ``` 25 | 26 | It will default to `0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e` as the address for the ENS registry when connecting with one of the official Ethereum networks. However, this can be parametrized at creation time. This module also requires a JsonRPC connection to make the calls to the ENS registry contract. 27 | 28 | ## Options 29 | 30 | - WithAddress: JsonRPC url of the endpoint to connect with. 31 | - WithClient: [`JsonRPC`](/jsonrpc) object to make rpc calls. It takes preference over an address from `WithAddress`. 32 | - WithResolver: Custom resolver address to use rather than the default one. 33 | 34 | ## Resolve 35 | 36 | Resolve resolves an ENS name to its registered address. 37 | 38 | ```go 39 | ensMod.Resolve("umbracle.eth") 40 | ``` 41 | 42 | Input: 43 | 44 | - `name` (string): ENS name to resolve. 45 | 46 | Output: 47 | 48 | - `address`
: Ethereum address that resolves to the input name. 49 | -------------------------------------------------------------------------------- /cmd/abigen/testdata/testdata.go: -------------------------------------------------------------------------------- 1 | // Code generated by ethgo/abigen. DO NOT EDIT. 2 | // Hash: 3f1af52b391dcf1991b5cee7468a69f382cfa0f819eaff85474464c969fe7ea9 3 | // Version: 0.1.1 4 | package testdata 5 | 6 | import ( 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/umbracle/ethgo" 11 | "github.com/umbracle/ethgo/contract" 12 | "github.com/umbracle/ethgo/jsonrpc" 13 | ) 14 | 15 | var ( 16 | _ = big.NewInt 17 | _ = jsonrpc.NewClient 18 | ) 19 | 20 | // Testdata is a solidity contract 21 | type Testdata struct { 22 | c *contract.Contract 23 | } 24 | 25 | // NewTestdata creates a new instance of the contract at a specific address 26 | func NewTestdata(addr ethgo.Address, opts ...contract.ContractOption) *Testdata { 27 | return &Testdata{c: contract.NewContract(addr, abiTestdata, opts...)} 28 | } 29 | 30 | // calls 31 | 32 | // CallBasicInput calls the callBasicInput method in the solidity contract 33 | func (t *Testdata) CallBasicInput(block ...ethgo.BlockNumber) (retval0 *big.Int, retval1 ethgo.Address, err error) { 34 | var out map[string]interface{} 35 | var ok bool 36 | 37 | out, err = t.c.Call("callBasicInput", ethgo.EncodeBlock(block...)) 38 | if err != nil { 39 | return 40 | } 41 | 42 | // decode outputs 43 | retval0, ok = out["0"].(*big.Int) 44 | if !ok { 45 | err = fmt.Errorf("failed to encode output at index 0") 46 | return 47 | } 48 | retval1, ok = out["1"].(ethgo.Address) 49 | if !ok { 50 | err = fmt.Errorf("failed to encode output at index 1") 51 | return 52 | } 53 | 54 | return 55 | } 56 | 57 | // txns 58 | 59 | // TxnBasicInput sends a txnBasicInput transaction in the solidity contract 60 | func (t *Testdata) TxnBasicInput(val1 ethgo.Address, val2 *big.Int) (contract.Txn, error) { 61 | return t.c.Txn("txnBasicInput", val1, val2) 62 | } 63 | 64 | // events 65 | 66 | func (t *Testdata) EventBasicEventSig() ethgo.Hash { 67 | return t.c.GetABI().Events["EventBasic"].ID() 68 | } 69 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type ArgBig big.Int 11 | 12 | func (a *ArgBig) UnmarshalText(input []byte) error { 13 | buf, err := decodeToHex(input) 14 | if err != nil { 15 | return err 16 | } 17 | b := new(big.Int) 18 | b.SetBytes(buf) 19 | *a = ArgBig(*b) 20 | return nil 21 | } 22 | 23 | func (a ArgBig) MarshalText() ([]byte, error) { 24 | b := (*big.Int)(&a) 25 | return []byte("0x" + b.Text(16)), nil 26 | } 27 | 28 | type ArgUint64 uint64 29 | 30 | func (b ArgUint64) MarshalText() ([]byte, error) { 31 | buf := make([]byte, 2, 10) 32 | copy(buf, `0x`) 33 | buf = strconv.AppendUint(buf, uint64(b), 16) 34 | return buf, nil 35 | } 36 | 37 | func (u *ArgUint64) UnmarshalText(input []byte) error { 38 | str := strings.TrimPrefix(string(input), "0x") 39 | if str == "" { 40 | str = "0" 41 | } 42 | num, err := strconv.ParseUint(str, 16, 64) 43 | if err != nil { 44 | return err 45 | } 46 | *u = ArgUint64(num) 47 | return nil 48 | } 49 | 50 | func (u *ArgUint64) Uint64() uint64 { 51 | return uint64(*u) 52 | } 53 | 54 | type ArgBytes []byte 55 | 56 | func (b ArgBytes) MarshalText() ([]byte, error) { 57 | return encodeToHex(b), nil 58 | } 59 | 60 | func (b *ArgBytes) UnmarshalText(input []byte) error { 61 | hh, err := decodeToHex(input) 62 | if err != nil { 63 | return nil 64 | } 65 | aux := make([]byte, len(hh)) 66 | copy(aux[:], hh[:]) 67 | *b = aux 68 | return nil 69 | } 70 | 71 | func (b *ArgBytes) Bytes() []byte { 72 | return *b 73 | } 74 | 75 | func decodeToHex(b []byte) ([]byte, error) { 76 | str := string(b) 77 | str = strings.TrimPrefix(str, "0x") 78 | if len(str)%2 != 0 { 79 | str = "0" + str 80 | } 81 | return hex.DecodeString(str) 82 | } 83 | 84 | func encodeToHex(b []byte) []byte { 85 | str := hex.EncodeToString(b) 86 | if len(str)%2 != 0 { 87 | str = "0" + str 88 | } 89 | return []byte("0x" + str) 90 | } 91 | -------------------------------------------------------------------------------- /etherscan/etherscan_test.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/umbracle/ethgo" 9 | ) 10 | 11 | func testEtherscanMainnet(t *testing.T) *Etherscan { 12 | apiKey := os.Getenv("ETHERSCAN_APIKEY") 13 | if apiKey == "" { 14 | t.Skip("Etherscan APIKey not specified") 15 | } 16 | return &Etherscan{url: "https://api.etherscan.io", apiKey: apiKey} 17 | } 18 | 19 | func TestNewEtherscan(t *testing.T) { 20 | wantUrl := "http://test.url/" 21 | wantApiKey := "abc123" 22 | e := NewEtherscan(wantUrl, wantApiKey) 23 | assert.Equal(t, wantUrl, e.url) 24 | assert.Equal(t, wantApiKey, e.apiKey) 25 | } 26 | 27 | func TestBlockByNumber(t *testing.T) { 28 | e := testEtherscanMainnet(t) 29 | n, err := e.BlockNumber() 30 | assert.NoError(t, err) 31 | assert.NotEqual(t, n, 0) 32 | } 33 | 34 | func TestGetBlockByNumber(t *testing.T) { 35 | e := testEtherscanMainnet(t) 36 | b, err := e.GetBlockByNumber(1, false) 37 | assert.NoError(t, err) 38 | assert.Equal(t, b.Number, uint64(1)) 39 | } 40 | 41 | func TestContract(t *testing.T) { 42 | e := testEtherscanMainnet(t) 43 | 44 | // uniswap v2. router 45 | code, err := e.GetContractCode(ethgo.HexToAddress("0x7a250d5630b4cf539739df2c5dacb4c659f2488d")) 46 | assert.NoError(t, err) 47 | assert.Equal(t, code.Runs, "999999") 48 | } 49 | 50 | func TestGetLogs(t *testing.T) { 51 | e := testEtherscanMainnet(t) 52 | 53 | from := ethgo.BlockNumber(379224) 54 | to := ethgo.Latest 55 | 56 | filter := ðgo.LogFilter{ 57 | From: &from, 58 | To: &to, 59 | Address: []ethgo.Address{ 60 | ethgo.HexToAddress("0x33990122638b9132ca29c723bdf037f1a891a70c"), 61 | }, 62 | } 63 | logs, err := e.GetLogs(filter) 64 | assert.NoError(t, err) 65 | assert.NotEmpty(t, logs) 66 | } 67 | 68 | func TestGasPrice(t *testing.T) { 69 | e := testEtherscanMainnet(t) 70 | 71 | gas, err := e.GasPrice() 72 | assert.NoError(t, err) 73 | assert.NotZero(t, gas) 74 | } 75 | -------------------------------------------------------------------------------- /testsuite/block-full.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": "0x1", 3 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 4 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000002", 5 | "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000003", 6 | "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000001", 7 | "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000003", 8 | "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000002", 9 | "miner": "0x0000000000000000000000000000000000000001", 10 | "gasLimit": "0x2", 11 | "gasUsed": "0x3", 12 | "timestamp": "0x4", 13 | "difficulty": "0x5", 14 | "extraData": "0x01", 15 | "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000003", 16 | "nonce": "0x0a00000000000000", 17 | "uncles": [ 18 | "0x0000000000000000000000000000000000000000000000000000000000000001", 19 | "0x0000000000000000000000000000000000000000000000000000000000000002" 20 | ], 21 | "transactions": [ 22 | { 23 | "type": "0x0", 24 | "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", 25 | "from": "0x0000000000000000000000000000000000000001", 26 | "input": "0x00", 27 | "value": "0x0", 28 | "gasPrice": "0x0", 29 | "gas": "0x10", 30 | "nonce": "0x10", 31 | "to": "0x0000000000000000000000000000000000000001", 32 | "v": "0x25", 33 | "r": "0x0000000000000000000000000000000000000000000000000000000000000001", 34 | "s": "0x0000000000000000000000000000000000000000000000000000000000000001", 35 | "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000001", 36 | "blockNumber": "0x0", 37 | "transactionIndex": "0x0" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /e2e/transaction_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/umbracle/ethgo" 9 | "github.com/umbracle/ethgo/jsonrpc" 10 | "github.com/umbracle/ethgo/testutil" 11 | "github.com/umbracle/ethgo/wallet" 12 | ) 13 | 14 | func TestSendSignedTransaction(t *testing.T) { 15 | s := testutil.NewTestServer(t) 16 | 17 | key, err := wallet.GenerateKey() 18 | assert.NoError(t, err) 19 | 20 | // add value to the new key 21 | value := big.NewInt(1000000000000000000) 22 | s.Transfer(key.Address(), value) 23 | 24 | c, _ := jsonrpc.NewClient(s.HTTPAddr()) 25 | 26 | found, _ := c.Eth().GetBalance(key.Address(), ethgo.Latest) 27 | assert.Equal(t, found, value) 28 | 29 | chainID, err := c.Eth().ChainID() 30 | assert.NoError(t, err) 31 | 32 | // send a signed transaction 33 | to := ethgo.Address{0x1} 34 | transferVal := big.NewInt(1000) 35 | 36 | gasPrice, err := c.Eth().GasPrice() 37 | assert.NoError(t, err) 38 | 39 | txn := ðgo.Transaction{ 40 | To: &to, 41 | Value: transferVal, 42 | Nonce: 0, 43 | GasPrice: gasPrice, 44 | } 45 | 46 | { 47 | msg := ðgo.CallMsg{ 48 | From: key.Address(), 49 | To: &to, 50 | Value: transferVal, 51 | GasPrice: gasPrice, 52 | } 53 | limit, err := c.Eth().EstimateGas(msg) 54 | assert.NoError(t, err) 55 | 56 | txn.Gas = limit 57 | } 58 | 59 | signer := wallet.NewEIP155Signer(chainID.Uint64()) 60 | txn, err = signer.SignTx(txn, key) 61 | assert.NoError(t, err) 62 | 63 | from, err := signer.RecoverSender(txn) 64 | assert.NoError(t, err) 65 | assert.Equal(t, from, key.Address()) 66 | 67 | data, err := txn.MarshalRLPTo(nil) 68 | assert.NoError(t, err) 69 | 70 | hash, err := c.Eth().SendRawTransaction(data) 71 | assert.NoError(t, err) 72 | 73 | _, err = s.WaitForReceipt(hash) 74 | assert.NoError(t, err) 75 | 76 | balance, err := c.Eth().GetBalance(to, ethgo.Latest) 77 | assert.NoError(t, err) 78 | assert.Equal(t, balance, transferVal) 79 | } 80 | -------------------------------------------------------------------------------- /wallet/fixtures/wallet_json.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "wallet": { 4 | "crypto": { 5 | "cipher": "aes-128-ctr", 6 | "cipherparams": { 7 | "iv": "6087dab2f9fdbbfaddc31a909735c1e6" 8 | }, 9 | "ciphertext": "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", 10 | "kdf": "pbkdf2", 11 | "kdfparams": { 12 | "c": 262144, 13 | "dklen": 32, 14 | "prf": "hmac-sha256", 15 | "salt": "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" 16 | }, 17 | "mac": "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" 18 | }, 19 | "id": "3198bc9c-6672-5ab3-d995-4942343ae5b6", 20 | "version": 3 21 | }, 22 | "password": "testpassword", 23 | "address": "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b" 24 | }, 25 | { 26 | "wallet": { 27 | "crypto" : { 28 | "cipher" : "aes-128-ctr", 29 | "cipherparams" : { 30 | "iv" : "83dbcc02d8ccb40e466191a123791e0e" 31 | }, 32 | "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", 33 | "kdf" : "scrypt", 34 | "kdfparams" : { 35 | "dklen" : 32, 36 | "n" : 262144, 37 | "p" : 8, 38 | "r" : 1, 39 | "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" 40 | }, 41 | "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" 42 | }, 43 | "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", 44 | "version" : 3 45 | }, 46 | "password": "testpassword", 47 | "address": "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b" 48 | } 49 | ] -------------------------------------------------------------------------------- /builtin/erc20/artifacts/ERC20.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] -------------------------------------------------------------------------------- /cmd/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/umbracle/ethgo/cmd 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/mitchellh/cli v1.1.2 7 | github.com/umbracle/ethgo v0.0.0-20220303093617-1621d9ff042b 8 | ) 9 | 10 | require github.com/spf13/pflag v1.0.5 11 | 12 | require ( 13 | github.com/Masterminds/goutils v1.1.0 // indirect 14 | github.com/Masterminds/semver v1.5.0 // indirect 15 | github.com/Masterminds/sprig v2.22.0+incompatible // indirect 16 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect 17 | github.com/bgentry/speakeasy v0.1.0 // indirect 18 | github.com/btcsuite/btcd v0.22.1 // indirect 19 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect 20 | github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect 21 | github.com/fatih/color v1.7.0 // indirect 22 | github.com/google/gofuzz v1.2.0 // indirect 23 | github.com/google/uuid v1.1.2 // indirect 24 | github.com/gorilla/websocket v1.4.1 // indirect 25 | github.com/hashicorp/errwrap v1.0.0 // indirect 26 | github.com/hashicorp/go-multierror v1.0.0 // indirect 27 | github.com/huandu/xstrings v1.3.2 // indirect 28 | github.com/imdario/mergo v0.3.11 // indirect 29 | github.com/klauspost/compress v1.4.1 // indirect 30 | github.com/klauspost/cpuid v1.2.0 // indirect 31 | github.com/mattn/go-colorable v0.0.9 // indirect 32 | github.com/mattn/go-isatty v0.0.3 // indirect 33 | github.com/mitchellh/copystructure v1.0.0 // indirect 34 | github.com/mitchellh/mapstructure v1.1.2 // indirect 35 | github.com/mitchellh/reflectwalk v1.0.0 // indirect 36 | github.com/posener/complete v1.1.1 // indirect 37 | github.com/tyler-smith/go-bip39 v1.1.0 // indirect 38 | github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 // indirect 39 | github.com/valyala/bytebufferpool v1.0.0 // indirect 40 | github.com/valyala/fasthttp v1.4.0 // indirect 41 | github.com/valyala/fastjson v1.4.1 // indirect 42 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect 43 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect 44 | golang.org/x/text v0.3.2 // indirect 45 | ) 46 | 47 | replace github.com/umbracle/ethgo => ../ 48 | -------------------------------------------------------------------------------- /ens/ens.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/umbracle/ethgo" 8 | "github.com/umbracle/ethgo/builtin/ens" 9 | "github.com/umbracle/ethgo/jsonrpc" 10 | ) 11 | 12 | type EnsConfig struct { 13 | Logger *log.Logger 14 | Client *jsonrpc.Client 15 | Addr string 16 | Resolver ethgo.Address 17 | } 18 | 19 | type EnsOption func(*EnsConfig) 20 | 21 | func WithResolver(resolver ethgo.Address) EnsOption { 22 | return func(c *EnsConfig) { 23 | c.Resolver = resolver 24 | } 25 | } 26 | 27 | func WithLogger(logger *log.Logger) EnsOption { 28 | return func(c *EnsConfig) { 29 | c.Logger = logger 30 | } 31 | } 32 | 33 | func WithAddress(addr string) EnsOption { 34 | return func(c *EnsConfig) { 35 | c.Addr = addr 36 | } 37 | } 38 | 39 | func WithClient(client *jsonrpc.Client) EnsOption { 40 | return func(c *EnsConfig) { 41 | c.Client = client 42 | } 43 | } 44 | 45 | type ENS struct { 46 | config *EnsConfig 47 | } 48 | 49 | func NewENS(opts ...EnsOption) (*ENS, error) { 50 | config := &EnsConfig{} 51 | for _, opt := range opts { 52 | opt(config) 53 | } 54 | 55 | if config.Client == nil { 56 | // addr must be set 57 | if config.Addr == "" { 58 | return nil, fmt.Errorf("jsonrpc addr is empty") 59 | } 60 | client, err := jsonrpc.NewClient(config.Addr) 61 | if err != nil { 62 | return nil, err 63 | } 64 | config.Client = client 65 | } 66 | 67 | if config.Resolver == ethgo.ZeroAddress { 68 | // try to get the resolver address from the builtin list 69 | chainID, err := config.Client.Eth().ChainID() 70 | if err != nil { 71 | return nil, err 72 | } 73 | addr, ok := builtinEnsAddr[chainID.Uint64()] 74 | if !ok { 75 | return nil, fmt.Errorf("no builtin Ens resolver found for chain %s", chainID) 76 | } 77 | config.Resolver = addr 78 | } 79 | ens := &ENS{ 80 | config: config, 81 | } 82 | return ens, nil 83 | } 84 | 85 | func (e *ENS) Resolve(name string) (ethgo.Address, error) { 86 | resolver := ens.NewENSResolver(e.config.Resolver, e.config.Client) 87 | return resolver.Resolve(name) 88 | } 89 | 90 | func (e *ENS) ReverseResolve(addr ethgo.Address) (string, error) { 91 | resolver := ens.NewENSResolver(e.config.Resolver, e.config.Client) 92 | return resolver.ReverseResolve(addr) 93 | } 94 | -------------------------------------------------------------------------------- /structs_encoding_test.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func compactJSON(s string) string { 15 | buffer := new(bytes.Buffer) 16 | if err := json.Compact(buffer, []byte(s)); err != nil { 17 | panic(err) 18 | } 19 | return buffer.String() 20 | } 21 | 22 | func TestDecodeL2Block(t *testing.T) { 23 | c := readTestsuite(t, "./testsuite/arbitrum-block-full.json") 24 | 25 | block := new(Block) 26 | require.NoError(t, block.UnmarshalJSON(c[0].content)) 27 | 28 | for _, txn := range block.Transactions { 29 | require.NotEqual(t, txn.Type, 0) 30 | } 31 | } 32 | 33 | func TestEncodingJSON_Block(t *testing.T) { 34 | for _, c := range readTestsuite(t, "./testsuite/block-*.json") { 35 | content := []byte(compactJSON(string(c.content))) 36 | txn := new(Block) 37 | 38 | // unmarshal 39 | err := txn.UnmarshalJSON(content) 40 | assert.NoError(t, err) 41 | 42 | // marshal back 43 | res2, err := txn.MarshalJSON() 44 | assert.NoError(t, err) 45 | 46 | assert.Equal(t, content, res2) 47 | } 48 | } 49 | 50 | func TestEncodingJSON_Transaction(t *testing.T) { 51 | for _, c := range readTestsuite(t, "./testsuite/transaction-*.json") { 52 | content := []byte(compactJSON(string(c.content))) 53 | txn := new(Transaction) 54 | 55 | // unmarshal 56 | err := txn.UnmarshalJSON(content) 57 | assert.NoError(t, err) 58 | 59 | if c.name == "testsuite/transaction-eip1559-notype.json" { 60 | continue 61 | } 62 | 63 | // marshal back 64 | res2, err := txn.MarshalJSON() 65 | assert.NoError(t, err) 66 | 67 | assert.Equal(t, content, res2) 68 | } 69 | } 70 | 71 | type testFile struct { 72 | name string 73 | content []byte 74 | } 75 | 76 | func readTestsuite(t *testing.T, pattern string) (res []*testFile) { 77 | files, err := filepath.Glob(pattern) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | if len(files) == 0 { 82 | t.Fatal("no test files found") 83 | } 84 | for _, f := range files { 85 | data, err := ioutil.ReadFile(f) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | res = append(res, &testFile{ 90 | name: f, 91 | content: data, 92 | }) 93 | } 94 | return 95 | } 96 | -------------------------------------------------------------------------------- /jsonrpc/transport/http.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/umbracle/ethgo/jsonrpc/codec" 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | // HTTP is an http transport 12 | type HTTP struct { 13 | addr string 14 | client *fasthttp.Client 15 | headers map[string]string 16 | } 17 | 18 | func newHTTP(addr string, headers map[string]string) *HTTP { 19 | return &HTTP{ 20 | addr: addr, 21 | client: &fasthttp.Client{ 22 | DialDualStack: true, 23 | }, 24 | headers: headers, 25 | } 26 | } 27 | 28 | // Close implements the transport interface 29 | func (h *HTTP) Close() error { 30 | return nil 31 | } 32 | 33 | // Call implements the transport interface 34 | func (h *HTTP) Call(method string, out interface{}, params ...interface{}) error { 35 | // Encode json-rpc request 36 | request := codec.Request{ 37 | JsonRPC: "2.0", 38 | Method: method, 39 | } 40 | if len(params) > 0 { 41 | data, err := json.Marshal(params) 42 | if err != nil { 43 | return err 44 | } 45 | request.Params = data 46 | } else { 47 | request.Params = []byte{'[', ']'} 48 | } 49 | raw, err := json.Marshal(request) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | req := fasthttp.AcquireRequest() 55 | res := fasthttp.AcquireResponse() 56 | 57 | defer fasthttp.ReleaseRequest(req) 58 | defer fasthttp.ReleaseResponse(res) 59 | 60 | req.SetRequestURI(h.addr) 61 | req.Header.SetMethod("POST") 62 | req.Header.SetContentType("application/json") 63 | for k, v := range h.headers { 64 | req.Header.Add(k, v) 65 | } 66 | req.SetBody(raw) 67 | 68 | if err := h.client.Do(req, res); err != nil { 69 | return err 70 | } 71 | 72 | if sc := res.StatusCode(); sc != fasthttp.StatusOK { 73 | return fmt.Errorf("status code is %d. response = %s", sc, string(res.Body())) 74 | } 75 | 76 | // Decode json-rpc response 77 | var response codec.Response 78 | if err := json.Unmarshal(res.Body(), &response); err != nil { 79 | return err 80 | } 81 | if response.Error != nil { 82 | return response.Error 83 | } 84 | 85 | if err := json.Unmarshal(response.Result, out); err != nil { 86 | return err 87 | } 88 | return nil 89 | } 90 | 91 | // SetMaxConnsPerHost sets the maximum number of connections that can be established with a host 92 | func (h *HTTP) SetMaxConnsPerHost(count int) { 93 | h.client.MaxConnsPerHost = count 94 | } 95 | -------------------------------------------------------------------------------- /wallet/wallet_hd.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | "github.com/btcsuite/btcd/btcutil/hdkeychain" 10 | "github.com/btcsuite/btcd/chaincfg" 11 | "github.com/tyler-smith/go-bip39" 12 | ) 13 | 14 | type DerivationPath []uint32 15 | 16 | // 0x800000 17 | var decVal = big.NewInt(2147483648) 18 | 19 | // DefaultDerivationPath is the default derivation path for Ethereum addresses 20 | var DefaultDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0} 21 | 22 | func (d *DerivationPath) Derive(master *hdkeychain.ExtendedKey) (*ecdsa.PrivateKey, error) { 23 | var err error 24 | key := master 25 | for _, n := range *d { 26 | key, err = key.Derive(n) 27 | if err != nil { 28 | return nil, err 29 | } 30 | } 31 | priv, err := key.ECPrivKey() 32 | if err != nil { 33 | return nil, err 34 | } 35 | return priv.ToECDSA(), nil 36 | } 37 | 38 | func parseDerivationPath(path string) (*DerivationPath, error) { 39 | parts := strings.Split(path, "/") 40 | if len(parts) == 0 { 41 | return nil, fmt.Errorf("no derivation path") 42 | } 43 | 44 | // clean all the parts of any trim spaces 45 | for indx := range parts { 46 | parts[indx] = strings.TrimSpace(parts[indx]) 47 | } 48 | 49 | // first part has to be an 'm' 50 | if parts[0] != "m" { 51 | return nil, fmt.Errorf("first has to be m") 52 | } 53 | 54 | result := DerivationPath{} 55 | for _, p := range parts[1:] { 56 | val := new(big.Int) 57 | if strings.HasSuffix(p, "'") { 58 | p = strings.TrimSuffix(p, "'") 59 | val.Add(val, decVal) 60 | } 61 | 62 | bigVal, ok := new(big.Int).SetString(p, 0) 63 | if !ok { 64 | return nil, fmt.Errorf("invalid path") 65 | } 66 | val.Add(val, bigVal) 67 | 68 | // TODO, limit to uint32 69 | if !val.IsUint64() { 70 | return nil, fmt.Errorf("bad") 71 | } 72 | result = append(result, uint32(val.Uint64())) 73 | } 74 | 75 | return &result, nil 76 | } 77 | 78 | func NewWalletFromMnemonic(mnemonic string) (*Key, error) { 79 | seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "") 80 | if err != nil { 81 | return nil, err 82 | } 83 | masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) 84 | if err != nil { 85 | return nil, err 86 | } 87 | priv, err := DefaultDerivationPath.Derive(masterKey) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return NewKey(priv) 92 | } 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/umbracle/ethgo 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.23.3 7 | github.com/btcsuite/btcd/btcec/v2 v2.1.3 8 | github.com/btcsuite/btcd/btcutil v1.1.0 9 | github.com/gorilla/websocket v1.4.1 10 | github.com/jmoiron/sqlx v1.2.0 11 | github.com/lib/pq v1.2.0 12 | github.com/mitchellh/mapstructure v1.4.1 13 | github.com/ory/dockertest v3.3.5+incompatible 14 | github.com/stretchr/testify v1.8.0 15 | github.com/tyler-smith/go-bip39 v1.1.0 16 | github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 17 | github.com/valyala/fasthttp v1.4.0 18 | github.com/valyala/fastjson v1.4.1 19 | go.etcd.io/bbolt v1.3.6 20 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 21 | golang.org/x/text v0.8.0 22 | pgregory.net/rapid v0.5.5 23 | ) 24 | 25 | require ( 26 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect 27 | github.com/Microsoft/go-winio v0.6.0 // indirect 28 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 29 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect 30 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 31 | github.com/containerd/continuity v0.3.0 // indirect 32 | github.com/davecgh/go-spew v1.1.1 // indirect 33 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 34 | github.com/docker/go-connections v0.4.0 // indirect 35 | github.com/docker/go-units v0.4.0 // indirect 36 | github.com/go-sql-driver/mysql v1.6.0 // indirect 37 | github.com/google/gofuzz v1.2.0 // indirect 38 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect 39 | github.com/klauspost/compress v1.4.1 // indirect 40 | github.com/klauspost/cpuid v1.2.0 // indirect 41 | github.com/opencontainers/go-digest v1.0.0 // indirect 42 | github.com/opencontainers/image-spec v1.0.2 // indirect 43 | github.com/opencontainers/runc v1.1.5 // indirect 44 | github.com/pkg/errors v0.9.1 // indirect 45 | github.com/pmezard/go-difflib v1.0.0 // indirect 46 | github.com/sirupsen/logrus v1.8.1 // indirect 47 | github.com/valyala/bytebufferpool v1.0.0 // indirect 48 | golang.org/x/mod v0.9.0 // indirect 49 | golang.org/x/net v0.8.0 // indirect 50 | golang.org/x/sys v0.7.0 // indirect 51 | golang.org/x/tools v0.7.0 // indirect 52 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 53 | gopkg.in/yaml.v3 v3.0.1 // indirect 54 | gotest.tools v2.2.0+incompatible // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /wallet/signer_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | "github.com/umbracle/ethgo" 10 | "pgregory.net/rapid" 11 | ) 12 | 13 | func TestSigner_SignAndRecover(t *testing.T) { 14 | rapid.Check(t, func(t *rapid.T) { 15 | // fill in common types for a transaction 16 | txn := ðgo.Transaction{} 17 | 18 | if rapid.Bool().Draw(t, "to") { 19 | to := ethgo.BytesToAddress(rapid.SliceOf(rapid.Byte()).Draw(t, "to_addr")) 20 | txn.To = &to 21 | } 22 | 23 | txType := rapid.IntRange(0, 2).Draw(t, "tx type") 24 | 25 | // fill in specific fields depending on the type 26 | // of the transaction. 27 | txn.Type = ethgo.TransactionType(txType) 28 | if txn.Type == ethgo.TransactionDynamicFee { 29 | maxFeePerGas := rapid.Int64Range(1, 1000000000).Draw(t, "maxFeePerGas") 30 | txn.MaxFeePerGas = big.NewInt(maxFeePerGas) 31 | maxPriorityFeePerGas := rapid.Int64Range(1, 1000000000).Draw(t, "maxPriorityFeePerGas") 32 | txn.MaxPriorityFeePerGas = big.NewInt(maxPriorityFeePerGas) 33 | } else { 34 | gasPrice := rapid.Uint64Range(1, 1000000000).Draw(t, "gasPrice") 35 | txn.GasPrice = gasPrice 36 | } 37 | 38 | // signer is from a random chain 39 | chainId := rapid.Uint64().Draw(t, "chainId") 40 | signer := NewEIP155Signer(chainId) 41 | 42 | key, err := GenerateKey() 43 | require.NoError(t, err) 44 | 45 | signedTxn, err := signer.SignTx(txn, key) 46 | require.NoError(t, err) 47 | 48 | // recover the sender 49 | sender, err := signer.RecoverSender(signedTxn) 50 | require.NoError(t, err) 51 | 52 | require.Equal(t, sender, key.Address()) 53 | }) 54 | } 55 | 56 | func TestSigner_EIP1155(t *testing.T) { 57 | signer1 := NewEIP155Signer(1337) 58 | 59 | addr0 := ethgo.Address{0x1} 60 | key, err := GenerateKey() 61 | assert.NoError(t, err) 62 | 63 | txn := ðgo.Transaction{ 64 | To: &addr0, 65 | Value: big.NewInt(10), 66 | GasPrice: 0, 67 | } 68 | txn, err = signer1.SignTx(txn, key) 69 | assert.NoError(t, err) 70 | 71 | from, err := signer1.RecoverSender(txn) 72 | assert.NoError(t, err) 73 | assert.Equal(t, from, key.addr) 74 | } 75 | 76 | func TestTrimBytesZeros(t *testing.T) { 77 | assert.Equal(t, trimBytesZeros([]byte{0x1, 0x2}), []byte{0x1, 0x2}) 78 | assert.Equal(t, trimBytesZeros([]byte{0x0, 0x1}), []byte{0x1}) 79 | assert.Equal(t, trimBytesZeros([]byte{0x0, 0x0}), []byte{}) 80 | } 81 | -------------------------------------------------------------------------------- /testsuite/receipts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "blockHash": "0x1ee40a01cdb865600eef166d175eade70ab51f215c5663a7fcc5ec1f960f4e40", 4 | "blockNumber": "0x10482a", 5 | "contractAddress": "0xd63d7145f125b16bb36fe73f2128bb77dcc8ccf1", 6 | "cumulativeGasUsed": "0x3765f", 7 | "effectiveGasPrice": "0xba43b7400", 8 | "from": "0x20e021a751cb6bcab2eaf7b9db570ec10c8ad0a2", 9 | "gasUsed": "0x32457", 10 | "logs": [], 11 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 12 | "root": "0xe41f2551706d287917eb795bb81348d68ca6f9300e1394e4c8d3288ae16865dc", 13 | "to": null, 14 | "transactionHash": "0xcbb8db2cec3fa38e2ff221462708252bbbe32abb57e5a43f402155880d2883e0", 15 | "transactionIndex": "0x1", 16 | "type": "0x0" 17 | }, 18 | { 19 | "blockHash": "0x7ba787b8371397a05412121bf915373ebbcdfa6e4117f6076911a34ee9bca4a8", 20 | "blockNumber": "0xee76d0", 21 | "contractAddress": null, 22 | "cumulativeGasUsed": "0x114db42", 23 | "effectiveGasPrice": "0x222359b14", 24 | "from": "0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5", 25 | "gasUsed": "0x523f", 26 | "logs": [], 27 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 28 | "status": "0x1", 29 | "to": "0xebec795c9c8bbd61ffc14a6662944748f299cacf", 30 | "transactionHash": "0xdd002538bd3165c8aed8a7435f965420bca4406951e490190f4271302a4c5254", 31 | "transactionIndex": "0x90", 32 | "type": "0x0" 33 | } 34 | ] -------------------------------------------------------------------------------- /cmd/abigen/testdata/testdata_artifacts.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/umbracle/ethgo/abi" 8 | ) 9 | 10 | var abiTestdata *abi.ABI 11 | 12 | // TestdataAbi returns the abi of the Testdata contract 13 | func TestdataAbi() *abi.ABI { 14 | return abiTestdata 15 | } 16 | 17 | var binTestdata []byte 18 | 19 | func init() { 20 | var err error 21 | abiTestdata, err = abi.NewABI(abiTestdataStr) 22 | if err != nil { 23 | panic(fmt.Errorf("cannot parse Testdata abi: %v", err)) 24 | } 25 | if len(binTestdataStr) != 0 { 26 | binTestdata, err = hex.DecodeString(binTestdataStr[2:]) 27 | if err != nil { 28 | panic(fmt.Errorf("cannot parse Testdata bin: %v", err)) 29 | } 30 | } 31 | } 32 | 33 | var binTestdataStr = "" 34 | 35 | var abiTestdataStr = `[ 36 | { 37 | "constant": false, 38 | "inputs": [ 39 | { 40 | "name": "_val1", 41 | "type": "address" 42 | }, 43 | { 44 | "name": "_val2", 45 | "type": "uint256" 46 | } 47 | ], 48 | "name": "txnBasicInput", 49 | "outputs": [ 50 | { 51 | "name": "", 52 | "type": "bool" 53 | } 54 | ], 55 | "payable": false, 56 | "stateMutability": "nonpayable", 57 | "type": "function" 58 | }, 59 | { 60 | "constant": true, 61 | "inputs": [], 62 | "name": "callBasicInput", 63 | "outputs": [ 64 | { 65 | "name": "", 66 | "type": "uint256" 67 | }, 68 | { 69 | "name": "", 70 | "type": "address" 71 | } 72 | ], 73 | "payable": false, 74 | "stateMutability": "view", 75 | "type": "function" 76 | }, 77 | { 78 | "anonymous": false, 79 | "inputs": [ 80 | { 81 | "indexed": true, 82 | "name": "owner", 83 | "type": "address" 84 | }, 85 | { 86 | "indexed": true, 87 | "name": "spender", 88 | "type": "address" 89 | }, 90 | { 91 | "indexed": false, 92 | "name": "value", 93 | "type": "uint256" 94 | } 95 | ], 96 | "name": "eventBasic", 97 | "type": "event" 98 | } 99 | ]` 100 | -------------------------------------------------------------------------------- /keystore/utils.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "encoding/json" 10 | "fmt" 11 | "strings" 12 | 13 | "golang.org/x/crypto/pbkdf2" 14 | "golang.org/x/crypto/scrypt" 15 | ) 16 | 17 | func getRand(size int) []byte { 18 | buf := make([]byte, size) 19 | rand.Read(buf) 20 | return buf 21 | } 22 | 23 | type hexString []byte 24 | 25 | func (h hexString) MarshalJSON() ([]byte, error) { 26 | str := "\"" + hex.EncodeToString(h) + "\"" 27 | return []byte(str), nil 28 | } 29 | 30 | func (h *hexString) UnmarshalJSON(data []byte) error { 31 | raw := string(data) 32 | raw = strings.Trim(raw, "\"") 33 | 34 | data, err := hex.DecodeString(raw) 35 | if err != nil { 36 | return err 37 | } 38 | *h = data 39 | return nil 40 | } 41 | 42 | func aesCTR(key, cipherText, iv []byte) ([]byte, error) { 43 | block, err := aes.NewCipher(key) 44 | if err != nil { 45 | return nil, err 46 | } 47 | stream := cipher.NewCTR(block, iv) 48 | 49 | dst := make([]byte, len(cipherText)) 50 | stream.XORKeyStream(dst, cipherText) 51 | 52 | return dst, nil 53 | } 54 | 55 | type pbkdf2Params struct { 56 | Dklen int `json:"dklen"` 57 | Salt hexString `json:"salt"` 58 | C int `json:"c"` 59 | Prf string `json:"prf"` 60 | } 61 | 62 | func (p *pbkdf2Params) Key(password []byte) []byte { 63 | return pbkdf2.Key(password, p.Salt, p.C, p.Dklen, sha256.New) 64 | } 65 | 66 | type scryptParams struct { 67 | Dklen int `json:"dklen"` 68 | Salt hexString `json:"salt"` 69 | N int `json:"n"` 70 | P int `json:"p"` 71 | R int `json:"r"` 72 | } 73 | 74 | func (s *scryptParams) Key(password []byte) ([]byte, error) { 75 | return scrypt.Key(password, s.Salt, s.N, s.R, s.P, s.Dklen) 76 | } 77 | 78 | func applyKdf(fn string, password, paramsRaw []byte) ([]byte, error) { 79 | var key []byte 80 | 81 | if fn == "pbkdf2" { 82 | var params pbkdf2Params 83 | if err := json.Unmarshal(paramsRaw, ¶ms); err != nil { 84 | return nil, err 85 | } 86 | if params.Prf != "hmac-sha256" { 87 | return nil, fmt.Errorf("not found") 88 | } 89 | key = params.Key(password) 90 | } else if fn == "scrypt" { 91 | var params scryptParams 92 | err := json.Unmarshal(paramsRaw, ¶ms) 93 | if err != nil { 94 | return nil, err 95 | } 96 | key, err = params.Key(password) 97 | if err != nil { 98 | return nil, err 99 | } 100 | } else { 101 | return nil, fmt.Errorf("kdf '%s' not supported", fn) 102 | } 103 | return key, nil 104 | } 105 | -------------------------------------------------------------------------------- /builtin/ens/artifacts/ENS.bin: -------------------------------------------------------------------------------- 1 | 0x6060604052341561000f57600080fd5b60008080526020527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb58054600160a060020a033316600160a060020a0319909116179055610503806100626000396000f3006060604052600436106100825763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630178b8bf811461008757806302571be3146100b957806306ab5923146100cf57806314ab9038146100f657806316a25cbd146101195780631896f70a1461014c5780635b0fc9c31461016e575b600080fd5b341561009257600080fd5b61009d600435610190565b604051600160a060020a03909116815260200160405180910390f35b34156100c457600080fd5b61009d6004356101ae565b34156100da57600080fd5b6100f4600435602435600160a060020a03604435166101c9565b005b341561010157600080fd5b6100f460043567ffffffffffffffff6024351661028b565b341561012457600080fd5b61012f600435610357565b60405167ffffffffffffffff909116815260200160405180910390f35b341561015757600080fd5b6100f4600435600160a060020a036024351661038e565b341561017957600080fd5b6100f4600435600160a060020a0360243516610434565b600090815260208190526040902060010154600160a060020a031690565b600090815260208190526040902054600160a060020a031690565b600083815260208190526040812054849033600160a060020a039081169116146101f257600080fd5b8484604051918252602082015260409081019051908190039020915083857fce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e8285604051600160a060020a03909116815260200160405180910390a3506000908152602081905260409020805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03929092169190911790555050565b600082815260208190526040902054829033600160a060020a039081169116146102b457600080fd5b827f1d4f9bbfc9cab89d66e1a1562f2233ccbf1308cb4f63de2ead5787adddb8fa688360405167ffffffffffffffff909116815260200160405180910390a250600091825260208290526040909120600101805467ffffffffffffffff90921674010000000000000000000000000000000000000000027fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff909216919091179055565b60009081526020819052604090206001015474010000000000000000000000000000000000000000900467ffffffffffffffff1690565b600082815260208190526040902054829033600160a060020a039081169116146103b757600080fd5b827f335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a083604051600160a060020a03909116815260200160405180910390a250600091825260208290526040909120600101805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03909216919091179055565b600082815260208190526040902054829033600160a060020a0390811691161461045d57600080fd5b827fd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d26683604051600160a060020a03909116815260200160405180910390a250600091825260208290526040909120805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039092169190911790555600a165627a7a72305820f4c798d4c84c9912f389f64631e85e8d16c3e6644f8c2e1579936015c7d5f6660029 -------------------------------------------------------------------------------- /abi/topics_test.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | "github.com/umbracle/ethgo" 12 | "github.com/umbracle/ethgo/testutil" 13 | ) 14 | 15 | func TestTopicEncoding(t *testing.T) { 16 | cases := []struct { 17 | Type string 18 | Val interface{} 19 | }{ 20 | { 21 | Type: "bool", 22 | Val: true, 23 | }, 24 | { 25 | Type: "bool", 26 | Val: false, 27 | }, 28 | { 29 | Type: "uint64", 30 | Val: uint64(20), 31 | }, 32 | { 33 | Type: "uint256", 34 | Val: big.NewInt(1000000), 35 | }, 36 | { 37 | Type: "address", 38 | Val: ethgo.Address{0x1}, 39 | }, 40 | } 41 | 42 | for _, c := range cases { 43 | tt, err := NewType(c.Type) 44 | assert.NoError(t, err) 45 | 46 | res, err := EncodeTopic(tt, c.Val) 47 | assert.NoError(t, err) 48 | 49 | val, err := ParseTopic(tt, res) 50 | assert.NoError(t, err) 51 | 52 | assert.Equal(t, val, c.Val) 53 | } 54 | } 55 | 56 | func TestIntegrationTopics(t *testing.T) { 57 | s := testutil.NewTestServer(t) 58 | 59 | type field struct { 60 | typ string 61 | indx bool 62 | val interface{} 63 | valStr string 64 | } 65 | 66 | cases := []struct { 67 | fields []field 68 | }{ 69 | { 70 | // uint 71 | fields: []field{ 72 | {"uint32", false, uint32(1), "1"}, 73 | {"uint8", true, uint8(10), "10"}, 74 | }, 75 | }, 76 | { 77 | // fixed bytes 78 | fields: []field{ 79 | {"bytes1", false, [1]byte{0x1}, "0x01"}, 80 | {"bytes1", true, [1]byte{0x1}, "0x01"}, 81 | }, 82 | }, 83 | } 84 | 85 | for _, c := range cases { 86 | cc := &testutil.Contract{} 87 | 88 | evnt := testutil.NewEvent("A") 89 | input := []string{} 90 | 91 | result := map[string]interface{}{} 92 | for indx, field := range c.fields { 93 | evnt.Add(field.typ, field.indx) 94 | input = append(input, field.valStr) 95 | result[fmt.Sprintf("val_%d", indx)] = field.val 96 | } 97 | 98 | cc.AddEvent(evnt) 99 | cc.EmitEvent("setA", "A", input...) 100 | 101 | // deploy the contract 102 | artifact, addr, err := s.DeployContract(cc) 103 | require.NoError(t, err) 104 | 105 | receipt, err := s.TxnTo(addr, "setA") 106 | require.NoError(t, err) 107 | 108 | // read the abi 109 | abi, err := NewABI(artifact.Abi) 110 | assert.NoError(t, err) 111 | 112 | // parse the logs 113 | found, err := ParseLog(abi.Events["A"].Inputs, receipt.Logs[0]) 114 | assert.NoError(t, err) 115 | 116 | if !reflect.DeepEqual(found, result) { 117 | t.Fatal("not equal") 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /builtin/erc20/erc20_artifacts.go: -------------------------------------------------------------------------------- 1 | package erc20 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/umbracle/ethgo/abi" 8 | ) 9 | 10 | var abiERC20 *abi.ABI 11 | 12 | // ERC20Abi returns the abi of the ERC20 contract 13 | func ERC20Abi() *abi.ABI { 14 | return abiERC20 15 | } 16 | 17 | var binERC20 []byte 18 | 19 | func init() { 20 | var err error 21 | abiERC20, err = abi.NewABI(abiERC20Str) 22 | if err != nil { 23 | panic(fmt.Errorf("cannot parse ERC20 abi: %v", err)) 24 | } 25 | if len(binERC20Str) != 0 { 26 | binERC20, err = hex.DecodeString(binERC20Str[2:]) 27 | if err != nil { 28 | panic(fmt.Errorf("cannot parse ERC20 bin: %v", err)) 29 | } 30 | } 31 | } 32 | 33 | var binERC20Str = "" 34 | 35 | var abiERC20Str = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]` 36 | -------------------------------------------------------------------------------- /builtin/ens/artifacts/Resolver.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"},{"name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"name":"contentType","type":"uint256"},{"name":"data","type":"bytes"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"x","type":"bytes32"},{"name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"content","outputs":[{"name":"ret","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"addr","outputs":[{"name":"ret","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"contentType","type":"uint256"},{"name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"name","outputs":[{"name":"ret","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"hash","type":"bytes32"}],"name":"setContent","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"name":"x","type":"bytes32"},{"name":"y","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"addr","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"ensAddr","type":"address"}],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes32"}],"name":"ContentChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"x","type":"bytes32"},{"indexed":false,"name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"}] -------------------------------------------------------------------------------- /wallet/key.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "fmt" 8 | 9 | "github.com/btcsuite/btcd/btcec/v2" 10 | btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" 11 | "github.com/umbracle/ethgo" 12 | ) 13 | 14 | // S256 is the secp256k1 elliptic curve 15 | var S256 = btcec.S256() 16 | 17 | var _ ethgo.Key = &Key{} 18 | 19 | // Key is an implementation of the Key interface with a private key 20 | type Key struct { 21 | priv *btcec.PrivateKey 22 | pub *btcec.PublicKey 23 | addr ethgo.Address 24 | } 25 | 26 | func (k *Key) Address() ethgo.Address { 27 | return k.addr 28 | } 29 | 30 | func (k *Key) MarshallPrivateKey() ([]byte, error) { 31 | return (*btcec.PrivateKey)(k.priv).Serialize(), nil 32 | } 33 | 34 | func (k *Key) SignMsg(msg []byte) ([]byte, error) { 35 | return k.Sign(ethgo.Keccak256(msg)) 36 | } 37 | 38 | func (k *Key) Sign(hash []byte) ([]byte, error) { 39 | sig, err := btcecdsa.SignCompact(k.priv, hash, false) 40 | if err != nil { 41 | return nil, err 42 | } 43 | term := byte(0) 44 | if sig[0] == 28 { 45 | term = 1 46 | } 47 | return append(sig, term)[1:], nil 48 | } 49 | 50 | // NewKey creates a new key with a private key 51 | func NewKey(prv *ecdsa.PrivateKey) (*Key, error) { 52 | var priv btcec.PrivateKey 53 | if overflow := priv.Key.SetByteSlice(prv.D.Bytes()); overflow || priv.Key.IsZero() { 54 | return nil, fmt.Errorf("invalid key: overflow") 55 | } 56 | 57 | k := &Key{ 58 | priv: &priv, 59 | pub: priv.PubKey(), 60 | addr: pubKeyToAddress(priv.PubKey().ToECDSA()), 61 | } 62 | return k, nil 63 | } 64 | 65 | func pubKeyToAddress(pub *ecdsa.PublicKey) (addr ethgo.Address) { 66 | b := ethgo.Keccak256(elliptic.Marshal(S256, pub.X, pub.Y)[1:]) 67 | copy(addr[:], b[12:]) 68 | return 69 | } 70 | 71 | // GenerateKey generates a new key based on the secp256k1 elliptic curve. 72 | func GenerateKey() (*Key, error) { 73 | priv, err := ecdsa.GenerateKey(S256, rand.Reader) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return NewKey(priv) 78 | } 79 | 80 | func EcrecoverMsg(msg, signature []byte) (ethgo.Address, error) { 81 | return Ecrecover(ethgo.Keccak256(msg), signature) 82 | } 83 | 84 | func Ecrecover(hash, signature []byte) (ethgo.Address, error) { 85 | pub, err := RecoverPubkey(signature, hash) 86 | if err != nil { 87 | return ethgo.Address{}, err 88 | } 89 | return pubKeyToAddress(pub), nil 90 | } 91 | 92 | func RecoverPubkey(signature, hash []byte) (*ecdsa.PublicKey, error) { 93 | size := len(signature) 94 | term := byte(27) 95 | if signature[size-1] == 1 { 96 | term = 28 97 | } 98 | 99 | sig := append([]byte{term}, signature[:size-1]...) 100 | pub, _, err := btcecdsa.RecoverCompact(sig, hash) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return pub.ToECDSA(), nil 105 | } 106 | -------------------------------------------------------------------------------- /tracker/store/inmem/inmem_store.go: -------------------------------------------------------------------------------- 1 | package inmem 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | 7 | "github.com/umbracle/ethgo" 8 | "github.com/umbracle/ethgo/tracker/store" 9 | ) 10 | 11 | var _ store.Store = (*InmemStore)(nil) 12 | 13 | // InmemStore implements the Store interface. 14 | type InmemStore struct { 15 | l sync.RWMutex 16 | entries map[string]*Entry 17 | kv map[string]string 18 | } 19 | 20 | // NewInmemStore returns a new in-memory store. 21 | func NewInmemStore() *InmemStore { 22 | return &InmemStore{ 23 | entries: map[string]*Entry{}, 24 | kv: map[string]string{}, 25 | } 26 | } 27 | 28 | // Close implements the store interface 29 | func (i *InmemStore) Close() error { 30 | return nil 31 | } 32 | 33 | // Get implements the store interface 34 | func (i *InmemStore) Get(k string) (string, error) { 35 | i.l.Lock() 36 | defer i.l.Unlock() 37 | return i.kv[string(k)], nil 38 | } 39 | 40 | // ListPrefix implements the store interface 41 | func (i *InmemStore) ListPrefix(prefix string) ([]string, error) { 42 | i.l.Lock() 43 | defer i.l.Unlock() 44 | 45 | res := []string{} 46 | for k, v := range i.kv { 47 | if strings.HasPrefix(k, prefix) { 48 | res = append(res, v) 49 | } 50 | } 51 | return res, nil 52 | } 53 | 54 | // Set implements the store interface 55 | func (i *InmemStore) Set(k, v string) error { 56 | i.l.Lock() 57 | defer i.l.Unlock() 58 | i.kv[string(k)] = v 59 | return nil 60 | } 61 | 62 | // GetEntry implements the store interface 63 | func (i *InmemStore) GetEntry(hash string) (store.Entry, error) { 64 | i.l.Lock() 65 | defer i.l.Unlock() 66 | e, ok := i.entries[hash] 67 | if ok { 68 | return e, nil 69 | } 70 | e = &Entry{ 71 | logs: []*ethgo.Log{}, 72 | } 73 | i.entries[hash] = e 74 | return e, nil 75 | } 76 | 77 | // Entry is a store.Entry implementation 78 | type Entry struct { 79 | l sync.RWMutex 80 | logs []*ethgo.Log 81 | } 82 | 83 | // LastIndex implements the store interface 84 | func (e *Entry) LastIndex() (uint64, error) { 85 | e.l.Lock() 86 | defer e.l.Unlock() 87 | return uint64(len(e.logs)), nil 88 | } 89 | 90 | // Logs returns the logs of the inmemory store 91 | func (e *Entry) Logs() []*ethgo.Log { 92 | return e.logs 93 | } 94 | 95 | // StoreLogs implements the store interface 96 | func (e *Entry) StoreLogs(logs []*ethgo.Log) error { 97 | e.l.Lock() 98 | defer e.l.Unlock() 99 | for _, log := range logs { 100 | e.logs = append(e.logs, log) 101 | } 102 | return nil 103 | } 104 | 105 | // RemoveLogs implements the store interface 106 | func (e *Entry) RemoveLogs(indx uint64) error { 107 | e.l.Lock() 108 | defer e.l.Unlock() 109 | e.logs = e.logs[:indx] 110 | return nil 111 | } 112 | 113 | // GetLog implements the store interface 114 | func (e *Entry) GetLog(indx uint64, log *ethgo.Log) error { 115 | *log = *e.logs[indx] 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /website/pages/integrations/etherscan.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../../components/godoc' 3 | import {Log, Address, Hash, Blocktag, Block, Transaction, Receipt, LogFilter} from '../../components/primitives' 4 | 5 | # Etherscan 6 | 7 | [Etherscan](https://etherscan.io/) is a block explorer and it implements some read compatible endpoint with the [JsonRPC](/jsonrpc) spec. It requires an [apiKey](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics) to use the service and not be rate limited. 8 | 9 | Create an instance of Etherscan from a network id: 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "github.com/umbracle/ethgo/etherscan" 16 | "github.com/umbracle/ethgo" 17 | ) 18 | 19 | func main() { 20 | ethscan, err := etherscan.NewEtherscanFromNetwork(ethgo.Mainnet, "apiKey") 21 | } 22 | ``` 23 | 24 | The package will resolve the name of the network to the specific endpoint in Etherscan, at this point it only works for `Mainnet`, `Ropsten`, `Rinkeby` and `Goerli`. 25 | 26 | For a custom url use: 27 | 28 | ```go 29 | ethscan, err := etherscan.NewEtherscan("https://api.polygonscan.com", "apiKey") 30 | ``` 31 | 32 | ## BlockNumber 33 | 34 | BlockNumber returns the current block number. 35 | 36 | ```go 37 | num, err := ethscan.BlockNumber() 38 | ``` 39 | 40 | Output 41 | 42 | - `num` (`uint64`): Last block number. 43 | 44 | ## GetBlockByNumber 45 | 46 | GetBlockByNumber returns a specific block by its number. 47 | 48 | ```go 49 | block, err := ethscan.GetBlockByNumber(ethgo.BlockNumber(100), true) 50 | ``` 51 | 52 | Input: 53 | 54 | - `block number` : Block number selection 55 | - `full` (`bool`): Whether to return the full block with transactions. 56 | 57 | Output: 58 | 59 | - `block`: (): Block object 60 | 61 | ## GetContractCode 62 | 63 | GetContractCode returns the contract of a given address (if any). 64 | 65 | ```go 66 | code, err := ethscan.GetContractCode(address) 67 | ``` 68 | 69 | Input: 70 | 71 | - `address`
: Address of the contract. 72 | 73 | Output: 74 | 75 | - `code` (`[]byte`): Code of the contract. 76 | 77 | ## GetLogs 78 | 79 | GetLogs returns the logs given a log filter. 80 | 81 | ```go 82 | filter := ðgo.LogFilter{ 83 | Address: []ethgo.Address{ 84 | ethgo.HexToAddress("..."), 85 | }, 86 | } 87 | logs, err := ethscan.GetLogs(filter) 88 | ``` 89 | 90 | Input: 91 | 92 | - `filter` : Filter for the logs to return. 93 | 94 | Output: 95 | 96 | - `logs` : List of logs that match the filter. 97 | 98 | ## GasPrice 99 | 100 | GasPrice returns the gas price of the latest block. 101 | 102 | Output: 103 | 104 | - `gasPrice` (`uint64`): Gas price of the latest block. 105 | -------------------------------------------------------------------------------- /website/pages/contract.mdx: -------------------------------------------------------------------------------- 1 | 2 | import GoDocLink from '../components/godoc' 3 | import EIPLink from '../components/eip' 4 | import {Address, ABI} from '../components/primitives' 5 | 6 | # Contract 7 | 8 | The Contract struct represents a deployed Solidity contract. 9 | 10 | To instantiate a `Contract` object use: 11 | 12 | ```go 13 | contract.NewContract(addr, abi) 14 | ``` 15 | 16 | with: 17 | 18 | - `addr`
: Address of the contract. 19 | - `abi` : ABI of the contract. 20 | 21 | By default, it connects to the `https://localhost:8545` JsonRPC endpoint. 22 | 23 | ## Options 24 | 25 | Besides `addr` and `abi`, you can use the option pattern to parametrize the contract, the available options are: 26 | 27 | - WithJsonRPCEndpoint: JsonRPC url of the endpoint to connect with. 28 | - WithJsonRPCClient: [`JsonRPC`](/jsonrpc) object to make rpc calls. It takes preference over an address from `WithAddress`. 29 | - WithSigner: [`Signer`](/signers/signer) object to send transactions or use a custom `from` address. 30 | - WithProvider: Custom NodeProvider implementation to resolve calls and transactions. 31 | - WithEIP1559: Send transactions with EIP-1559 pricing. 32 | 33 | ## Examples 34 | 35 | Check [examples](https://github.com/umbracle/ethgo/tree/master/examples) for a list of examples on how to interact with a smart contract. 36 | 37 | ### Call a contract 38 | 39 | ```go 40 | package main 41 | 42 | import ( 43 | "fmt" 44 | "math/big" 45 | 46 | "github.com/umbracle/ethgo" 47 | "github.com/umbracle/ethgo/abi" 48 | "github.com/umbracle/ethgo/contract" 49 | "github.com/umbracle/ethgo/jsonrpc" 50 | ) 51 | 52 | func handleErr(err error) { 53 | if err != nil { 54 | panic(err) 55 | } 56 | } 57 | 58 | // call a contract 59 | func main() { 60 | var functions = []string{ 61 | "function totalSupply() view returns (uint256)", 62 | } 63 | 64 | abiContract, err := abi.NewABIFromList(functions) 65 | handleErr(err) 66 | 67 | // Matic token 68 | addr := ethgo.HexToAddress("0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0") 69 | 70 | client, err := jsonrpc.NewClient("https://mainnet.infura.io") 71 | handleErr(err) 72 | 73 | c := contract.NewContract(addr, abiContract, contract.WithJsonRPC(client.Eth())) 74 | res, err := c.Call("totalSupply", ethgo.Latest) 75 | handleErr(err) 76 | 77 | fmt.Printf("TotalSupply: %s", res["totalSupply"].(*big.Int)) 78 | } 79 | ``` 80 | 81 | ## Abigen 82 | 83 | One small limitation of `Contract` is that works with `interface` objects since the input and outputs of a smart contract are arbitrary. As an alternative, you can use [Abigen](./cli/abigen) to generate Go bindings that wrap the `Contract` object and provide native and typed Go functions to interact with the contracts. 84 | 85 | By default, `ethgo` includes builtin `abigen` contracts for `ens` and `erc20` tokens. 86 | -------------------------------------------------------------------------------- /structs_marshal_test.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func generateHashPtr(input string) *Hash { 13 | res := HexToHash(input) 14 | return &res 15 | } 16 | 17 | func TestLogFilter_MarshalJSON(t *testing.T) { 18 | testTable := []struct { 19 | name string 20 | topics [][]*Hash 21 | }{ 22 | { 23 | "match any topic", 24 | [][]*Hash{ 25 | nil, 26 | {}, 27 | }, 28 | }, 29 | { 30 | "match single topic in pos. 1", 31 | [][]*Hash{ 32 | { 33 | generateHashPtr("0xa"), 34 | }, 35 | }, 36 | }, 37 | { 38 | "match single topic in pos. 2", 39 | [][]*Hash{ 40 | {}, 41 | { 42 | generateHashPtr("0xb"), 43 | }, 44 | }, 45 | }, 46 | { 47 | "match topic in pos. 1 AND pos. 2", 48 | [][]*Hash{ 49 | { 50 | generateHashPtr("0xa"), 51 | }, 52 | { 53 | generateHashPtr("0xb"), 54 | }, 55 | }, 56 | }, 57 | { 58 | "match topic A or B in pos. 1 AND C or D in pos. 2", 59 | [][]*Hash{ 60 | { 61 | generateHashPtr("0xa"), 62 | generateHashPtr("0xb"), 63 | }, 64 | { 65 | generateHashPtr("0xc"), 66 | generateHashPtr("0xd"), 67 | }, 68 | }, 69 | }, 70 | } 71 | 72 | defaultLogFilter := &LogFilter{ 73 | Address: []Address{HexToAddress("0x123")}, 74 | Topics: nil, 75 | BlockHash: generateHashPtr("0xabc"), 76 | } 77 | for _, testCase := range testTable { 78 | t.Run(testCase.name, func(t *testing.T) { 79 | defaultLogFilter.Topics = testCase.topics 80 | 81 | // Marshal it to JSON 82 | output, marshalErr := defaultLogFilter.MarshalJSON() 83 | if marshalErr != nil { 84 | t.Fatalf("Unable to marshal value, %v", marshalErr) 85 | } 86 | 87 | // Unmarshal it from JSON 88 | reverseOutput := &LogFilter{} 89 | unmarshalErr := json.Unmarshal(output, reverseOutput) 90 | if unmarshalErr != nil { 91 | t.Fatalf("Unable to unmarshal value, %v", unmarshalErr) 92 | } 93 | 94 | // Assert that the original and unmarshalled values match 95 | assert.Equal(t, defaultLogFilter, reverseOutput) 96 | }) 97 | } 98 | } 99 | 100 | func TestMarshal_StateOverride(t *testing.T) { 101 | nonce := uint64(1) 102 | code := []byte{0x1} 103 | 104 | o := StateOverride{ 105 | {0x0}: OverrideAccount{ 106 | Nonce: &nonce, 107 | Balance: big.NewInt(1), 108 | Code: &code, 109 | State: &map[Hash]Hash{ 110 | {0x1}: {0x1}, 111 | }, 112 | StateDiff: &map[Hash]Hash{ 113 | {0x1}: {0x1}, 114 | }, 115 | }, 116 | } 117 | 118 | res, err := o.MarshalJSON() 119 | require.NoError(t, err) 120 | 121 | expected := `{"0x0000000000000000000000000000000000000000":{"nonce":"0x1","balance":"0x1","code":"0x01","state":{"0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000"},"stateDiff":{"0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000"}}}` 122 | require.Equal(t, expected, string(res)) 123 | } 124 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.4 (Unreleased) 2 | 3 | - feat: Add override to `eth_call` request [[GH-240](https://github.com/umbracle/ethgo/issues/240)] 4 | - fix: Recovery of typed transactions [[GH-238](https://github.com/umbracle/ethgo/issues/238)] 5 | - fix: Parse `nonce` and `mixHash` on `Block` [[GH-228](https://github.com/umbracle/ethgo/issues/228)] 6 | - feat: `abi` decodes function string in multilines [[GH-212](https://github.com/umbracle/ethgo/issues/212)] 7 | - feat: `abi` DecodeStruct uses the `abi` tag instead of the default `mapstructure` [[GH-211](https://github.com/umbracle/ethgo/issues/211)] 8 | - feat: Implement `ens` reverse resolver [[GH-210](https://github.com/umbracle/ethgo/issues/210)] 9 | - fix: Jsonrpc eth_getLogs request cannot return string [[GH-209](https://github.com/umbracle/ethgo/issues/209)] 10 | 11 | # 0.1.3 (13 June, 2022) 12 | 13 | - Fix out-of-bounds reading of bytes during ABI decoding [[GH-205](https://github.com/umbracle/ethgo/issues/205)] 14 | - Update `fastrlp` to `59d5dd3` commit to fix a bug on bytes length check [[GH-204](https://github.com/umbracle/ethgo/issues/204)] 15 | - Fix out-of-bounds RLP unmarshal of transactions [[GH-203](https://github.com/umbracle/ethgo/issues/203)] 16 | 17 | # 0.1.2 (5 May, 2022) 18 | 19 | - Update `btcd` library to new `v0.22.1` 20 | - Add option in `contract` to send transactions with EIP-1559 [[GH-198](https://github.com/umbracle/ethgo/issues/198)] 21 | - Add custom `TxnOpts` to send a transaction in `contract` [[GH-195](https://github.com/umbracle/ethgo/issues/195)] 22 | - Add `ens resolve` command to resolve an ENS name [[GH-196](https://github.com/umbracle/ethgo/issues/196)] 23 | - Fix signing of typed transactions [[GH-197](https://github.com/umbracle/ethgo/issues/197)] 24 | - Fix. Use `ethgo.BlockNumber` input to make `Call` in contract [[GH-194](https://github.com/umbracle/ethgo/issues/194)] 25 | - Add `testcases` for contract signature and transaction signing [[GH-193](https://github.com/umbracle/ethgo/issues/193)] 26 | - Add `eth_feeHistory` rpc endpoint [[GH-192](https://github.com/umbracle/ethgo/issues/192)] 27 | - Update `testserver` to `go-ethereum:v1.10.15` [[GH-191](https://github.com/umbracle/ethgo/issues/191)] 28 | - Do not decode `to` in `Transaction` if not exists [[GH-190](https://github.com/umbracle/ethgo/issues/190)] 29 | 30 | # 0.1.1 (25 April, 2022) 31 | 32 | - Retrieve latest nonce when sending a transaction on `contract` [[GH-185](https://github.com/umbracle/ethgo/issues/185)] 33 | - Add `etherscan.GasPrice` function to return last block gas price [[GH-182](https://github.com/umbracle/ethgo/issues/182)] 34 | - Add `4byte` package and cli [[GH-178](https://github.com/umbracle/ethgo/issues/178)] 35 | - Install and use `ethers.js` spec tests for wallet private key decoding [[GH-177](https://github.com/umbracle/ethgo/issues/177)] 36 | - Add `GetLogs` function Etherscan to return logs by filter [[GH-170](https://github.com/umbracle/ethgo/issues/170)] 37 | - Add `Copy` function to major data types [[GH-169](https://github.com/umbracle/ethgo/issues/169)] 38 | - Parse `fixed bytes` type in event topic [[GH-168](https://github.com/umbracle/ethgo/issues/168)] 39 | - Introduce `NodeProvider` and update `Contract` and `abigen` format. [[GH-167](https://github.com/umbracle/ethgo/issues/167)] 40 | 41 | # 0.1.0 (5 March, 2022) 42 | 43 | - Initial public release. 44 | -------------------------------------------------------------------------------- /structs_test.go: -------------------------------------------------------------------------------- 1 | package ethgo 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "math/big" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestAddress_Checksum(t *testing.T) { 14 | cases := []struct { 15 | src, dst string 16 | }{ 17 | { 18 | "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", 19 | "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", 20 | }, 21 | { 22 | "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", 23 | "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", 24 | }, 25 | { 26 | "0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb", 27 | "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", 28 | }, 29 | { 30 | "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", 31 | "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", 32 | }, 33 | } 34 | for _, c := range cases { 35 | addr := HexToAddress(c.src) 36 | assert.Equal(t, addr.String(), c.dst) 37 | } 38 | } 39 | 40 | func TestAddress_HexToString(t *testing.T) { 41 | assert.Equal(t, HexToAddress("0x1").String(), "0x0000000000000000000000000000000000000001") 42 | assert.Equal(t, HexToAddress("00000000000000000000000000000000000000001").String(), "0x0000000000000000000000000000000000000001") 43 | assert.Equal(t, HexToAddress("0000000000000000000000000000000000000001").String(), "0x0000000000000000000000000000000000000001") 44 | } 45 | 46 | func TestHash_HexToString(t *testing.T) { 47 | assert.Equal(t, HexToHash("1").String(), "0x0000000000000000000000000000000000000000000000000000000000000001") 48 | } 49 | 50 | func TestBlock_Copy(t *testing.T) { 51 | b := &Block{ 52 | Difficulty: big.NewInt(1), 53 | Transactions: []*Transaction{}, 54 | ExtraData: []byte{0x1, 0x2}, 55 | } 56 | b1 := b.Copy() 57 | if !reflect.DeepEqual(b, b1) { 58 | t.Fatal("incorrect block copy") 59 | } 60 | } 61 | 62 | func TestTransaction_Copy(t *testing.T) { 63 | txn := &Transaction{ 64 | GasPrice: 10, 65 | Input: []byte{0x1, 0x2}, 66 | V: []byte{0x1, 0x2}, 67 | R: []byte{0x1, 0x2}, 68 | S: []byte{0x1, 0x2}, 69 | AccessList: AccessList{ 70 | AccessEntry{ 71 | Address: Address{0x1}, 72 | Storage: []Hash{ 73 | {0x1}, 74 | }, 75 | }, 76 | }, 77 | } 78 | txn1 := txn.Copy() 79 | if !reflect.DeepEqual(txn, txn1) { 80 | t.Fatal("incorrect transaction") 81 | } 82 | } 83 | 84 | func TestReceipt_Copy(t *testing.T) { 85 | r := &Receipt{ 86 | LogsBloom: []byte{0x1, 0x2}, 87 | Logs: []*Log{ 88 | {LogIndex: 1, Topics: []Hash{{0x1}}}, 89 | }, 90 | GasUsed: 10, 91 | } 92 | rr := r.Copy() 93 | if !reflect.DeepEqual(r, rr) { 94 | t.Fatal("incorrect receipt") 95 | } 96 | } 97 | 98 | func TestLog_Copy(t *testing.T) { 99 | l := &Log{ 100 | Data: []byte{0x1, 0x2}, 101 | BlockHash: Hash{0x1}, 102 | } 103 | ll := l.Copy() 104 | if !reflect.DeepEqual(l, ll) { 105 | t.Fatal("incorrect receipt") 106 | } 107 | } 108 | 109 | //go:embed testsuite/receipts.json 110 | var receiptsFixtures []byte 111 | 112 | func TestReceipt_Unmarshal(t *testing.T) { 113 | var cases []json.RawMessage 114 | assert.NoError(t, json.Unmarshal(receiptsFixtures, &cases)) 115 | 116 | for _, c := range cases { 117 | receipt := &Receipt{} 118 | assert.NoError(t, receipt.UnmarshalJSON(c)) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tracker/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Tracker 3 | 4 | ``` 5 | package main 6 | 7 | import ( 8 | "context" 9 | "encoding/binary" 10 | "flag" 11 | "fmt" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | 16 | "github.com/umbracle/ethgo" 17 | "github.com/umbracle/ethgo/abi" 18 | "github.com/umbracle/ethgo/jsonrpc" 19 | "github.com/umbracle/ethgo/tracker" 20 | 21 | boltdbStore "github.com/umbracle/ethgo/tracker/store/boltdb" 22 | ) 23 | 24 | var depositEvent = abi.MustNewEvent(`DepositEvent( 25 | bytes pubkey, 26 | bytes whitdrawalcred, 27 | bytes amount, 28 | bytes signature, 29 | bytes index 30 | )`) 31 | 32 | func main() { 33 | var endpoint string 34 | var target string 35 | 36 | flag.StringVar(&endpoint, "endpoint", "", "") 37 | flag.StringVar(&target, "target", "", "") 38 | 39 | flag.Parse() 40 | 41 | provider, err := jsonrpc.NewClient(endpoint) 42 | if err != nil { 43 | fmt.Printf("[ERR]: %v", err) 44 | os.Exit(1) 45 | } 46 | 47 | store, err := boltdbStore.New("deposit.db") 48 | if err != nil { 49 | fmt.Printf("[ERR]: failted to start store %v", err) 50 | os.Exit(1) 51 | } 52 | 53 | tt, err := tracker.NewTracker(provider.Eth(), 54 | tracker.WithBatchSize(20000), 55 | tracker.WithStore(store), 56 | tracker.WithEtherscan(os.Getenv("ETHERSCAN_APIKEY")), 57 | tracker.WithFilter(&tracker.FilterConfig{ 58 | Async: true, 59 | Address: []ethgo.Address{ 60 | ethgo.HexToAddress(target), 61 | }, 62 | }), 63 | ) 64 | if err != nil { 65 | fmt.Printf("[ERR]: failed to create the tracker %v", err) 66 | os.Exit(1) 67 | } 68 | 69 | lastBlock, err := tt.GetLastBlock() 70 | if err != nil { 71 | fmt.Printf("[ERR]: failed to get last block %v", err) 72 | os.Exit(1) 73 | } 74 | if lastBlock != nil { 75 | fmt.Printf("Last block processed: %d\n", lastBlock.Number) 76 | } 77 | 78 | ctx, cancelFn := context.WithCancel(context.Background()) 79 | go func() { 80 | go func() { 81 | if err := tt.Sync(ctx); err != nil { 82 | fmt.Printf("[ERR]: %v", err) 83 | } 84 | }() 85 | 86 | go func() { 87 | for { 88 | select { 89 | case evnt := <-tt.EventCh: 90 | for _, log := range evnt.Added { 91 | if depositEvent.Match(log) { 92 | vals, err := depositEvent.ParseLog(log) 93 | if err != nil { 94 | panic(err) 95 | } 96 | 97 | index := binary.LittleEndian.Uint64(vals["index"].([]byte)) 98 | amount := binary.LittleEndian.Uint64(vals["amount"].([]byte)) 99 | 100 | fmt.Printf("Deposit: Block %d Index %d Amount %d\n", log.BlockNumber, index, amount) 101 | } 102 | } 103 | case <-tt.DoneCh: 104 | fmt.Println("historical sync done") 105 | } 106 | } 107 | }() 108 | 109 | }() 110 | 111 | handleSignals(cancelFn) 112 | } 113 | 114 | func handleSignals(cancelFn context.CancelFunc) int { 115 | signalCh := make(chan os.Signal, 4) 116 | signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 117 | 118 | <-signalCh 119 | 120 | gracefulCh := make(chan struct{}) 121 | go func() { 122 | cancelFn() 123 | close(gracefulCh) 124 | }() 125 | 126 | select { 127 | case <-signalCh: 128 | return 1 129 | case <-gracefulCh: 130 | return 0 131 | } 132 | } 133 | ``` 134 | 135 | You can query the ETH2.0 Deposit contract like so: 136 | 137 | ``` 138 | go run main.go --endpoint https://mainnet.infura.io/v3/... --target 0x00000000219ab540356cbb839cbe05303d7705fa 139 | ``` 140 | -------------------------------------------------------------------------------- /keystore/v3.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "encoding/json" 7 | "fmt" 8 | 9 | "github.com/umbracle/ethgo" 10 | ) 11 | 12 | // EncryptV3 encrypts data in v3 format 13 | func EncryptV3(content []byte, password string, customScrypt ...int) ([]byte, error) { 14 | 15 | // default scrypt values 16 | scryptN, scryptP := 1<<18, 1 17 | 18 | if len(customScrypt) >= 1 { 19 | scryptN = customScrypt[0] 20 | } 21 | if len(customScrypt) >= 2 { 22 | scryptP = customScrypt[1] 23 | } 24 | 25 | iv := getRand(aes.BlockSize) 26 | 27 | scrypt := scryptParams{ 28 | N: scryptN, 29 | R: 8, 30 | P: scryptP, 31 | Dklen: 32, 32 | Salt: hexString(getRand(32)), 33 | } 34 | kdf, err := scrypt.Key([]byte(password)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | cipherText, err := aesCTR(kdf[:16], content, iv) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | // generate mac 45 | mac := ethgo.Keccak256(kdf[16:32], cipherText) 46 | 47 | v3 := &v3Encoding{ 48 | Version: 3, 49 | Crypto: &cryptoEncoding{ 50 | Cipher: "aes-128-ctr", 51 | CipherText: hexString(cipherText), 52 | CipherParams: struct{ IV hexString }{ 53 | IV: hexString(iv), 54 | }, 55 | KDF: "scrypt", 56 | KDFParams: scrypt, 57 | Mac: hexString(mac), 58 | }, 59 | } 60 | 61 | encrypted, err := v3.Marshal() 62 | if err != nil { 63 | return nil, err 64 | } 65 | return encrypted, nil 66 | } 67 | 68 | // DecryptV3 decodes bytes in the v3 keystore format 69 | func DecryptV3(content []byte, password string) ([]byte, error) { 70 | encoding := v3Encoding{} 71 | if err := encoding.Unmarshal(content); err != nil { 72 | return nil, err 73 | } 74 | if encoding.Version != 3 { 75 | return nil, fmt.Errorf("only version 3 supported") 76 | } 77 | if encoding.Crypto.Cipher != "aes-128-ctr" { 78 | return nil, fmt.Errorf("cipher %s not supported", encoding.Crypto.Cipher) 79 | } 80 | 81 | // decode the kdf 82 | kdf, err := applyKdf(encoding.Crypto.KDF, []byte(password), encoding.Crypto.KDFParamsRaw) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | // validate mac 88 | mac := ethgo.Keccak256(kdf[16:32], encoding.Crypto.CipherText) 89 | if !bytes.Equal(mac, encoding.Crypto.Mac) { 90 | return nil, fmt.Errorf("incorrect mac") 91 | } 92 | 93 | dst, err := aesCTR(kdf[:16], encoding.Crypto.CipherText, encoding.Crypto.CipherParams.IV) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return dst, nil 98 | } 99 | 100 | type v3Encoding struct { 101 | ID string `json:"id"` 102 | Version int64 `json:"version"` 103 | Crypto *cryptoEncoding `json:"crypto"` 104 | } 105 | 106 | func (j *v3Encoding) Marshal() ([]byte, error) { 107 | params, err := json.Marshal(j.Crypto.KDFParams) 108 | if err != nil { 109 | return nil, err 110 | } 111 | j.Crypto.KDFParamsRaw = json.RawMessage(params) 112 | return json.Marshal(j) 113 | } 114 | 115 | func (j *v3Encoding) Unmarshal(data []byte) error { 116 | return json.Unmarshal(data, j) 117 | } 118 | 119 | type cryptoEncoding struct { 120 | Cipher string `json:"cipher"` 121 | CipherParams struct { 122 | IV hexString 123 | } `json:"cipherparams"` 124 | CipherText hexString `json:"ciphertext"` 125 | KDF string `json:"kdf"` 126 | KDFParams interface{} 127 | KDFParamsRaw json.RawMessage `json:"kdfparams"` 128 | Mac hexString `json:"mac"` 129 | } 130 | -------------------------------------------------------------------------------- /wallet/signer.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/umbracle/ethgo" 7 | "github.com/umbracle/fastrlp" 8 | ) 9 | 10 | type Signer interface { 11 | // RecoverSender returns the sender to the transaction 12 | RecoverSender(tx *ethgo.Transaction) (ethgo.Address, error) 13 | 14 | // SignTx signs a transaction 15 | SignTx(tx *ethgo.Transaction, key ethgo.Key) (*ethgo.Transaction, error) 16 | } 17 | 18 | type EIP1155Signer struct { 19 | chainID uint64 20 | } 21 | 22 | func NewEIP155Signer(chainID uint64) *EIP1155Signer { 23 | return &EIP1155Signer{chainID: chainID} 24 | } 25 | 26 | func (e *EIP1155Signer) RecoverSender(tx *ethgo.Transaction) (ethgo.Address, error) { 27 | v := new(big.Int).SetBytes(tx.V).Uint64() 28 | if v > 1 { 29 | v -= 27 30 | if v > 1 { 31 | v -= e.chainID * 2 32 | v -= 8 33 | } 34 | } 35 | 36 | sig, err := encodeSignature(tx.R, tx.S, byte(v)) 37 | if err != nil { 38 | return ethgo.Address{}, err 39 | } 40 | addr, err := Ecrecover(signHash(tx, e.chainID), sig) 41 | if err != nil { 42 | return ethgo.Address{}, err 43 | } 44 | return addr, nil 45 | } 46 | 47 | func trimBytesZeros(b []byte) []byte { 48 | var i int 49 | for i = 0; i < len(b); i++ { 50 | if b[i] != 0x0 { 51 | break 52 | } 53 | } 54 | return b[i:] 55 | } 56 | 57 | func (e *EIP1155Signer) SignTx(tx *ethgo.Transaction, key ethgo.Key) (*ethgo.Transaction, error) { 58 | hash := signHash(tx, e.chainID) 59 | 60 | sig, err := key.Sign(hash) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | vv := uint64(sig[64]) 66 | if tx.Type == 0 { 67 | vv = vv + 35 + e.chainID*2 68 | } 69 | 70 | tx.R = trimBytesZeros(sig[:32]) 71 | tx.S = trimBytesZeros(sig[32:64]) 72 | tx.V = new(big.Int).SetUint64(vv).Bytes() 73 | return tx, nil 74 | } 75 | 76 | func signHash(tx *ethgo.Transaction, chainID uint64) []byte { 77 | a := fastrlp.DefaultArenaPool.Get() 78 | defer fastrlp.DefaultArenaPool.Put(a) 79 | 80 | v := a.NewArray() 81 | 82 | if tx.Type != ethgo.TransactionLegacy { 83 | // either dynamic and access type 84 | v.Set(a.NewBigInt(new(big.Int).SetUint64(chainID))) 85 | } 86 | 87 | v.Set(a.NewUint(tx.Nonce)) 88 | 89 | if tx.Type == ethgo.TransactionDynamicFee { 90 | // dynamic fee uses 91 | v.Set(a.NewBigInt(tx.MaxPriorityFeePerGas)) 92 | v.Set(a.NewBigInt(tx.MaxFeePerGas)) 93 | } else { 94 | // legacy and access type use gas price 95 | v.Set(a.NewUint(tx.GasPrice)) 96 | } 97 | 98 | v.Set(a.NewUint(tx.Gas)) 99 | if tx.To == nil { 100 | v.Set(a.NewNull()) 101 | } else { 102 | v.Set(a.NewCopyBytes((*tx.To)[:])) 103 | } 104 | v.Set(a.NewBigInt(tx.Value)) 105 | v.Set(a.NewCopyBytes(tx.Input)) 106 | 107 | if tx.Type != ethgo.TransactionLegacy { 108 | // either dynamic and access type 109 | accessList, err := tx.AccessList.MarshalRLPWith(a) 110 | if err != nil { 111 | panic(err) 112 | } 113 | v.Set(accessList) 114 | } 115 | 116 | // EIP155 117 | if chainID != 0 && tx.Type == ethgo.TransactionLegacy { 118 | v.Set(a.NewUint(chainID)) 119 | v.Set(a.NewUint(0)) 120 | v.Set(a.NewUint(0)) 121 | } 122 | 123 | dst := v.MarshalTo(nil) 124 | 125 | // append the tx type byte 126 | if tx.Type != ethgo.TransactionLegacy { 127 | dst = append([]byte{byte(tx.Type)}, dst...) 128 | } 129 | return ethgo.Keccak256(dst) 130 | } 131 | 132 | func encodeSignature(R, S []byte, V byte) ([]byte, error) { 133 | sig := make([]byte, 65) 134 | copy(sig[32-len(R):32], R) 135 | copy(sig[64-len(S):64], S) 136 | sig[64] = V 137 | return sig, nil 138 | } 139 | -------------------------------------------------------------------------------- /compiler/solidity_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | var ( 17 | solcDir = "/tmp/ethgo-solc" 18 | solcPath = solcDir + "/solidity" 19 | ) 20 | 21 | func init() { 22 | _, err := os.Stat(solcDir) 23 | if err == nil { 24 | // already exists 25 | return 26 | } 27 | if !os.IsNotExist(err) { 28 | panic(err) 29 | } 30 | // solc folder does not exists 31 | if err := DownloadSolidity("0.5.5", solcDir, false); err != nil { 32 | panic(err) 33 | } 34 | } 35 | 36 | func TestSolidityInline(t *testing.T) { 37 | solc := NewSolidityCompiler(solcPath) 38 | 39 | cases := []struct { 40 | code string 41 | contracts []string 42 | }{ 43 | { 44 | ` 45 | pragma solidity >0.0.0; 46 | contract foo{} 47 | `, 48 | []string{ 49 | "foo", 50 | }, 51 | }, 52 | { 53 | ` 54 | pragma solidity >0.0.0; 55 | contract foo{} 56 | contract bar{} 57 | `, 58 | []string{ 59 | "bar", 60 | "foo", 61 | }, 62 | }, 63 | } 64 | 65 | for _, c := range cases { 66 | t.Run("", func(t *testing.T) { 67 | output, err := solc.CompileCode(c.code) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | result := map[string]struct{}{} 73 | for i := range output.Contracts { 74 | result[strings.TrimPrefix(i, ":")] = struct{}{} 75 | } 76 | 77 | // only one source file 78 | assert.Len(t, output.Sources, 1) 79 | 80 | expected := map[string]struct{}{} 81 | for _, i := range c.contracts { 82 | expected[i] = struct{}{} 83 | } 84 | 85 | if !reflect.DeepEqual(result, expected) { 86 | t.Fatal("bad") 87 | } 88 | }) 89 | } 90 | } 91 | 92 | func TestSolidity(t *testing.T) { 93 | solc := NewSolidityCompiler(solcPath) 94 | 95 | files := []string{ 96 | "./fixtures/ballot.sol", 97 | "./fixtures/simple_auction.sol", 98 | } 99 | output, err := solc.Compile(files...) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | if len(output.Contracts) != 2 { 104 | t.Fatal("two expected") 105 | } 106 | } 107 | 108 | func existsSolidity(t *testing.T, path string) bool { 109 | _, err := os.Stat(path) 110 | if err != nil { 111 | if os.IsNotExist(err) { 112 | return false 113 | } 114 | t.Fatal(err) 115 | } 116 | 117 | cmd := exec.Command(path, "--version") 118 | var stderr, stdout bytes.Buffer 119 | cmd.Stderr = &stderr 120 | cmd.Stdout = &stdout 121 | if err := cmd.Run(); err != nil { 122 | t.Fatalf("solidity version failed: %s", string(stderr.Bytes())) 123 | } 124 | if len(stdout.Bytes()) == 0 { 125 | t.Fatal("empty output") 126 | } 127 | return true 128 | } 129 | 130 | func TestDownloadSolidityCompiler(t *testing.T) { 131 | dst1, err := ioutil.TempDir("/tmp", "ethgo-") 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | defer os.RemoveAll(dst1) 136 | 137 | if err := DownloadSolidity("0.5.5", dst1, true); err != nil { 138 | t.Fatal(err) 139 | } 140 | if existsSolidity(t, filepath.Join(dst1, "solidity")) { 141 | t.Fatal("it should not exist") 142 | } 143 | if !existsSolidity(t, filepath.Join(dst1, "solidity-0.5.5")) { 144 | t.Fatal("it should exist") 145 | } 146 | 147 | dst2, err := ioutil.TempDir("/tmp", "ethgo-") 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | defer os.RemoveAll(dst2) 152 | 153 | if err := DownloadSolidity("0.5.5", dst2, false); err != nil { 154 | t.Fatal(err) 155 | } 156 | if !existsSolidity(t, filepath.Join(dst2, "solidity")) { 157 | t.Fatal("it should exist") 158 | } 159 | if existsSolidity(t, filepath.Join(dst2, "solidity-0.5.5")) { 160 | t.Fatal("it should not exist") 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /abi/topics.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/umbracle/ethgo" 9 | ) 10 | 11 | // ParseLog parses an event log 12 | func ParseLog(args *Type, log *ethgo.Log) (map[string]interface{}, error) { 13 | var indexed, nonIndexed []*TupleElem 14 | 15 | for _, arg := range args.TupleElems() { 16 | if arg.Indexed { 17 | indexed = append(indexed, arg) 18 | } else { 19 | nonIndexed = append(nonIndexed, arg) 20 | } 21 | } 22 | 23 | // decode indexed fields 24 | indexedObjs, err := ParseTopics(&Type{kind: KindTuple, tuple: indexed}, log.Topics[1:]) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | var nonIndexedObjs map[string]interface{} 30 | if len(nonIndexed) > 0 { 31 | nonIndexedRaw, err := Decode(&Type{kind: KindTuple, tuple: nonIndexed}, log.Data) 32 | if err != nil { 33 | return nil, err 34 | } 35 | raw, ok := nonIndexedRaw.(map[string]interface{}) 36 | if !ok { 37 | return nil, fmt.Errorf("bad decoding") 38 | } 39 | nonIndexedObjs = raw 40 | } 41 | 42 | res := map[string]interface{}{} 43 | for _, arg := range args.TupleElems() { 44 | if arg.Indexed { 45 | res[arg.Name] = indexedObjs[0] 46 | indexedObjs = indexedObjs[1:] 47 | } else { 48 | res[arg.Name] = nonIndexedObjs[arg.Name] 49 | } 50 | } 51 | 52 | return res, nil 53 | } 54 | 55 | // ParseTopics parses topics from a log event 56 | func ParseTopics(args *Type, topics []ethgo.Hash) ([]interface{}, error) { 57 | if args.kind != KindTuple { 58 | return nil, fmt.Errorf("expected a tuple type") 59 | } 60 | if len(args.TupleElems()) != len(topics) { 61 | return nil, fmt.Errorf("bad length") 62 | } 63 | 64 | elems := []interface{}{} 65 | for indx, arg := range args.TupleElems() { 66 | elem, err := ParseTopic(arg.Elem, topics[indx]) 67 | if err != nil { 68 | return nil, err 69 | } 70 | elems = append(elems, elem) 71 | } 72 | 73 | return elems, nil 74 | } 75 | 76 | // ParseTopic parses an individual topic 77 | func ParseTopic(t *Type, topic ethgo.Hash) (interface{}, error) { 78 | switch t.kind { 79 | case KindBool: 80 | if bytes.Equal(topic[:], topicTrue[:]) { 81 | return true, nil 82 | } else if bytes.Equal(topic[:], topicFalse[:]) { 83 | return false, nil 84 | } 85 | return true, fmt.Errorf("is not a boolean") 86 | 87 | case KindInt, KindUInt: 88 | return readInteger(t, topic[:]), nil 89 | 90 | case KindAddress: 91 | return readAddr(topic[:]) 92 | 93 | case KindFixedBytes: 94 | return readFixedBytes(t, topic[:]) 95 | 96 | default: 97 | return nil, fmt.Errorf("topic parsing for type %s not supported", t.String()) 98 | } 99 | } 100 | 101 | // EncodeTopic encodes a topic 102 | func EncodeTopic(t *Type, val interface{}) (ethgo.Hash, error) { 103 | return encodeTopic(t, reflect.ValueOf(val)) 104 | } 105 | 106 | func encodeTopic(t *Type, val reflect.Value) (ethgo.Hash, error) { 107 | switch t.kind { 108 | case KindBool: 109 | return encodeTopicBool(val) 110 | 111 | case KindUInt, KindInt: 112 | return encodeTopicNum(t, val) 113 | 114 | case KindAddress: 115 | return encodeTopicAddress(val) 116 | 117 | } 118 | return ethgo.Hash{}, fmt.Errorf("not found") 119 | } 120 | 121 | var topicTrue, topicFalse ethgo.Hash 122 | 123 | func init() { 124 | topicTrue[31] = 1 125 | } 126 | 127 | func encodeTopicAddress(val reflect.Value) (res ethgo.Hash, err error) { 128 | var b []byte 129 | b, err = encodeAddress(val) 130 | if err != nil { 131 | return 132 | } 133 | copy(res[:], b[:]) 134 | return 135 | } 136 | 137 | func encodeTopicNum(t *Type, val reflect.Value) (res ethgo.Hash, err error) { 138 | var b []byte 139 | b, err = encodeNum(val) 140 | if err != nil { 141 | return 142 | } 143 | copy(res[:], b[:]) 144 | return 145 | } 146 | 147 | func encodeTopicBool(v reflect.Value) (res ethgo.Hash, err error) { 148 | if v.Kind() != reflect.Bool { 149 | return ethgo.Hash{}, encodeErr(v, "bool") 150 | } 151 | if v.Bool() { 152 | return topicTrue, nil 153 | } 154 | return topicFalse, nil 155 | } 156 | -------------------------------------------------------------------------------- /builtin/ens/ens.go: -------------------------------------------------------------------------------- 1 | // Code generated by ethgo/abigen. DO NOT EDIT. 2 | // Hash: bfee2618a5908e1a24f19dcce873d3b8e797374138dd7604f7b593db3cca5c17 3 | // Version: 0.1.1 4 | package ens 5 | 6 | import ( 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/umbracle/ethgo" 11 | "github.com/umbracle/ethgo/contract" 12 | "github.com/umbracle/ethgo/jsonrpc" 13 | ) 14 | 15 | var ( 16 | _ = big.NewInt 17 | _ = jsonrpc.NewClient 18 | ) 19 | 20 | // ENS is a solidity contract 21 | type ENS struct { 22 | c *contract.Contract 23 | } 24 | 25 | // DeployENS deploys a new ENS contract 26 | func DeployENS(provider *jsonrpc.Client, from ethgo.Address, args []interface{}, opts ...contract.ContractOption) (contract.Txn, error) { 27 | return contract.DeployContract(abiENS, binENS, args, opts...) 28 | } 29 | 30 | // NewENS creates a new instance of the contract at a specific address 31 | func NewENS(addr ethgo.Address, opts ...contract.ContractOption) *ENS { 32 | return &ENS{c: contract.NewContract(addr, abiENS, opts...)} 33 | } 34 | 35 | // calls 36 | 37 | // Owner calls the owner method in the solidity contract 38 | func (e *ENS) Owner(node [32]byte, block ...ethgo.BlockNumber) (retval0 ethgo.Address, err error) { 39 | var out map[string]interface{} 40 | var ok bool 41 | 42 | out, err = e.c.Call("owner", ethgo.EncodeBlock(block...), node) 43 | if err != nil { 44 | return 45 | } 46 | 47 | // decode outputs 48 | retval0, ok = out["0"].(ethgo.Address) 49 | if !ok { 50 | err = fmt.Errorf("failed to encode output at index 0") 51 | return 52 | } 53 | 54 | return 55 | } 56 | 57 | // Resolver calls the resolver method in the solidity contract 58 | func (e *ENS) Resolver(node [32]byte, block ...ethgo.BlockNumber) (retval0 ethgo.Address, err error) { 59 | var out map[string]interface{} 60 | var ok bool 61 | 62 | out, err = e.c.Call("resolver", ethgo.EncodeBlock(block...), node) 63 | if err != nil { 64 | return 65 | } 66 | 67 | // decode outputs 68 | retval0, ok = out["0"].(ethgo.Address) 69 | if !ok { 70 | err = fmt.Errorf("failed to encode output at index 0") 71 | return 72 | } 73 | 74 | return 75 | } 76 | 77 | // Ttl calls the ttl method in the solidity contract 78 | func (e *ENS) Ttl(node [32]byte, block ...ethgo.BlockNumber) (retval0 uint64, err error) { 79 | var out map[string]interface{} 80 | var ok bool 81 | 82 | out, err = e.c.Call("ttl", ethgo.EncodeBlock(block...), node) 83 | if err != nil { 84 | return 85 | } 86 | 87 | // decode outputs 88 | retval0, ok = out["0"].(uint64) 89 | if !ok { 90 | err = fmt.Errorf("failed to encode output at index 0") 91 | return 92 | } 93 | 94 | return 95 | } 96 | 97 | // txns 98 | 99 | // SetOwner sends a setOwner transaction in the solidity contract 100 | func (e *ENS) SetOwner(node [32]byte, owner ethgo.Address) (contract.Txn, error) { 101 | return e.c.Txn("setOwner", node, owner) 102 | } 103 | 104 | // SetResolver sends a setResolver transaction in the solidity contract 105 | func (e *ENS) SetResolver(node [32]byte, resolver ethgo.Address) (contract.Txn, error) { 106 | return e.c.Txn("setResolver", node, resolver) 107 | } 108 | 109 | // SetSubnodeOwner sends a setSubnodeOwner transaction in the solidity contract 110 | func (e *ENS) SetSubnodeOwner(node [32]byte, label [32]byte, owner ethgo.Address) (contract.Txn, error) { 111 | return e.c.Txn("setSubnodeOwner", node, label, owner) 112 | } 113 | 114 | // SetTTL sends a setTTL transaction in the solidity contract 115 | func (e *ENS) SetTTL(node [32]byte, ttl uint64) (contract.Txn, error) { 116 | return e.c.Txn("setTTL", node, ttl) 117 | } 118 | 119 | // events 120 | 121 | func (e *ENS) NewOwnerEventSig() ethgo.Hash { 122 | return e.c.GetABI().Events["NewOwner"].ID() 123 | } 124 | 125 | func (e *ENS) NewResolverEventSig() ethgo.Hash { 126 | return e.c.GetABI().Events["NewResolver"].ID() 127 | } 128 | 129 | func (e *ENS) NewTTLEventSig() ethgo.Hash { 130 | return e.c.GetABI().Events["NewTTL"].ID() 131 | } 132 | 133 | func (e *ENS) TransferEventSig() ethgo.Hash { 134 | return e.c.GetABI().Events["Transfer"].ID() 135 | } 136 | -------------------------------------------------------------------------------- /compiler/solidity.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "strings" 14 | ) 15 | 16 | type Output struct { 17 | Contracts map[string]*Artifact 18 | Sources map[string]*Source 19 | Version string 20 | } 21 | 22 | type Source struct { 23 | AST map[string]interface{} 24 | } 25 | 26 | type Artifact struct { 27 | Abi string 28 | Bin string 29 | BinRuntime string `json:"bin-runtime"` 30 | SrcMap string `json:"srcmap"` 31 | SrcMapRuntime string `json:"srcmap-runtime"` 32 | } 33 | 34 | // Solidity is the solidity compiler 35 | type Solidity struct { 36 | path string 37 | } 38 | 39 | // NewSolidityCompiler instantiates a new solidity compiler 40 | func NewSolidityCompiler(path string) *Solidity { 41 | return &Solidity{path} 42 | } 43 | 44 | // CompileCode compiles a solidity code 45 | func (s *Solidity) CompileCode(code string) (*Output, error) { 46 | if code == "" { 47 | return nil, fmt.Errorf("code is empty") 48 | } 49 | output, err := s.compileImpl(code) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return output, nil 54 | } 55 | 56 | // Compile implements the compiler interface 57 | func (s *Solidity) Compile(files ...string) (*Output, error) { 58 | if len(files) == 0 { 59 | return nil, fmt.Errorf("no input files") 60 | } 61 | return s.compileImpl("", files...) 62 | } 63 | 64 | func (s *Solidity) compileImpl(code string, files ...string) (*Output, error) { 65 | args := []string{ 66 | "--combined-json", 67 | "bin,bin-runtime,srcmap-runtime,abi,srcmap,ast", 68 | } 69 | if code != "" { 70 | args = append(args, "-") 71 | } 72 | if len(files) != 0 { 73 | args = append(args, files...) 74 | } 75 | 76 | var stdout, stderr bytes.Buffer 77 | cmd := exec.Command(s.path, args...) 78 | if code != "" { 79 | cmd.Stdin = strings.NewReader(code) 80 | } 81 | 82 | cmd.Stdout = &stdout 83 | cmd.Stderr = &stderr 84 | 85 | if err := cmd.Run(); err != nil { 86 | return nil, fmt.Errorf("failed to compile: %s", string(stderr.Bytes())) 87 | } 88 | 89 | var output *Output 90 | if err := json.Unmarshal(stdout.Bytes(), &output); err != nil { 91 | return nil, err 92 | } 93 | return output, nil 94 | } 95 | 96 | // DownloadSolidity downloads the solidity compiler 97 | func DownloadSolidity(version string, dst string, renameDst bool) error { 98 | url := "https://github.com/ethereum/solidity/releases/download/v" + version + "/solc-static-linux" 99 | 100 | // check if the dst is correct 101 | exists := false 102 | fi, err := os.Stat(dst) 103 | if err == nil { 104 | switch mode := fi.Mode(); { 105 | case mode.IsDir(): 106 | exists = true 107 | case mode.IsRegular(): 108 | return fmt.Errorf("dst is a file") 109 | } 110 | } else { 111 | if !os.IsNotExist(err) { 112 | return fmt.Errorf("failed to stat dst '%s': %v", dst, err) 113 | } 114 | } 115 | 116 | // create the destiny path if does not exists 117 | if !exists { 118 | if err := os.MkdirAll(dst, 0755); err != nil { 119 | return fmt.Errorf("cannot create dst path: %v", err) 120 | } 121 | } 122 | 123 | // rename binary 124 | name := "solidity" 125 | if renameDst { 126 | name += "-" + version 127 | } 128 | 129 | // tmp folder to download the binary 130 | tmpDir, err := ioutil.TempDir("/tmp", "solc-") 131 | if err != nil { 132 | return err 133 | } 134 | defer os.RemoveAll(tmpDir) 135 | 136 | path := filepath.Join(tmpDir, name) 137 | 138 | // Get the data 139 | resp, err := http.Get(url) 140 | if err != nil { 141 | return err 142 | } 143 | defer resp.Body.Close() 144 | 145 | // Create the file 146 | out, err := os.Create(path) 147 | if err != nil { 148 | return err 149 | } 150 | defer out.Close() 151 | 152 | // Write the body to file 153 | _, err = io.Copy(out, resp.Body) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | // make binary executable 159 | if err := os.Chmod(path, 0755); err != nil { 160 | return err 161 | } 162 | 163 | // move file to dst 164 | if err := os.Rename(path, filepath.Join(dst, name)); err != nil { 165 | return err 166 | } 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /cmd/abigen/abigen.go: -------------------------------------------------------------------------------- 1 | package abigen 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strings" 11 | 12 | "path/filepath" 13 | 14 | "github.com/umbracle/ethgo/compiler" 15 | ) 16 | 17 | func Parse(sources string, pckg string, output string) error { 18 | config := &config{ 19 | Package: pckg, 20 | Output: output, 21 | } 22 | 23 | if sources == "" { 24 | return fmt.Errorf("no source") 25 | } 26 | 27 | for _, source := range strings.Split(sources, ",") { 28 | matches, err := filepath.Glob(source) 29 | if err != nil { 30 | fmt.Printf("Failed to read files: %v", err) 31 | os.Exit(1) 32 | } 33 | if len(matches) == 0 { 34 | fmt.Printf("No match for source: %s\n", source) 35 | continue 36 | } 37 | for _, source := range matches { 38 | artifacts, err := process(source, config) 39 | if err != nil { 40 | fmt.Printf("Failed to parse sources: %v", err) 41 | os.Exit(1) 42 | } 43 | 44 | // hash the source file 45 | raw := sha256.Sum256([]byte(source)) 46 | hash := hex.EncodeToString(raw[:]) 47 | 48 | if err := gen(artifacts, config, hash); err != nil { 49 | fmt.Printf("Failed to generate sources: %v", err) 50 | os.Exit(1) 51 | } 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | const ( 58 | solExt = 1 59 | abiExt = 2 60 | jsonExt = 3 61 | ) 62 | 63 | func process(sources string, config *config) (map[string]*compiler.Artifact, error) { 64 | files := strings.Split(sources, ",") 65 | if len(files) == 0 { 66 | return nil, fmt.Errorf("input not found") 67 | } 68 | 69 | prev := -1 70 | for _, f := range files { 71 | var ext int 72 | switch extt := filepath.Ext(f); extt { 73 | case ".abi": 74 | ext = abiExt 75 | case ".sol": 76 | ext = solExt 77 | case ".json": 78 | ext = jsonExt 79 | default: 80 | return nil, fmt.Errorf("file extension '%s' not found", extt) 81 | } 82 | 83 | if prev == -1 { 84 | prev = ext 85 | } else if ext != prev { 86 | return nil, fmt.Errorf("two file formats found") 87 | } 88 | } 89 | 90 | switch prev { 91 | case abiExt: 92 | return processAbi(files, config) 93 | case solExt: 94 | return processSolc(files) 95 | case jsonExt: 96 | return processJson(files) 97 | } 98 | 99 | return nil, nil 100 | } 101 | 102 | func processSolc(sources []string) (map[string]*compiler.Artifact, error) { 103 | c := compiler.NewSolidityCompiler("solc") 104 | raw, err := c.Compile(sources...) 105 | if err != nil { 106 | return nil, err 107 | } 108 | res := map[string]*compiler.Artifact{} 109 | for rawName, entry := range raw.Contracts { 110 | name := strings.Split(rawName, ":")[1] 111 | res[strings.Title(name)] = entry 112 | } 113 | return res, nil 114 | } 115 | 116 | func processAbi(sources []string, config *config) (map[string]*compiler.Artifact, error) { 117 | artifacts := map[string]*compiler.Artifact{} 118 | 119 | for _, abiPath := range sources { 120 | content, err := ioutil.ReadFile(abiPath) 121 | if err != nil { 122 | return nil, fmt.Errorf("failed to read abi file (%s): %v", abiPath, err) 123 | } 124 | 125 | // Use the name of the file to name the contract 126 | path, name := filepath.Split(abiPath) 127 | 128 | name = strings.TrimSuffix(name, filepath.Ext(name)) 129 | binPath := filepath.Join(path, name+".bin") 130 | 131 | bin, err := ioutil.ReadFile(binPath) 132 | if err != nil { 133 | // bin not found 134 | bin = []byte{} 135 | } 136 | artifacts[strings.Title(name)] = &compiler.Artifact{ 137 | Abi: string(content), 138 | Bin: string(bin), 139 | } 140 | } 141 | return artifacts, nil 142 | } 143 | 144 | type JSONArtifact struct { 145 | Bytecode string `json:"bytecode"` 146 | Abi json.RawMessage `json:"abi"` 147 | } 148 | 149 | func processJson(sources []string) (map[string]*compiler.Artifact, error) { 150 | artifacts := map[string]*compiler.Artifact{} 151 | 152 | for _, jsonPath := range sources { 153 | content, err := ioutil.ReadFile(jsonPath) 154 | if err != nil { 155 | return nil, fmt.Errorf("failed to read abi file (%s): %v", jsonPath, err) 156 | } 157 | 158 | // Use the name of the file to name the contract 159 | _, name := filepath.Split(jsonPath) 160 | name = strings.TrimSuffix(name, ".json") 161 | 162 | var art *JSONArtifact 163 | if err := json.Unmarshal(content, &art); err != nil { 164 | return nil, err 165 | } 166 | 167 | artifacts[strings.Title(name)] = &compiler.Artifact{ 168 | Abi: string(art.Abi), 169 | Bin: "0x" + art.Bytecode, 170 | } 171 | } 172 | return artifacts, nil 173 | } 174 | -------------------------------------------------------------------------------- /keystore/v4.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | 10 | "golang.org/x/text/unicode/norm" 11 | ) 12 | 13 | func EncryptV4(content []byte, password string) ([]byte, error) { 14 | password = normalizePassword(password) 15 | 16 | // decryption key 17 | scrypt := scryptParams{ 18 | N: 1 << 18, 19 | R: 8, 20 | P: 1, 21 | Dklen: 32, 22 | Salt: hexString(getRand(32)), 23 | } 24 | key, err := scrypt.Key([]byte(password)) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | // decrypt 30 | iv := getRand(16) 31 | cipherText, err := aesCTR(key[:16], content, iv) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | // checksum 37 | hash := sha256.New() 38 | hash.Write(key[16:32]) 39 | hash.Write(cipherText) 40 | 41 | checksum := hash.Sum(nil) 42 | 43 | kdfParams, err := json.Marshal(scrypt) 44 | if err != nil { 45 | return nil, err 46 | } 47 | cipherParams, err := json.Marshal(&cipherParams{Iv: hexString(iv)}) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | encoding := &v4Encoding{ 53 | Version: 4, 54 | Crypto: &v4crypto{ 55 | Kdf: &v4Module{ 56 | Function: "scrypt", 57 | Params: kdfParams, 58 | }, 59 | Cipher: &v4Module{ 60 | Function: "aes-128-ctr", 61 | Params: cipherParams, 62 | Message: hexString(cipherText), 63 | }, 64 | Checksum: &v4Module{ 65 | Function: "sha256", 66 | Message: hexString(checksum), 67 | }, 68 | }, 69 | } 70 | return encoding.Marshal() 71 | } 72 | 73 | type cipherParams struct { 74 | Iv hexString `json:"iv"` 75 | } 76 | 77 | func DecryptV4(content []byte, password string) ([]byte, error) { 78 | encoding := v4Encoding{} 79 | if err := encoding.Unmarshal(content); err != nil { 80 | return nil, err 81 | } 82 | if encoding.Version != 4 { 83 | return nil, fmt.Errorf("only version 4 supported") 84 | } 85 | 86 | password = normalizePassword(password) 87 | 88 | // decryption key 89 | key, err := applyKdf(encoding.Crypto.Kdf.Function, []byte(password), encoding.Crypto.Kdf.Params) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | // checksum 95 | hash := sha256.New() 96 | hash.Write(key[16:32]) 97 | hash.Write(encoding.Crypto.Cipher.Message) 98 | 99 | checksum := hash.Sum(nil) 100 | if !bytes.Equal(checksum, encoding.Crypto.Checksum.Message) { 101 | return nil, fmt.Errorf("bad checksum") 102 | } 103 | 104 | // decrypt 105 | var msg []byte 106 | if encoding.Crypto.Cipher.Function == "aes-128-ctr" { 107 | var params cipherParams 108 | if err := json.Unmarshal(encoding.Crypto.Cipher.Params, ¶ms); err != nil { 109 | return nil, err 110 | } 111 | res, err := aesCTR(key[:16], encoding.Crypto.Cipher.Message, params.Iv) 112 | if err != nil { 113 | return nil, err 114 | } 115 | msg = res 116 | } else { 117 | return nil, fmt.Errorf("cipher '%s' not supported", encoding.Crypto.Cipher.Function) 118 | } 119 | return msg, nil 120 | } 121 | 122 | type v4Encoding struct { 123 | Crypto *v4crypto `json:"crypto"` 124 | Description string `json:"description"` 125 | PubKey hexString `json:"pubkey"` 126 | Path string `json:"path"` 127 | Version int `json:"version"` 128 | Uuid string `json:"uuid"` 129 | } 130 | 131 | func (j *v4Encoding) Marshal() ([]byte, error) { 132 | return json.Marshal(j) 133 | } 134 | 135 | func (j *v4Encoding) Unmarshal(data []byte) error { 136 | return json.Unmarshal(data, j) 137 | } 138 | 139 | type v4crypto struct { 140 | Kdf *v4Module `json:"kdf"` 141 | Checksum *v4Module `json:"checksum"` 142 | Cipher *v4Module `json:"cipher"` 143 | } 144 | 145 | type v4Module struct { 146 | Function string `json:"function"` 147 | Params json.RawMessage `json:"params"` 148 | Message hexString `json:"message"` 149 | } 150 | 151 | // normalizePassword normalizes the password following the next rules 152 | // https://eips.ethereum.org/EIPS/eip-2335#password-requirements 153 | func normalizePassword(password string) string { 154 | str := norm.NFKD.String(password) 155 | 156 | skip := func(i byte) bool { 157 | // skip runes in the range 0x00 - 0x1F, 0x80 - 0x9F and 0x7F 158 | if i == 0x7F { 159 | return true 160 | } 161 | if 0x00 <= i && i <= 0x1F { 162 | return true 163 | } 164 | if 0x80 <= i && i <= 0x9F { 165 | return true 166 | } 167 | return false 168 | } 169 | 170 | normalized := strings.Builder{} 171 | for _, r := range str { 172 | elem := string(r) 173 | if len(elem) == 1 { 174 | if skip(elem[0]) { 175 | continue 176 | } 177 | } 178 | normalized.WriteRune(r) 179 | } 180 | return normalized.String() 181 | } 182 | -------------------------------------------------------------------------------- /compiler/fixtures/simple_auction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.22 <0.6.0; 2 | 3 | contract SimpleAuction { 4 | // Parameters of the auction. Times are either 5 | // absolute unix timestamps (seconds since 1970-01-01) 6 | // or time periods in seconds. 7 | address payable public beneficiary; 8 | uint public auctionEndTime; 9 | 10 | // Current state of the auction. 11 | address public highestBidder; 12 | uint public highestBid; 13 | 14 | // Allowed withdrawals of previous bids 15 | mapping(address => uint) pendingReturns; 16 | 17 | // Set to true at the end, disallows any change. 18 | // By default initialized to `false`. 19 | bool ended; 20 | 21 | // Events that will be emitted on changes. 22 | event HighestBidIncreased(address bidder, uint amount); 23 | event AuctionEnded(address winner, uint amount); 24 | 25 | // The following is a so-called natspec comment, 26 | // recognizable by the three slashes. 27 | // It will be shown when the user is asked to 28 | // confirm a transaction. 29 | 30 | /// Create a simple auction with `_biddingTime` 31 | /// seconds bidding time on behalf of the 32 | /// beneficiary address `_beneficiary`. 33 | constructor( 34 | uint _biddingTime, 35 | address payable _beneficiary 36 | ) public { 37 | beneficiary = _beneficiary; 38 | auctionEndTime = now + _biddingTime; 39 | } 40 | 41 | /// Bid on the auction with the value sent 42 | /// together with this transaction. 43 | /// The value will only be refunded if the 44 | /// auction is not won. 45 | function bid() public payable { 46 | // No arguments are necessary, all 47 | // information is already part of 48 | // the transaction. The keyword payable 49 | // is required for the function to 50 | // be able to receive Ether. 51 | 52 | // Revert the call if the bidding 53 | // period is over. 54 | require( 55 | now <= auctionEndTime, 56 | "Auction already ended." 57 | ); 58 | 59 | // If the bid is not higher, send the 60 | // money back. 61 | require( 62 | msg.value > highestBid, 63 | "There already is a higher bid." 64 | ); 65 | 66 | if (highestBid != 0) { 67 | // Sending back the money by simply using 68 | // highestBidder.send(highestBid) is a security risk 69 | // because it could execute an untrusted contract. 70 | // It is always safer to let the recipients 71 | // withdraw their money themselves. 72 | pendingReturns[highestBidder] += highestBid; 73 | } 74 | highestBidder = msg.sender; 75 | highestBid = msg.value; 76 | emit HighestBidIncreased(msg.sender, msg.value); 77 | } 78 | 79 | /// Withdraw a bid that was overbid. 80 | function withdraw() public returns (bool) { 81 | uint amount = pendingReturns[msg.sender]; 82 | if (amount > 0) { 83 | // It is important to set this to zero because the recipient 84 | // can call this function again as part of the receiving call 85 | // before `send` returns. 86 | pendingReturns[msg.sender] = 0; 87 | 88 | if (!msg.sender.send(amount)) { 89 | // No need to call throw here, just reset the amount owing 90 | pendingReturns[msg.sender] = amount; 91 | return false; 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | /// End the auction and send the highest bid 98 | /// to the beneficiary. 99 | function auctionEnd() public { 100 | // It is a good guideline to structure functions that interact 101 | // with other contracts (i.e. they call functions or send Ether) 102 | // into three phases: 103 | // 1. checking conditions 104 | // 2. performing actions (potentially changing conditions) 105 | // 3. interacting with other contracts 106 | // If these phases are mixed up, the other contract could call 107 | // back into the current contract and modify the state or cause 108 | // effects (ether payout) to be performed multiple times. 109 | // If functions called internally include interaction with external 110 | // contracts, they also have to be considered interaction with 111 | // external contracts. 112 | 113 | // 1. Conditions 114 | require(now >= auctionEndTime, "Auction not yet ended."); 115 | require(!ended, "auctionEnd has already been called."); 116 | 117 | // 2. Effects 118 | ended = true; 119 | emit AuctionEnded(highestBidder, highestBid); 120 | 121 | // 3. Interaction 122 | beneficiary.transfer(highestBid); 123 | } 124 | } -------------------------------------------------------------------------------- /builtin/erc20/erc20.go: -------------------------------------------------------------------------------- 1 | // Code generated by ethgo/abigen. DO NOT EDIT. 2 | // Hash: a1a873d70d345feef023ee086fd6135b24d775444b950ee9d5ea411e72b0f373 3 | // Version: 0.1.1 4 | package erc20 5 | 6 | import ( 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/umbracle/ethgo" 11 | "github.com/umbracle/ethgo/contract" 12 | "github.com/umbracle/ethgo/jsonrpc" 13 | ) 14 | 15 | var ( 16 | _ = big.NewInt 17 | _ = jsonrpc.NewClient 18 | ) 19 | 20 | // ERC20 is a solidity contract 21 | type ERC20 struct { 22 | c *contract.Contract 23 | } 24 | 25 | // NewERC20 creates a new instance of the contract at a specific address 26 | func NewERC20(addr ethgo.Address, opts ...contract.ContractOption) *ERC20 { 27 | return &ERC20{c: contract.NewContract(addr, abiERC20, opts...)} 28 | } 29 | 30 | // calls 31 | 32 | // Allowance calls the allowance method in the solidity contract 33 | func (e *ERC20) Allowance(owner ethgo.Address, spender ethgo.Address, block ...ethgo.BlockNumber) (retval0 *big.Int, err error) { 34 | var out map[string]interface{} 35 | var ok bool 36 | 37 | out, err = e.c.Call("allowance", ethgo.EncodeBlock(block...), owner, spender) 38 | if err != nil { 39 | return 40 | } 41 | 42 | // decode outputs 43 | retval0, ok = out["0"].(*big.Int) 44 | if !ok { 45 | err = fmt.Errorf("failed to encode output at index 0") 46 | return 47 | } 48 | 49 | return 50 | } 51 | 52 | // BalanceOf calls the balanceOf method in the solidity contract 53 | func (e *ERC20) BalanceOf(owner ethgo.Address, block ...ethgo.BlockNumber) (retval0 *big.Int, err error) { 54 | var out map[string]interface{} 55 | var ok bool 56 | 57 | out, err = e.c.Call("balanceOf", ethgo.EncodeBlock(block...), owner) 58 | if err != nil { 59 | return 60 | } 61 | 62 | // decode outputs 63 | retval0, ok = out["balance"].(*big.Int) 64 | if !ok { 65 | err = fmt.Errorf("failed to encode output at index 0") 66 | return 67 | } 68 | 69 | return 70 | } 71 | 72 | // Decimals calls the decimals method in the solidity contract 73 | func (e *ERC20) Decimals(block ...ethgo.BlockNumber) (retval0 uint8, err error) { 74 | var out map[string]interface{} 75 | var ok bool 76 | 77 | out, err = e.c.Call("decimals", ethgo.EncodeBlock(block...)) 78 | if err != nil { 79 | return 80 | } 81 | 82 | // decode outputs 83 | retval0, ok = out["0"].(uint8) 84 | if !ok { 85 | err = fmt.Errorf("failed to encode output at index 0") 86 | return 87 | } 88 | 89 | return 90 | } 91 | 92 | // Name calls the name method in the solidity contract 93 | func (e *ERC20) Name(block ...ethgo.BlockNumber) (retval0 string, err error) { 94 | var out map[string]interface{} 95 | var ok bool 96 | 97 | out, err = e.c.Call("name", ethgo.EncodeBlock(block...)) 98 | if err != nil { 99 | return 100 | } 101 | 102 | // decode outputs 103 | retval0, ok = out["0"].(string) 104 | if !ok { 105 | err = fmt.Errorf("failed to encode output at index 0") 106 | return 107 | } 108 | 109 | return 110 | } 111 | 112 | // Symbol calls the symbol method in the solidity contract 113 | func (e *ERC20) Symbol(block ...ethgo.BlockNumber) (retval0 string, err error) { 114 | var out map[string]interface{} 115 | var ok bool 116 | 117 | out, err = e.c.Call("symbol", ethgo.EncodeBlock(block...)) 118 | if err != nil { 119 | return 120 | } 121 | 122 | // decode outputs 123 | retval0, ok = out["0"].(string) 124 | if !ok { 125 | err = fmt.Errorf("failed to encode output at index 0") 126 | return 127 | } 128 | 129 | return 130 | } 131 | 132 | // TotalSupply calls the totalSupply method in the solidity contract 133 | func (e *ERC20) TotalSupply(block ...ethgo.BlockNumber) (retval0 *big.Int, err error) { 134 | var out map[string]interface{} 135 | var ok bool 136 | 137 | out, err = e.c.Call("totalSupply", ethgo.EncodeBlock(block...)) 138 | if err != nil { 139 | return 140 | } 141 | 142 | // decode outputs 143 | retval0, ok = out["0"].(*big.Int) 144 | if !ok { 145 | err = fmt.Errorf("failed to encode output at index 0") 146 | return 147 | } 148 | 149 | return 150 | } 151 | 152 | // txns 153 | 154 | // Approve sends a approve transaction in the solidity contract 155 | func (e *ERC20) Approve(spender ethgo.Address, value *big.Int) (contract.Txn, error) { 156 | return e.c.Txn("approve", spender, value) 157 | } 158 | 159 | // Transfer sends a transfer transaction in the solidity contract 160 | func (e *ERC20) Transfer(to ethgo.Address, value *big.Int) (contract.Txn, error) { 161 | return e.c.Txn("transfer", to, value) 162 | } 163 | 164 | // TransferFrom sends a transferFrom transaction in the solidity contract 165 | func (e *ERC20) TransferFrom(from ethgo.Address, to ethgo.Address, value *big.Int) (contract.Txn, error) { 166 | return e.c.Txn("transferFrom", from, to, value) 167 | } 168 | 169 | // events 170 | 171 | func (e *ERC20) ApprovalEventSig() ethgo.Hash { 172 | return e.c.GetABI().Events["Approval"].ID() 173 | } 174 | 175 | func (e *ERC20) TransferEventSig() ethgo.Hash { 176 | return e.c.GetABI().Events["Transfer"].ID() 177 | } 178 | -------------------------------------------------------------------------------- /builtin/ens/ens_artifacts.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/umbracle/ethgo/abi" 8 | ) 9 | 10 | var abiENS *abi.ABI 11 | 12 | // ENSAbi returns the abi of the ENS contract 13 | func ENSAbi() *abi.ABI { 14 | return abiENS 15 | } 16 | 17 | var binENS []byte 18 | 19 | // ENSBin returns the bin of the ENS contract 20 | func ENSBin() []byte { 21 | return binENS 22 | } 23 | 24 | func init() { 25 | var err error 26 | abiENS, err = abi.NewABI(abiENSStr) 27 | if err != nil { 28 | panic(fmt.Errorf("cannot parse ENS abi: %v", err)) 29 | } 30 | if len(binENSStr) != 0 { 31 | binENS, err = hex.DecodeString(binENSStr[2:]) 32 | if err != nil { 33 | panic(fmt.Errorf("cannot parse ENS bin: %v", err)) 34 | } 35 | } 36 | } 37 | 38 | var binENSStr = "0x6060604052341561000f57600080fd5b60008080526020527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb58054600160a060020a033316600160a060020a0319909116179055610503806100626000396000f3006060604052600436106100825763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630178b8bf811461008757806302571be3146100b957806306ab5923146100cf57806314ab9038146100f657806316a25cbd146101195780631896f70a1461014c5780635b0fc9c31461016e575b600080fd5b341561009257600080fd5b61009d600435610190565b604051600160a060020a03909116815260200160405180910390f35b34156100c457600080fd5b61009d6004356101ae565b34156100da57600080fd5b6100f4600435602435600160a060020a03604435166101c9565b005b341561010157600080fd5b6100f460043567ffffffffffffffff6024351661028b565b341561012457600080fd5b61012f600435610357565b60405167ffffffffffffffff909116815260200160405180910390f35b341561015757600080fd5b6100f4600435600160a060020a036024351661038e565b341561017957600080fd5b6100f4600435600160a060020a0360243516610434565b600090815260208190526040902060010154600160a060020a031690565b600090815260208190526040902054600160a060020a031690565b600083815260208190526040812054849033600160a060020a039081169116146101f257600080fd5b8484604051918252602082015260409081019051908190039020915083857fce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e8285604051600160a060020a03909116815260200160405180910390a3506000908152602081905260409020805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03929092169190911790555050565b600082815260208190526040902054829033600160a060020a039081169116146102b457600080fd5b827f1d4f9bbfc9cab89d66e1a1562f2233ccbf1308cb4f63de2ead5787adddb8fa688360405167ffffffffffffffff909116815260200160405180910390a250600091825260208290526040909120600101805467ffffffffffffffff90921674010000000000000000000000000000000000000000027fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff909216919091179055565b60009081526020819052604090206001015474010000000000000000000000000000000000000000900467ffffffffffffffff1690565b600082815260208190526040902054829033600160a060020a039081169116146103b757600080fd5b827f335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a083604051600160a060020a03909116815260200160405180910390a250600091825260208290526040909120600101805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03909216919091179055565b600082815260208190526040902054829033600160a060020a0390811691161461045d57600080fd5b827fd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d26683604051600160a060020a03909116815260200160405180910390a250600091825260208290526040909120805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039092169190911790555600a165627a7a72305820f4c798d4c84c9912f389f64631e85e8d16c3e6644f8c2e1579936015c7d5f6660029" 39 | 40 | var abiENSStr = `[{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}]` 41 | -------------------------------------------------------------------------------- /testcases/transaction_test.go: -------------------------------------------------------------------------------- 1 | package testcases 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | "github.com/umbracle/ethgo" 10 | "github.com/umbracle/ethgo/wallet" 11 | ) 12 | 13 | func getUint64FromBigInt(b *ethgo.ArgBig) (uint64, bool) { 14 | g := (*big.Int)(b) 15 | if !g.IsUint64() { 16 | return 0, false 17 | } 18 | return g.Uint64(), true 19 | } 20 | 21 | func TestTransactions(t *testing.T) { 22 | var transactions []struct { 23 | Name string `json:"name"` 24 | AccountAddress ethgo.Address `json:"accountAddress"` 25 | PrivateKey ethgo.ArgBytes `json:"privateKey"` 26 | SignedTransaction ethgo.ArgBytes `json:"signedTransactionChainId5"` 27 | 28 | Data *ethgo.ArgBytes `json:"data,omitempty"` 29 | Value *ethgo.ArgBig `json:"value,omitempty"` 30 | To *ethgo.Address `json:"to,omitempty"` 31 | GasLimit *ethgo.ArgBig `json:"gasLimit,omitempty"` 32 | Nonce *ethgo.ArgUint64 `json:"nonce,omitempty"` 33 | GasPrice *ethgo.ArgBig `json:"gasPrice,omitempty"` 34 | } 35 | ReadTestCase(t, "transactions", &transactions) 36 | 37 | for _, c := range transactions { 38 | key, err := wallet.NewWalletFromPrivKey(c.PrivateKey) 39 | assert.NoError(t, err) 40 | assert.Equal(t, key.Address(), c.AccountAddress) 41 | 42 | txn := ðgo.Transaction{ 43 | ChainID: big.NewInt(5), 44 | } 45 | if c.Data != nil { 46 | txn.Input = *c.Data 47 | } 48 | if c.Value != nil { 49 | txn.Value = (*big.Int)(c.Value) 50 | } 51 | if c.To != nil { 52 | txn.To = c.To 53 | } 54 | if c.GasLimit != nil { 55 | gasLimit, isUint64 := getUint64FromBigInt(c.GasLimit) 56 | if !isUint64 { 57 | return 58 | } 59 | txn.Gas = gasLimit 60 | } 61 | if c.Nonce != nil { 62 | txn.Nonce = c.Nonce.Uint64() 63 | } 64 | if c.GasPrice != nil { 65 | gasPrice, isUint64 := getUint64FromBigInt(c.GasPrice) 66 | if !isUint64 { 67 | return 68 | } 69 | txn.GasPrice = gasPrice 70 | } 71 | 72 | signer := wallet.NewEIP155Signer(5) 73 | signedTxn, err := signer.SignTx(txn, key) 74 | assert.NoError(t, err) 75 | 76 | txnRaw, err := signedTxn.MarshalRLPTo(nil) 77 | assert.NoError(t, err) 78 | assert.Equal(t, txnRaw, c.SignedTransaction.Bytes()) 79 | 80 | sender, err := signer.RecoverSender(signedTxn) 81 | require.NoError(t, err) 82 | require.Equal(t, sender, key.Address()) 83 | } 84 | } 85 | 86 | func TestTypedTransactions(t *testing.T) { 87 | var transactions []struct { 88 | Name string `json:"name"` 89 | AccountAddress ethgo.Address `json:"address"` 90 | Key ethgo.ArgBytes `json:"key"` 91 | Signed ethgo.ArgBytes `json:"signed"` 92 | 93 | Tx struct { 94 | Type ethgo.TransactionType 95 | Data *ethgo.ArgBytes `json:"data,omitempty"` 96 | GasLimit *ethgo.ArgBig `json:"gasLimit,omitempty"` 97 | MaxPriorityFeePerGas *ethgo.ArgBig `json:"maxPriorityFeePerGas,omitempty"` 98 | MaxFeePerGas *ethgo.ArgBig `json:"maxFeePerGas,omitempty"` 99 | Nonce uint64 `json:"nonce,omitempty"` 100 | To *ethgo.Address `json:"to,omitempty"` 101 | Value *ethgo.ArgBig `json:"value,omitempty"` 102 | GasPrice *ethgo.ArgBig `json:"gasPrice,omitempty"` 103 | ChainID uint64 `json:"chainId,omitempty"` 104 | AccessList ethgo.AccessList `json:"accessList,omitempty"` 105 | } 106 | } 107 | ReadTestCase(t, "typed-transactions", &transactions) 108 | 109 | for _, c := range transactions { 110 | key, err := wallet.NewWalletFromPrivKey(c.Key) 111 | assert.NoError(t, err) 112 | assert.Equal(t, key.Address(), c.AccountAddress) 113 | 114 | chainID := big.NewInt(int64(c.Tx.ChainID)) 115 | 116 | txn := ðgo.Transaction{ 117 | ChainID: chainID, 118 | Type: c.Tx.Type, 119 | MaxPriorityFeePerGas: (*big.Int)(c.Tx.MaxPriorityFeePerGas), 120 | MaxFeePerGas: (*big.Int)(c.Tx.MaxFeePerGas), 121 | AccessList: c.Tx.AccessList, 122 | } 123 | if c.Tx.Data != nil { 124 | txn.Input = *c.Tx.Data 125 | } 126 | if c.Tx.Value != nil { 127 | txn.Value = (*big.Int)(c.Tx.Value) 128 | } 129 | if c.Tx.To != nil { 130 | txn.To = c.Tx.To 131 | } 132 | if c.Tx.GasLimit != nil { 133 | gasLimit, isUint64 := getUint64FromBigInt(c.Tx.GasLimit) 134 | if !isUint64 { 135 | return 136 | } 137 | txn.Gas = gasLimit 138 | } 139 | txn.Nonce = c.Tx.Nonce 140 | if c.Tx.GasPrice != nil { 141 | gasPrice, isUint64 := getUint64FromBigInt(c.Tx.GasPrice) 142 | if !isUint64 { 143 | return 144 | } 145 | txn.GasPrice = gasPrice 146 | } 147 | 148 | signer := wallet.NewEIP155Signer(chainID.Uint64()) 149 | signedTxn, err := signer.SignTx(txn, key) 150 | assert.NoError(t, err) 151 | 152 | txnRaw, err := signedTxn.MarshalRLPTo(nil) 153 | assert.NoError(t, err) 154 | 155 | assert.Equal(t, txnRaw, c.Signed.Bytes()) 156 | 157 | sender, err := signer.RecoverSender(signedTxn) 158 | require.NoError(t, err) 159 | require.Equal(t, sender, key.Address()) 160 | } 161 | } 162 | --------------------------------------------------------------------------------