w3

package module
v0.17.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 14, 2025 License: MIT Imports: 16 Imported by: 29

README

w3: Enhanced Ethereum Integration for Go

Go Reference Go Report Card Coverage Status Latest Release W3 Gopher

w3 is your toolbelt for integrating with Ethereum in Go. Closely linked to go‑ethereum, it provides an ergonomic wrapper for working with RPC, ABI's, and the EVM.

go get github.com/lmittmann/w3

At a Glance

  • Use w3.Client to connect to an RPC endpoint. The client features batch request support for up to 80x faster requests and easy extendibility. learn more ↗
  • Use w3vm.VM to simulate EVM execution with optional tracing and Mainnet state forking, or test Smart Contracts. learn more ↗
  • Use w3.Func and w3.Event to create ABI bindings from Solidity function and event signatures. learn more ↗
  • Use w3.A, w3.H, and many other utility functions to parse addresses, hashes, and other common types from strings. learn more ↗

Getting Started

RPC Client

w3.Client is a batch request focused RPC client that can be used to connect to an Ethereum node via HTTP, WebSocket, or IPC. Its modular API allows to create custom RPC method integrations that can be used alongside the common methods implemented by this package.

Example: Batch Request (Playground)

// 1. Connect to an RPC endpoint
client, err := w3.Dial("https://rpc.ankr.com/eth")
if err != nil {
    // handle error
}
defer client.Close()

// 2. Make a batch request
var (
    balance *big.Int
    nonce   uint64
)
if err := client.Call(
    eth.Balance(addr, nil).Returns(&balance),
    eth.Nonce(addr, nil).Returns(&nonce),
); err != nil {
    // handle error
}

[!NOTE]

Why send batch requests?

Most of the time you need to call multiple RPC methods to get the data you need. When you make separate requests per RPC call you need a single round trip to the server for each call. This can be slow, especially for remote endpoints. Batching multiple RPC calls into a single request only requires a single round trip, and speeds up RPC calls significantly.

Error Handling

If one or more calls in a batch request fail, Client.Call returns an error of type w3.CallErrors.

Example: Check which RPC calls failed in a batch request (Playground)

var batchErr w3.CallErrors
if err := client.Call(calls...); errors.As(err, &batchErr) {
    // handle call errors
} else if err != nil {
    // handle other errors
}
Learn More
VM

w3vm.VM is a high-level EVM environment with a simple but powerful API to simulate EVM execution, test Smart Contracts, or trace transactions. It supports Mainnet state forking via RPC and state caching for faster testing.

Example: Simulate an Uniswap v3 swap (Playground)

// 1. Create a VM that forks the Mainnet state from the latest block,
// disables the base fee, and has a fake WETH balance and approval for the router
vm, err := w3vm.New(
    w3vm.WithFork(client, nil),
    w3vm.WithNoBaseFee(),
    w3vm.WithState(w3types.State{
        addrWETH: {Storage: w3types.Storage{
            w3vm.WETHBalanceSlot(addrEOA):               common.BigToHash(w3.I("1 ether")),
            w3vm.WETHAllowanceSlot(addrEOA, addrRouter): common.BigToHash(w3.I("1 ether")),
        }},
    }),
)
if err != nil {
    // handle error
}

// 2. Simulate a Uniswap v3 swap
receipt, err := vm.Apply(&w3types.Message{
    From: addrEOA,
    To:   &addrRouter,
    Func: funcExactInput,
    Args: []any{&ExactInputParams{
        Path:             encodePath(addrWETH, 500, addrUNI),
        Recipient:        addrEOA,
        Deadline:         big.NewInt(time.Now().Unix()),
        AmountIn:         w3.I("1 ether"),
        AmountOutMinimum: w3.Big0,
    }},
})
if err != nil {
    // handle error
}

// 3. Decode output amount
var amountOut *big.Int
if err := receipt.DecodeReturns(&amountOut); err != nil {
    // handle error
}
ABI Bindings

ABI bindings in w3 are specified for individual functions using Solidity syntax and are usable for any contract that supports that function.

Example: ABI binding for the ERC20 balanceOf function (Playground)

funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")

Example: ABI binding for the Uniswap v4 swap function (Playground)

funcSwap := w3.MustNewFunc(`swap(
    (address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key,
    (bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) params,
    bytes hookData
)`, "int256 delta")

A Func can be used to

Utils

Static addresses, hashes, bytes or integers can be parsed from (hex-)strings with the following utility functions that panic if the string is not valid.

