Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Readme #126

Merged
merged 36 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ba4a244
updated intro
lmittmann Feb 1, 2024
70f225c
use non breaking hyphen
lmittmann Feb 2, 2024
c8a7ab4
updated RPC section
lmittmann Feb 4, 2024
347a533
added VM section
lmittmann Feb 4, 2024
664c265
changed code ident
lmittmann Feb 4, 2024
f0e0c17
added links
lmittmann Feb 4, 2024
b8ccb99
updated client example
lmittmann Mar 3, 2024
ffbd4cd
added VM example
lmittmann Mar 3, 2024
53ee615
updated spacing
lmittmann Mar 3, 2024
ee98691
small fix
lmittmann Mar 3, 2024
b7f775a
updated VM example
lmittmann Mar 5, 2024
eb066ee
.
lmittmann Mar 5, 2024
93f357d
dropped g1.22 function
lmittmann Mar 6, 2024
64beb27
.
lmittmann Mar 6, 2024
2fca65d
.
lmittmann Mar 6, 2024
bf6ea13
tabs to spaces
lmittmann Mar 6, 2024
71e21b7
renamed and linked `NewFunc` example
lmittmann Mar 25, 2024
45ecd9f
highlight examples
lmittmann Mar 25, 2024
96b4d74
added UniswapV4Swap ABI-example
lmittmann Mar 25, 2024
8a95053
~~s~~
lmittmann Mar 25, 2024
a07b8d4
added "utils" section
lmittmann Mar 30, 2024
fc4045f
abi-section: updated example description
lmittmann Mar 30, 2024
c97f991
added detailed rpc sections
lmittmann Mar 30, 2024
130eba7
w3types: added `RPCCaller` example
lmittmann Mar 30, 2024
6690993
added content for "Custom RPC Method Bindings"-section
lmittmann Mar 30, 2024
f36d0d0
added example code
lmittmann Mar 30, 2024
d7044b9
dropped old text
lmittmann Mar 30, 2024
6117136
w3types: improved example doc
lmittmann Mar 30, 2024
7d81887
w3vm: added `ExampleVM_Call`
lmittmann Mar 30, 2024
6261256
added client example
lmittmann Mar 30, 2024
5c26241
use non breaking spaces in links
lmittmann Mar 30, 2024
c216df5
small fix
lmittmann Mar 30, 2024
b832ffe
`map[common.Hash]common.Hash` -> `w3types.Storage`
lmittmann Mar 30, 2024
aec9bd3
docs update
lmittmann Mar 31, 2024
3961cb0
small css update
lmittmann Mar 31, 2024
ca63b92
dep update
lmittmann Mar 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 148 additions & 131 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,189 +1,154 @@
# w3
# `w3`: Enhanced Ethereum Integration for Go

