├── 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 |
--------------------------------------------------------------------------------