addr := w3.A("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
hash := w3.H("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
bytes := w3.B("0x27c5342c")
amount := w3.I("12.34 ether")

Use go-ethereum/common to parse strings that may not be valid instead.

RPC Methods

List of supported RPC methods for w3.Client.

eth
Method Go Code
eth_blockNumber eth.BlockNumber().Returns(blockNumber **big.Int)
eth_call eth.Call(msg *w3types.Message, blockNumber *big.Int, overrides w3types.State).Returns(output *[]byte)
eth.CallFunc(contract common.Address, f w3types.Func, args ...any).Returns(returns ...any)
eth_chainId eth.ChainID().Returns(chainID *uint64)
eth_createAccessList eth.AccessList(msg *w3types.Message, blockNumber *big.Int).Returns(resp **eth.AccessListResponse)
eth_estimateGas eth.EstimateGas(msg *w3types.Message, blockNumber *big.Int).Returns(gas *uint64)
eth_gasPrice eth.GasPrice().Returns(gasPrice **big.Int)
eth_maxPriorityFeePerGas eth.GasTipCap().Returns(gasTipCap **big.Int)
eth_getBalance eth.Balance(addr common.Address, blockNumber *big.Int).Returns(balance **big.Int)
eth_getBlockByHash eth.BlockByHash(hash common.Hash).Returns(block *types.Block)
eth.HeaderByHash(hash common.Hash).Returns(header **types.Header)
eth_getBlockByNumber eth.BlockByNumber(number *big.Int).Returns(block *types.Block)
eth.HeaderByNumber(number *big.Int).Returns(header **types.Header)
eth_getBlockReceipts eth.BlockReceipts(blockNumber *big.Int).Returns(receipts *types.Receipts)
eth_getBlockTransactionCountByHash eth.BlockTxCountByHash(hash common.Hash).Returns(count *uint)
eth_getBlockTransactionCountByNumber eth.BlockTxCountByNumber(number *big.Int).Returns(count *uint)
eth_getCode eth.Code(addr common.Address, blockNumber *big.Int).Returns(code *[]byte)
eth_getLogs eth.Logs(q ethereum.FilterQuery).Returns(logs *[]types.Log)
eth_getStorageAt eth.StorageAt(addr common.Address, slot common.Hash, blockNumber *big.Int).Returns(storage *common.Hash)
eth_getTransactionByHash eth.Tx(hash common.Hash).Returns(tx **types.Transaction)
eth_getTransactionByBlockHashAndIndex eth.TxByBlockHashAndIndex(blockHash common.Hash, index uint).Returns(tx **types.Transaction)
eth_getTransactionByBlockNumberAndIndex eth.TxByBlockNumberAndIndex(blockNumber *big.Int, index uint).Returns(tx **types.Transaction)
eth_getTransactionCount eth.Nonce(addr common.Address, blockNumber *big.Int).Returns(nonce *uint)
eth_getTransactionReceipt eth.TxReceipt(txHash common.Hash).Returns(receipt **types.Receipt)
eth_sendRawTransaction eth.SendRawTx(rawTx []byte).Returns(hash *common.Hash)
eth.SendTx(tx *types.Transaction).Returns(hash *common.Hash)
eth_getUncleByBlockHashAndIndex eth.UncleByBlockHashAndIndex(hash common.Hash, index uint).Returns(uncle **types.Header)
eth_getUncleByBlockNumberAndIndex eth.UncleByBlockNumberAndIndex(number *big.Int, index uint).Returns(uncle **types.Header)
eth_getUncleCountByBlockHash eth.UncleCountByBlockHash(hash common.Hash).Returns(count *uint)
eth_getUncleCountByBlockNumber eth.UncleCountByBlockNumber(number *big.Int).Returns(count *uint)
debug
Method Go Code
debug_traceCall debug.TraceCall(msg *w3types.Message, blockNumber *big.Int, config *debug.TraceConfig).Returns(trace **debug.Trace)
debug.CallTraceCall(msg *w3types.Message, blockNumber *big.Int, overrides w3types.State).Returns(trace **debug.CallTrace)
debug_traceTransaction debug.TraceTx(txHash common.Hash, config *debug.TraceConfig).Returns(trace **debug.Trace)
debug.CallTraceTx(txHash common.Hash, overrides w3types.State).Returns(trace **debug.CallTrace)
txpool
Method Go Code
txpool_content txpool.Content().Returns(resp **txpool.ContentResponse)
txpool_contentFrom txpool.ContentFrom(addr common.Address).Returns(resp **txpool.ContentFromResponse)
txpool_status txpool.Status().Returns(resp **txpool.StatusResponse)
web3
Method Go Code
web3_clientVersion web3.ClientVersion().Returns(clientVersion *string)
Third Party RPC Method Packages
Package Description
github.com/lmittmann/flashbots Package flashbots implements RPC API bindings for the Flashbots relay and mev-geth.

Custom RPC Method Bindings

Custom RPC method bindings can be created by implementing the w3types.RPCCaller interface.

Example: RPC binding for the Otterscan ots_getTransactionBySenderAndNonce method (Playground)

// TxBySenderAndNonceFactory requests the senders transaction hash by the nonce.
func TxBySenderAndNonceFactory(sender common.Address, nonce uint64) w3types.RPCCallerFactory[common.Hash] {
    return &getTransactionBySenderAndNonceFactory{
        sender: sender,
        nonce:  nonce,
    }
}

// getTransactionBySenderAndNonceFactory implements the w3types.RPCCaller and
// w3types.RPCCallerFactory interfaces. It stores the method parameters and
// the reference to the return value.
type getTransactionBySenderAndNonceFactory struct {
    // params
    sender common.Address
    nonce  uint64

    // returns
    returns *common.Hash
}

// Returns sets the reference to the return value.
func (f *getTransactionBySenderAndNonceFactory) Returns(txHash *common.Hash) w3types.RPCCaller {
    f.returns = txHash
    return f
}

// CreateRequest creates a batch request element for the Otterscan getTransactionBySenderAndNonce method.
func (f *getTransactionBySenderAndNonceFactory) CreateRequest() (rpc.BatchElem, error) {
    return rpc.BatchElem{
        Method: "ots_getTransactionBySenderAndNonce",
        Args:   []any{f.sender, f.nonce},
        Result: f.returns,
    }, nil
}

// HandleResponse handles the response of the Otterscan getTransactionBySenderAndNonce method.
func (f *getTransactionBySenderAndNonceFactory) HandleResponse(elem rpc.BatchElem) error {
    if err := elem.Error; err != nil {
        return err
    }
    return nil
}

Sponsors

ef logo

Documentation

Overview

Package w3 is your toolbelt for integrating with Ethereum in Go. Closely linked to go-ethereum, it provides an ergonomic wrapper for working with RPC, ABI's, and the EVM.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidABI       = errors.New("w3: invalid ABI")
	ErrArgumentMismatch = errors.New("w3: argument mismatch")
	ErrReturnsMismatch  = errors.New("w3: returns mismatch")
	ErrInvalidType      = errors.New("w3: invalid type")
	ErrEvmRevert        = errors.New("w3: evm reverted")
)
View Source
var (
	Big0     = new(big.Int)
	Big1     = big.NewInt(1)
	Big2     = big.NewInt(2)
	Big10    = big.NewInt(10)
	BigGwei  = big.NewInt(1_000000000)
	BigEther = big.NewInt(1_000000000_000000000)

	// Max Uint Values.
	BigMaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 256), Big1)
	BigMaxUint248 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 248), Big1)
	BigMaxUint240 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 240), Big1)
	BigMaxUint232 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 232), Big1)
	BigMaxUint224 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 224), Big1)
	BigMaxUint216 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 216), Big1)
	BigMaxUint208 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 208), Big1)
	BigMaxUint200 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 200), Big1)
	BigMaxUint192 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 192), Big1)
	BigMaxUint184 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 184), Big1)
	BigMaxUint176 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 176), Big1)
	BigMaxUint168 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 168), Big1)
	BigMaxUint160 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 160), Big1)
	BigMaxUint152 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 152), Big1)
	BigMaxUint144 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 144), Big1)
	BigMaxUint136 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 136), Big1)
	BigMaxUint128 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 128), Big1)
	BigMaxUint120 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 120), Big1)
	BigMaxUint112 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 112), Big1)
	BigMaxUint104 = new(big.Int).Sub(new(big.Int).Lsh(Big1, 104), Big1)
	BigMaxUint96  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 96), Big1)
	BigMaxUint88  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 88), Big1)
	BigMaxUint80  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 80), Big1)
	BigMaxUint72  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 72), Big1)
	BigMaxUint64  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 64), Big1)
	BigMaxUint56  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 56), Big1)
	BigMaxUint48  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 48), Big1)
	BigMaxUint40  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 40), Big1)
	BigMaxUint32  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 32), Big1)
	BigMaxUint24  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 24), Big1)
	BigMaxUint16  = new(big.Int).Sub(new(big.Int).Lsh(Big1, 16), Big1)
	BigMaxUint8   = new(big.Int).Sub(new(big.Int).Lsh(Big1, 8), Big1)
)