[![Go Reference](https://pkg.go.dev/badge/github.com/lmittmann/w3.svg)](https://pkg.go.dev/github.com/lmittmann/w3)
[![Go Report Card](https://goreportcard.com/badge/github.com/lmittmann/w3)](https://goreportcard.com/report/github.com/lmittmann/w3)
[![Coverage Status](https://coveralls.io/repos/github/lmittmann/w3/badge.svg?branch=main)](https://coveralls.io/github/lmittmann/w3?branch=main)
[![Latest Release](https://img.shields.io/github/v/release/lmittmann/w3)](https://github.com/lmittmann/w3/releases)
<img src="https://w3.cool/gopher.png" align="right" alt="W3 Gopher" width="158" height="224">

<img src="https://user-images.githubusercontent.com/3458786/153202258-24bf253e-5ab0-4efd-a0ed-43dc1bf093c9.png" align="right" alt="W3 Gopher" width="158" height="224">
`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**.

Package `w3` implements a blazing fast and modular Ethereum JSON RPC client with
first-class ABI support.

* **Batch request** support significantly reduces the duration of requests to
both remote and local endpoints.
* **ABI** bindings are specified for individual functions using Solidity syntax.
No need for `abigen` and ABI JSON files.
* **Modular** API allows to create custom RPC method integrations that can be
used alongside the methods implemented by the package.

`w3` is closely linked to [go-ethereum](https://github.com/ethereum/go-ethereum)
and uses a variety of its types, such as [`common.Address`](https://pkg.go.dev/github.com/ethereum/go-ethereum/common#Address)
or [`types.Transaction`](https://pkg.go.dev/github.com/ethereum/go-ethereum/core/types#Transaction).

Batch requests with `w3` are up to **85x faster** than sequential requests with
`go-ethereum/ethclient`.

<details>
<summary>Benchmarks</summary>
<pre>
name ethclient time/op w3 time/op delta
Call_BalanceNonce 78.3ms ± 2% 39.0ms ± 1% -50.15% (p=0.000 n=23+22)
Call_Balance100 3.90s ± 5% 0.05s ± 2% -98.84% (p=0.000 n=20+24)
Call_BalanceOf100 3.99s ± 3% 0.05s ± 2% -98.73% (p=0.000 n=22+23)
Call_Block100 6.89s ± 7% 1.94s ±11% -71.77% (p=0.000 n=24+23)
</pre>
</details>

## Install

```
go get github.com/lmittmann/w3
```


## Getting Started

> **Note**
> Check out the [examples](https://github.com/lmittmann/w3/tree/main/examples)!
## At a Glance

Connect to an RPC endpoint via HTTP, WebSocket, or IPC using [`Dial`](https://pkg.go.dev/github.com/lmittmann/w3#Dial)
or [`MustDial`](https://pkg.go.dev/github.com/lmittmann/w3#MustDial).
* 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&nbsp;more&nbsp;↗](#rpc-client)
* Use `w3vm.VM` to simulate EVM execution with optional tracing and Mainnet state forking, or test Smart Contracts. [learn&nbsp;more&nbsp;↗](#vm)
* Use `w3.Func` and `w3.Event` to create ABI bindings from Solidity function and event signatures. [learn&nbsp;more&nbsp;↗](#abi-bindings)
* Use `w3.A`, `w3.H`, and many other utility functions to parse addresses, hashes, and other common types from strings. [learn&nbsp;more&nbsp;↗](#utils)

```go
// Connect (or panic on error)
client := w3.MustDial("https://rpc.ankr.com/eth")
defer client.Close()
```

## Getting Started

## Batch Requests
### RPC Client

Batch request support in the [`Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client)
allows to send multiple RPC requests in a single HTTP request. The speed gains
to remote endpoints are huge. Fetching 100 blocks in a single batch request
with `w3` is ~80x faster compared to sequential requests with `ethclient`.
[`w3.Client`](https://pkg.go.dev/github.com/lmittmann/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: Request the nonce and balance of an address in a single request
**Example:** Batch Request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client))

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

nonce uint64
balance big.Int
)
err := client.Call(
eth.Nonce(addr, nil).Returns(&nonce),
eth.Balance(addr, nil).Returns(&balance),
// 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.

## ABI Bindings
#### Learn More
* List of supported [**RPC methods**](#rpc-methods)
* Learn how to create [**custom RPC method bindings**](#custom-rpc-method-bindings)

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

Example: ABI binding for the ERC20-function `balanceOf`
[`w3vm.VM`](https://pkg.go.dev/github.com/lmittmann/w3/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.

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

A [`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func) can be used to

* encode arguments to the contracts input data ([`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs)),
* decode arguments from the contracts input data ([`Func.DecodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeArgs)), and
* decode returns form the contracts output data ([`Func.DecodeReturns`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeReturns)).

### Reading Contracts

[`Func`](https://pkg.go.dev/github.com/lmittmann/w3#Func)'s can be used with
[`eth.CallFunc`](https://pkg.go.dev/github.com/lmittmann/w3/module/eth#CallFunc)
in the client to read contract data.
**Example:** Simulate an Uniswap v3 swap ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM))

```go
var (
weth9 = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
dai = w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F")

weth9Balance big.Int
daiBalance big.Int
)

err := client.Call(
eth.CallFunc(weth9, funcBalanceOf, addr).Returns(&weth9Balance),
eth.CallFunc(dai, funcBalanceOf, addr).Returns(&daiBalance),
// 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
}
```

### Writing Contracts

Sending a transaction to a contract requires three steps.

1. Encode the transaction input data using [`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs).

```go
var funcTransfer = w3.MustNewFunc("transfer(address,uint256)", "bool")
### ABI Bindings

input, err := funcTransfer.EncodeArgs(w3.A("0x…"), w3.I("1 ether"))
```
ABI bindings in `w3` are specified for individual functions using Solidity syntax and are usable for any contract that supports that function.

2. Create a signed transaction to the contract using [go-ethereum/types](https://github.com/ethereum/go-ethereum).
**Example:** ABI binding for the ERC20 `balanceOf` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-BalanceOf))

```go
signer := types.LatestSigner(params.MainnetChainConfig)
tx := types.MustSignNewTx(privKey, signer, &types.DynamicFeeTx{
To: w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
Nonce: 0,
Data: input,
Gas: 75000,
GasFeeCap: w3.I("100 gwei"),
GasTipCap: w3.I("1 gwei"),
})
funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")
```

3. Send the signed transaction.
**Example:** ABI binding for the Uniswap v4 `swap` function ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-NewFunc-UniswapV4Swap))

```go
var txHash common.Hash
err := client.Call(
eth.SendTx(tx).Returns(&txHash),
)
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`](https://pkg.go.dev/github.com/lmittmann/w3#Func) can be used to

## Custom RPC Methods

Custom RPC methods can be called with the `w3` client by creating a
[`w3types.Caller`](https://pkg.go.dev/github.com/lmittmann/w3/w3types#Caller)
implementation.
The `w3/module/eth` package can be used as implementation reference.

* encode arguments to the contracts input data ([`Func.EncodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.EncodeArgs)),
* decode arguments from the contracts input data ([`Func.DecodeArgs`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeArgs)), and
* decode returns form the contracts output data ([`Func.DecodeReturns`](https://pkg.go.dev/github.com/lmittmann/w3#Func.DecodeReturns)).

## Utils
### Utils

Static addresses, hashes, hex byte slices or `big.Int`'s can be parsed from
strings with the following utility functions.
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.

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

Note that these functions panic if the string cannot be parsed. Use
[go-ethereum/common](https://pkg.go.dev/github.com/ethereum/go-ethereum/common)
to parse strings that may not be valid instead.
Use [go-ethereum/common](https://pkg.go.dev/github.com/ethereum/go-ethereum/common) to parse strings that may not be valid instead.


## RPC Methods

List of supported RPC methods.
List of supported RPC methods for [`w3.Client`](https://pkg.go.dev/github.com/lmittmann/w3#Client).

### [`eth`](https://pkg.go.dev/github.com/lmittmann/w3/module/eth)

Expand Down Expand Up @@ -242,3 +207,55 @@ List of supported RPC methods.
| Package | Description
| :----------------------------------------------------------------------- | :-----------
| [github.com/lmittmann/flashbots](https://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`](https://pkg.go.dev/github.com/lmittmann/w3/w3types#RPCCaller) interface.

**Example:** RPC binding for the Otterscan `ots_getTransactionBySenderAndNonce` method ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3types#example-RPCCaller-GetTransactionBySenderAndNonce))

```go
// 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 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
}
```
Loading