Common big.Int's.

View Source
var (
	Addr0 common.Address
	Hash0 common.Hash
)

Zero Values.

Functions

func A

func A(hexAddr string) (addr common.Address)

A returns an address from a hexstring or panics if the hexstring does not represent a valid address.

Use common.HexToAddress to get the address from a hexstring without panicking.

func APtr

func APtr(hexAddress string) *common.Address

APtr returns an address pointer from a hexstring or panics if the hexstring does not represent a valid address.

func B

func B(hexBytes ...string) (bytes []byte)

B returns a byte slice from a hexstring or panics if the hexstring does not represent a valid byte slice.

Use common.FromHex to get the byte slice from a hexstring without panicking.

func BigMax added in v0.17.3

func BigMax(a, b *big.Int) *big.Int

BigMax returns the larger of the two big integers.

func BigMin added in v0.17.3

func BigMin(a, b *big.Int) *big.Int

BigMin returns the smaller of the two big integers.

func FromWei

func FromWei(wei *big.Int, decimals uint8) string

FromWei returns the given Wei as decimal with the given number of decimals.

Example
package main

import (
	"fmt"
	"math/big"

	"github.com/lmittmann/w3"
)

func main() {
	wei := big.NewInt(1_230000000_000000000)
	fmt.Printf("%s Ether\n", w3.FromWei(wei, 18))
}
Output:

1.23 Ether

func H

func H(hexHash string) (hash common.Hash)

H returns a hash from a hexstring or panics if the hexstring does not represent a valid hash.

Use common.HexToHash to get the hash from a hexstring without panicking.

func I

func I(strInt string) *big.Int

I returns a big.Int from a hexstring or decimal number string (with optional unit) or panics if the parsing fails.

I supports the units "ether" or "eth" and "gwei" for decimal number strings. E.g.:

w3.I("1 ether")   -> 1000000000000000000
w3.I("10 gwei")   -> 10000000000

Fractional digits that exceed the units maximum number of fractional digits are ignored. E.g.:

w3.I("0.000000123456 gwei") -> 123
Example
package main

import (
	"fmt"

	"github.com/lmittmann/w3"
)

func main() {
	fmt.Printf("%v wei\n", w3.I("0x1111d67bb1bb0000"))
	fmt.Printf("%v wei\n", w3.I("1230000000000000000"))
	fmt.Printf("%v wei\n", w3.I("1.23 ether"))
	fmt.Printf("%v wei\n", w3.I("1.23 gwei"))
}
Output:

1230000000000000000 wei
1230000000000000000 wei
1230000000000000000 wei
1230000000 wei

Types

type CallErrors added in v0.10.0

type CallErrors []error

CallErrors is an error type that contains the errors of multiple calls. The length of the error slice is equal to the number of calls. Each error at a given index corresponds to the call at the same index. An error is nil if the corresponding call was successful.

func (CallErrors) Error added in v0.10.0

func (e CallErrors) Error() string

func (CallErrors) Is added in v0.10.0

func (e CallErrors) Is(target error) bool

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents a connection to an RPC endpoint.

Example (BatchBlocks)

Fetch 1000 blocks in batches.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
	"github.com/lmittmann/w3/w3types"
)

var client = w3.MustDial("https://rpc.ankr.com/eth")

func main() {
	const (
		startBlock = 20_000_000
		nBlocks    = 1000
		batchSize  = 100
	)

	blocks := make([]*types.Block, nBlocks)
	calls := make([]w3types.RPCCaller, batchSize)
	for i := 0; i < nBlocks; i += batchSize {
		for j := 0; j < batchSize; j++ {
			blockNumber := new(big.Int).SetUint64(uint64(startBlock + i + j))
			calls[j] = eth.BlockByNumber(blockNumber).Returns(&blocks[i+j])
		}
		if err := client.Call(calls...); err != nil {
			// ...
		}
		fmt.Printf("Fetched %d blocks\n", i+batchSize)
	}
}
Output:

Example (BatchCallFunc)

Call the name, symbol, decimals, and balanceOf functions of the Wrapped Ether in a single batch.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var (
	funcName      = w3.MustNewFunc("name()", "string")
	funcSymbol    = w3.MustNewFunc("symbol()", "string")
	funcDecimals  = w3.MustNewFunc("decimals()", "uint8")
	funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")

	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	blockNumber := big.NewInt(20_000_000)

	var (
		name, symbol string
		decimals     uint8
		balance      big.Int
	)
	if err := client.Call(
		eth.CallFunc(addrWETH, funcName).Returns(&name),
		eth.CallFunc(addrWETH, funcSymbol).Returns(&symbol),
		eth.CallFunc(addrWETH, funcDecimals).Returns(&decimals),
		eth.CallFunc(addrWETH, funcBalanceOf, addrWETH).AtBlock(blockNumber).Returns(&balance),
	); err != nil {
		// ...
	}

	fmt.Printf("%s's own balance: %s %s\n", name, w3.FromWei(&balance, decimals), symbol)
}
Output:

Wrapped Ether's own balance: 748.980125465356473638 WETH
Example (BatchCallFuncUniswapQuoter)

Call the Uniswap V3 Quoter for quotes on swapping 100 WETH for DAI in pools of all fee tiers in a single batch.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var (
	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
	addrDAI  = w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F")

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	blockNumber := big.NewInt(20_000_000)

	var (
		addrUniswapV3Quoter = w3.A("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6")
		addrTokenIn         = addrWETH
		addrTokenOut        = addrDAI

		funcQuote = w3.MustNewFunc(`quoteExactInputSingle(
			address tokenIn,
			address tokenOut,
			uint24 fee,
			uint256 amountIn,
			uint160 sqrtPriceLimitX96clear
		)`, "uint256 amountOut")
	)

	var (
		amountIn       = w3.I("100 ether")
		amountOut100   *big.Int
		amountOut500   *big.Int
		amountOut3000  *big.Int
		amountOut10000 *big.Int
	)
	if err := client.Call(
		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(100), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut100),
		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(500), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut500),
		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(3000), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut3000),
		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(10000), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut10000),
	); err != nil {
		// ...
	}
	fmt.Println("Swap 100 WETH for DAI:")
	fmt.Printf("Pool with 0.01%% fee: %s DAI\n", w3.FromWei(amountOut100, 18))
	fmt.Printf("Pool with 0.05%% fee: %s DAI\n", w3.FromWei(amountOut500, 18))
	fmt.Printf("Pool with  0.3%% fee: %s DAI\n", w3.FromWei(amountOut3000, 18))
	fmt.Printf("Pool with    1%% fee: %s DAI\n", w3.FromWei(amountOut10000, 18))
}
Output:

Swap 100 WETH for DAI:
Pool with 0.01% fee: 0.840975419471618588 DAI
Pool with 0.05% fee: 371877.453117609415215338 DAI
Pool with  0.3% fee: 378532.856217317782434539 DAI
Pool with    1% fee: 3447.634026125332130689 DAI
Example (BatchEOAState)

Fetch the nonce and balance of an EOA in a single batch.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var (
	addrA = common.Address{0x0a}

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	var (
		nonce   uint64
		balance *big.Int
	)
	if err := client.Call(
		eth.Nonce(addrA, nil).Returns(&nonce),
		eth.Balance(addrA, nil).Returns(&balance),
	); err != nil {
		// ...
	}

	fmt.Printf("Nonce: %d\nBalance: %d\n", nonce, balance)
}
Output:

Example (BatchHandleError)

Handle errors of individual calls in a batch.

package main

import (
	"crypto/ecdsa"
	"errors"
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
	"github.com/lmittmann/w3/w3types"
)

var (
	funcSymbol = w3.MustNewFunc("symbol()", "string")

	addrA = common.Address{0x0a}
	addrB = common.Address{0x0b}

	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	tokens := []common.Address{addrWETH, addrA, addrB}
	symbols := make([]string, len(tokens))

	// build rpc calls
	calls := make([]w3types.RPCCaller, len(tokens))
	for i, token := range tokens {
		calls[i] = eth.CallFunc(token, funcSymbol).Returns(&symbols[i])
	}

	var batchErr w3.CallErrors
	if err := client.Call(calls...); errors.As(err, &batchErr) {
	} else if err != nil {
		// all calls failed
	}

	for i, symbol := range symbols {
		if len(batchErr) > 0 && batchErr[i] != nil {
			symbol = "call failed"
		}
		fmt.Printf("%s: %s\n", tokens[i], symbol)
	}
}
Output:

0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2: WETH
0x0a00000000000000000000000000000000000000: call failed
0x0B00000000000000000000000000000000000000: call failed
Example (BatchTxDetails)

Fetch a transaction and its receipt in a single batch.

package main

import (
	"crypto/ecdsa"
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var client = w3.MustDial("https://rpc.ankr.com/eth")

func main() {
	txHash := w3.H("0xc31d7e7e85cab1d38ce1b8ac17e821ccd47dbde00f9d57f2bd8613bff9428396")

	var (
		tx      *types.Transaction
		receipt *types.Receipt
	)
	if err := client.Call(
		eth.Tx(txHash).Returns(&tx),
		eth.TxReceipt(txHash).Returns(&receipt),
	); err != nil {
		// ...
	}

	fmt.Printf("Tx: %#v\nReceipt: %#v\n", tx, receipt)
}
Output:

Example (CallFunc)

Fetch the token balance of an address.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var (
	funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")

	addrA = common.Address{0x0a}

	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	var balance *big.Int
	if err := client.Call(
		eth.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balance),
	); err != nil {
		// ...
	}

	fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
}
Output:

Balance: 0 WETH
Example (CallFuncWithStateOverride)

Fetch the token balance of an address, with state override.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
	"github.com/lmittmann/w3/w3types"
)

var (
	funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")

	addrA = common.Address{0x0a}

	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	var balance *big.Int
	if err := client.Call(
		eth.CallFunc(addrWETH, funcBalanceOf, addrA).Overrides(w3types.State{
			addrWETH: {Storage: w3types.Storage{
				w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
			}},
		}).Returns(&balance),
	); err != nil {
		// ...
	}

	fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
}
Output:

Balance: 100 WETH
Example (RateLimitByComputeUnits)

Rate Limit the number of requests to 300 compute units (CUs) per second, with bursts of up to 300 CUs. An individual CU can be charged per RPC method call.

package main

import (
	"fmt"
	"time"

	"github.com/lmittmann/w3"
	"golang.org/x/time/rate"
)

func main() {
	// cu returns the CU cost for all method calls in a batch.
	cu := func(methods []string) (cost int) {
		for _, method := range methods {
			switch method {
			case "eth_blockNumber":
				cost += 5
			case "eth_getBalance",
				"eth_getBlockByNumber",
				"eth_getCode",
				"eth_getStorageAt",
				"eth_getTransactionByHash",
				"eth_getTransactionReceipt":
				cost += 15
			case "eth_call":
				cost += 20
			case "eth_getTransactionCount":
				cost += 25
			default:
				panic(fmt.Sprintf("unknown costs for %q", method))
			}
		}
		return cost
	}

	client, err := w3.Dial("https://rpc.ankr.com/eth",
		w3.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second/300), 300), cu),
	)
	if err != nil {
		// ...
	}
	defer client.Close()
}
Output:

Example (RateLimitByRequest)

Rate Limit the number of requests to 10 per second, with bursts of up to 20 requests.

package main

import (
	"time"

	"github.com/lmittmann/w3"
	"golang.org/x/time/rate"
)

func main() {
	client, err := w3.Dial("https://rpc.ankr.com/eth",
		w3.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second/10), 20), nil),
	)
	if err != nil {
		// ...
	}
	defer client.Close()
}
Output:

Example (SendETHTransfer)

Send Ether transfer.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/params"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var (
	addrA = common.Address{0x0a}
	addrB = common.Address{0x0b}

	prvA *ecdsa.PrivateKey

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	var (
		nonce    uint64
		gasPrice *big.Int
	)
	if err := client.Call(
		eth.Nonce(addrA, nil).Returns(&nonce),
		eth.GasPrice().Returns(&gasPrice),
	); err != nil {
		// ...
	}

	signer := types.LatestSigner(params.MainnetChainConfig)
	tx := types.MustSignNewTx(prvA, signer, &types.LegacyTx{
		Nonce:    nonce,
		Gas:      21_000,
		GasPrice: gasPrice,
		To:       &addrB,
		Value:    w3.I("1 ether"),
	})

	var txHash common.Hash
	if err := client.Call(eth.SendTx(tx).Returns(&txHash)); err != nil {
		// ...
	}

	fmt.Printf("Sent tx: %s\n", txHash)
}
Output:

Example (SendTokenTransfer)

Send ERC20 token transfer (Wrapped Ether).

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/params"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

var (
	addrA = common.Address{0x0a}
	addrB = common.Address{0x0b}

	prvA *ecdsa.PrivateKey

	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

	client = w3.MustDial("https://rpc.ankr.com/eth")
)

func main() {
	var (
		nonce    uint64
		gasPrice *big.Int
	)
	if err := client.Call(
		eth.Nonce(addrA, nil).Returns(&nonce),
		eth.GasPrice().Returns(&gasPrice),
	); err != nil {
		// ...
	}

	funcTransfer := w3.MustNewFunc("transfer(address receiver, uint256 amount)", "bool")
	data, err := funcTransfer.EncodeArgs(addrB, w3.I("1 ether"))
	if err != nil {
		// ...
	}

	signer := types.LatestSigner(params.MainnetChainConfig)
	tx := types.MustSignNewTx(prvA, signer, &types.LegacyTx{
		Nonce:    nonce,
		Gas:      100_000,
		GasPrice: gasPrice,
		To:       &addrWETH,
		Data:     data,
	})

	var txHash common.Hash
	if err := client.Call(eth.SendTx(tx).Returns(&txHash)); err != nil {
		// ...
	}

	fmt.Printf("Sent tx: %s\n", txHash)
}
Output:

Example (SubscribeToPendingTransactions)

Subscribe to pending transactions.

package main

import (
	"fmt"

	"github.com/ethereum/go-ethereum/core/types"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/module/eth"
)

func main() {
	client, err := w3.Dial("wss://mainnet.gateway.tenderly.co")
	if err != nil {
		// ...
	}
	defer client.Close()

	pendingTxCh := make(chan *types.Transaction)
	sub, err := client.Subscribe(eth.PendingTransactions(pendingTxCh))
	if err != nil {
		// ...
	}

	for {
		select {
		case tx := <-pendingTxCh:
			fmt.Printf("New pending tx: %s\n", tx.Hash())
		case err := <-sub.Err():
			fmt.Printf("Subscription error: %v\n", err)
			return
		}
	}
}
Output:

func Dial

func Dial(rawurl string, opts ...Option) (*Client, error)

Dial returns a new Client connected to the URL rawurl. An error is returned if the connection establishment fails.

The supported URL schemes are "http", "https", "ws" and "wss". If rawurl is a file name with no URL scheme, a local IPC socket connection is established.

func MustDial

func MustDial(rawurl string, opts ...Option) *Client

MustDial is like Dial but panics if the connection establishment fails.

func NewClient

func NewClient(client *rpc.Client, opts ...Option) *Client

NewClient returns a new Client given an rpc.Client client.

func (*Client) Call

func (c *Client) Call(calls ...w3types.RPCCaller) error

Call is like Client.CallCtx with ctx equal to context.Background().

func (*Client) CallCtx

func (c *Client) CallCtx(ctx context.Context, calls ...w3types.RPCCaller) error

CallCtx creates the final RPC request, sends it, and handles the RPC response.

An error is returned if RPC request creation, networking, or RPC response handling fails.

func (*Client) Close

func (c *Client) Close() error

Close the RPC connection and cancel all in-flight requests.

Close implements the io.Closer interface.

func (*Client) Subscribe added in v0.16.4

Subscribe is like Client.SubscribeCtx with ctx equal to context.Background().

func (*Client) SubscribeCtx added in v0.16.4

func (c *Client) SubscribeCtx(ctx context.Context, s w3types.RPCSubscriber) (*rpc.ClientSubscription, error)

SubscribeCtx creates a new subscription and returns a rpc.ClientSubscription.

type Event added in v0.3.0

type Event struct {
	Signature string        // Event signature
	Topic0    common.Hash   // Hash of event signature (Topic 0)
	Args      abi.Arguments // Arguments
	// contains filtered or unexported fields
}

Event represents a Smart Contract event decoder.

Example (DecodeTransferEvent)
package main

import (
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/lmittmann/w3"
)

func main() {
	var (
		eventTransfer = w3.MustNewEvent("Transfer(address indexed from, address indexed to, uint256 value)")
		log           = &types.Log{
			Address: w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
			Topics: []common.Hash{
				w3.H("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
				w3.H("0x000000000000000000000000000000000000000000000000000000000000c0fe"),
				w3.H("0x000000000000000000000000000000000000000000000000000000000000dead"),
			},
			Data: w3.B("0x0000000000000000000000000000000000000000000000001111d67bb1bb0000"),
		}

		from  common.Address
		to    common.Address
		value big.Int
	)

	if err := eventTransfer.DecodeArgs(log, &from, &to, &value); err != nil {
		fmt.Printf("Failed to decode event log: %v\n", err)
		return
	}
	fmt.Printf("Transferred %s WETH9 from %s to %s", w3.FromWei(&value, 18), from, to)
}
Output:

Transferred 1.23 WETH9 from 0x000000000000000000000000000000000000c0Fe to 0x000000000000000000000000000000000000dEaD

func MustNewEvent added in v0.3.0

func MustNewEvent(signature string) *Event

MustNewEvent is like NewEvent but panics if the signature parsing fails.

func NewEvent added in v0.3.0

func NewEvent(signature string) (*Event, error)

NewEvent returns a new Smart Contract event log decoder from the given Solidity event signature.

An error is returned if the signature parsing fails.

func (*Event) DecodeArgs added in v0.3.0

func (e *Event) DecodeArgs(log *types.Log, args ...any) error

DecodeArgs decodes the topics and data of the given log to the given args.

type Func added in v0.1.1

type Func struct {
	Signature string        // Function signature
	Selector  [4]byte       // 4-byte selector
	Args      abi.Arguments // Arguments (input)
	Returns   abi.Arguments // Returns (output)
	// contains filtered or unexported fields
}

Func represents a Smart Contract function ABI binding.

Func implements the w3types.Func interface.

Example (BalanceOf)

Encode and decode the arguments of the balanceOf function.

package main

import (
	"crypto/ecdsa"
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
)

var (
	funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")

	addrA = common.Address{0x0a}
)

func main() {
	// encode
	input, err := funcBalanceOf.EncodeArgs(addrA)
	if err != nil {
		// ...
	}
	fmt.Printf("encoded: 0x%x\n", input)

	// decode
	var who common.Address
	if err := funcBalanceOf.DecodeArgs(input, &who); err != nil {
		// ...
	}
	fmt.Printf("decoded: balanceOf(%s)\n", who)
}
Output:

encoded: 0x70a082310000000000000000000000000a00000000000000000000000000000000000000
decoded: balanceOf(0x0a00000000000000000000000000000000000000)
Example (Erc20)

ABI bindings for the ERC20 functions.

package main

import (
	"github.com/lmittmann/w3"
)

func main() {
	var (
		funcTotalSupply  = w3.MustNewFunc("totalSupply()", "uint256")
		funcBalanceOf    = w3.MustNewFunc("balanceOf(address)", "uint256")
		funcTransfer     = w3.MustNewFunc("transfer(address to, uint256 amount)", "bool")
		funcAllowance    = w3.MustNewFunc("allowance(address owner, address spender)", "uint256")
		funcApprove      = w3.MustNewFunc("approve(address spender, uint256 amount)", "bool")
		funcTransferFrom = w3.MustNewFunc("transferFrom(address from, address to, uint256 amount)", "bool")
	)
	_ = funcTotalSupply
	_ = funcBalanceOf
	_ = funcTransfer
	_ = funcAllowance
	_ = funcApprove
	_ = funcTransferFrom
}
Output:

Example (UniswapV4Swap)

ABI bindings for the Uniswap v4 swap function.

package main

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
)

var (
	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
	addrDAI  = w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F")
)

func main() {
	funcSwap := w3.MustNewFunc(`swap(
		(address currency0, address currency1, uint24 fee, int24 tickSpacing, address hooks) key,
		(bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96) params,
		bytes hookData
	)`, "int256 delta")

	// ABI binding for the PoolKey struct.
	type PoolKey struct {
		Currency0   common.Address
		Currency1   common.Address
		Fee         *big.Int
		TickSpacing *big.Int
		Hooks       common.Address
	}

	// ABI binding for the SwapParams struct.
	type SwapParams struct {
		ZeroForOne        bool
		AmountSpecified   *big.Int
		SqrtPriceLimitX96 *big.Int
	}

	// encode
	input, _ := funcSwap.EncodeArgs(
		&PoolKey{
			Currency0:   addrWETH,
			Currency1:   addrDAI,
			Fee:         big.NewInt(0),
			TickSpacing: big.NewInt(0),
		},
		&SwapParams{
			ZeroForOne:        false,
			AmountSpecified:   big.NewInt(0),
			SqrtPriceLimitX96: big.NewInt(0),
		},
		[]byte{},
	)
	fmt.Printf("encoded: 0x%x\n", input)
}
Output:

encoded: 0xf3cd914c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000

func MustNewFunc

func MustNewFunc(signature, returns string) *Func

MustNewFunc is like NewFunc but panics if the signature or returns parsing fails.

func NewFunc

func NewFunc(signature, returns string) (*Func, error)

NewFunc returns a new Smart Contract function ABI binding from the given Solidity function signature and its returns.

An error is returned if the signature or returns parsing fails.

func (*Func) DecodeArgs added in v0.1.1

func (f *Func) DecodeArgs(input []byte, args ...any) error

DecodeArgs ABI-decodes the given input to the given args.

func (*Func) DecodeReturns added in v0.1.1

func (f *Func) DecodeReturns(output []byte, returns ...any) error

DecodeReturns ABI-decodes the given output to the given returns.

Example (GetReserves)
package main

import (
	"fmt"
	"math/big"

	"github.com/lmittmann/w3"
)

func main() {
	funcGetReserves := w3.MustNewFunc("getReserves()", "uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast")
	output := w3.B(
		"0x00000000000000000000000000000000000000000000003635c9adc5dea00000",
		"0x0000000000000000000000000000000000000000000000a2a15d09519be00000",
		"0x0000000000000000000000000000000000000000000000000000000064373057",
	)

	var (
		reserve0, reserve1 *big.Int
		blockTimestampLast uint32
	)
	if err := funcGetReserves.DecodeReturns(output, &reserve0, &reserve1, &blockTimestampLast); err != nil {
		// ...
	}
	fmt.Println("Reserve0:", reserve0)
	fmt.Println("Reserve1:", reserve1)
	fmt.Println("BlockTimestampLast:", blockTimestampLast)
}
Output:

Reserve0: 1000000000000000000000
Reserve1: 3000000000000000000000
BlockTimestampLast: 1681338455

func (*Func) EncodeArgs added in v0.1.1

func (f *Func) EncodeArgs(args ...any) ([]byte, error)

EncodeArgs ABI-encodes the given args and prepends the Func's 4-byte selector.

type Option added in v0.12.0

type Option func(*Client)

An Option configures a Client.

func WithRateLimiter added in v0.12.0

func WithRateLimiter(rl *rate.Limiter, costFunc func(methods []string) (cost int)) Option

WithRateLimiter sets the rate limiter for the client. Set the optional argument costFunc to nil to limit the number of requests. Supply a costFunc to limit the the number of requests based on individual RPC calls for advanced rate limiting by e.g. Compute Units (CUs). Note that only if len(methods) > 1, the calls are sent in a batch request.

Directories

Path Synopsis
abi
Package abi implements a Solidity ABI lexer and parser.
Package abi implements a Solidity ABI lexer and parser.
fourbyte
Code generated by "go generate"; DO NOT EDIT.
Code generated by "go generate"; DO NOT EDIT.
mod
module
debug
Package debug implements RPC API bindings for methods in the "debug" namespace.
Package debug implements RPC API bindings for methods in the "debug" namespace.
eth
Package eth implements RPC API bindings for methods in the "eth" namespace.
Package eth implements RPC API bindings for methods in the "eth" namespace.
txpool
Package txpool implements RPC API bindings for methods in the "txpool" namespace.
Package txpool implements RPC API bindings for methods in the "txpool" namespace.
web3
Package web3 implements RPC API bindings for methods in the "web3" namespace.
Package web3 implements RPC API bindings for methods in the "web3" namespace.
Package rpctest provides utilities for testing RPC methods.
Package rpctest provides utilities for testing RPC methods.
Package w3types implements common types.
Package w3types implements common types.
Package w3vm provides a VM for executing EVM messages.
Package w3vm provides a VM for executing EVM messages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL