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

docs: Improved Examples #194

Merged
merged 24 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ jobs:
- name: staticcheck
run: staticcheck ./...
- name: go mod tidy
run: |
go mod tidy -diff
cd examples/ && go mod tidy -diff
run: go mod tidy -diff

test:
name: Test
Expand Down
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ go get github.com/lmittmann/w3

[`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:** Batch Request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client))
**Example:** Batch Request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchEOAState))

```go
// 1. Connect to an RPC endpoint
Expand Down Expand Up @@ -59,10 +59,10 @@ if err := client.Call(

If one or more calls in a batch request fail, `Client.Call` returns an error of type [`w3.CallErrors`](https://pkg.go.dev/github.com/lmittmann/w3#CallErrors).

**Example:** Check which RPC calls failed in a batch request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-CallErrors))
**Example:** Check which RPC calls failed in a batch request ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchHandleError))
```go
var errs w3.CallErrors
if err := client.Call(rpcCalls...); errors.As(err, &errs) {
var batchErr w3.CallErrors
if err := client.Call(calls...); errors.As(err, &batchErr) {
// handle call errors
} else if err != nil {
// handle other errors
Expand All @@ -77,7 +77,7 @@ if err := client.Call(rpcCalls...); errors.As(err, &errs) {

[`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.

**Example:** Simulate an Uniswap v3 swap ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM))
**Example:** Simulate an Uniswap v3 swap ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-UniswapV3Swap))

```go
// 1. Create a VM that forks the Mainnet state from the latest block,
Expand Down Expand Up @@ -273,3 +273,11 @@ func (f *getTransactionBySenderAndNonceFactory) HandleResponse(elem rpc.BatchEle
return nil
}
```

## Sponsors

<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/public/ef-logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="docs/public/ef-logo.svg">
<img src="docs/public/ef-logo.svg" alt="ef logo" width="256" height="auto">
</picture>
213 changes: 0 additions & 213 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,21 @@ import (
"context"
"errors"
"flag"
"fmt"
"math/big"
"strconv"
"testing"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/google/go-cmp/cmp"
"github.com/lmittmann/w3"
"github.com/lmittmann/w3/internal"
"github.com/lmittmann/w3/module/eth"
"github.com/lmittmann/w3/rpctest"
"github.com/lmittmann/w3/w3types"
"golang.org/x/time/rate"
)

var (
Expand All @@ -36,189 +31,6 @@ var (
`< [{"jsonrpc":"2.0","id":1,"result":"0x1"},{"jsonrpc":"2.0","id":2,"result":"0x1"}]`
)

func ExampleClient() {
addr := w3.A("0x0000000000000000000000000000000000000000")

// 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
}

fmt.Printf("balance: %s\nnonce: %d\n", w3.FromWei(balance, 18), nonce)
}

func ExampleClient_Call_balanceOf() {
// Connect to RPC endpoint (or panic on error) and
// close the connection when you are done.
client := w3.MustDial("https://rpc.ankr.com/eth")
defer client.Close()

var (
addr = w3.A("0x000000000000000000000000000000000000dEaD")
weth9 = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

// Declare a Smart Contract function using Solidity syntax,
// no "abigen" and ABI JSON file needed.
balanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")

// Declare variables for the RPC responses.
ethBalance *big.Int
weth9Balance *big.Int
)

// Do batch request (both RPC requests are send in the same
// HTTP request).
if err := client.Call(
eth.Balance(addr, nil).Returns(&ethBalance),
eth.CallFunc(weth9, balanceOf, addr).Returns(&weth9Balance),
); err != nil {
fmt.Printf("Request failed: %v\n", err)
return
}

fmt.Printf("Combined balance: %v wei",
new(big.Int).Add(ethBalance, weth9Balance),
)
}

func ExampleClient_Call_nonceAndBalance() {
client := w3.MustDial("https://rpc.ankr.com/eth")
defer client.Close()

var (
addr = w3.A("0x000000000000000000000000000000000000c0Fe")

nonce uint64
balance *big.Int
)

if err := client.Call(
eth.Nonce(addr, nil).Returns(&nonce),
eth.Balance(addr, nil).Returns(&balance),
); err != nil {
fmt.Printf("Request failed: %v\n", err)
return
}

fmt.Printf("%s: Nonce: %d, Balance: ♦%s\n", addr, nonce, w3.FromWei(balance, 18))
}

func ExampleClient_Call_sendERC20transferTx() {
client := w3.MustDial("https://rpc.ankr.com/eth")
defer client.Close()

var (
weth9 = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
receiver = w3.A("0x000000000000000000000000000000000000c0Fe")
eoaPrv, _ = crypto.GenerateKey()
)

funcTransfer := w3.MustNewFunc("transfer(address receiver, uint256 amount)", "bool")
input, err := funcTransfer.EncodeArgs(receiver, w3.I("1 ether"))
if err != nil {
fmt.Printf("Failed to encode args: %v\n", err)
return
}

signer := types.LatestSigner(params.MainnetChainConfig)
var txHash common.Hash
if err := client.Call(
eth.SendTx(types.MustSignNewTx(eoaPrv, signer, &types.DynamicFeeTx{
Nonce: 0,
To: &weth9,
Data: input,
GasTipCap: w3.I("1 gwei"),
GasFeeCap: w3.I("100 gwei"),
Gas: 100_000,
})).Returns(&txHash),
); err != nil {
fmt.Printf("Failed to send tx: %v\n", err)
return
}

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

func ExampleCallErrors() {
client := w3.MustDial("https://rpc.ankr.com/eth")
defer client.Close()

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

// list of addresses that might be an ERC20 token
potentialTokens := []common.Address{
w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
w3.A("0x00000000219ab540356cBB839Cbe05303d7705Fa"),
}

// build symbol()-call for each potential ERC20 token
tokenSymbols := make([]string, len(potentialTokens))
rpcCalls := make([]w3types.RPCCaller, len(potentialTokens))
for i, addr := range potentialTokens {
rpcCalls[i] = eth.CallFunc(addr, funcSymbol).Returns(&tokenSymbols[i])
}

// execute batch request
var errs w3.CallErrors
if err := client.Call(rpcCalls...); errors.As(err, &errs) {
// handle call errors
} else if err != nil {
// handle other errors
fmt.Printf("Request failed: %v\n", err)
return
}

for i, addr := range potentialTokens {
var symbol string
if errs == nil || errs[i] == nil {
symbol = tokenSymbols[i]
} else {
symbol = fmt.Sprintf("unknown symbol: %v", errs[i].Error())
}
fmt.Printf("%s: %s\n", addr, symbol)
}

// Output:
// 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2: WETH
// 0x00000000219ab540356cBB839Cbe05303d7705Fa: unknown symbol: execution reverted
}

func ExampleClient_Subscribe() {
client := w3.MustDial("wss://mainnet.gateway.tenderly.co")
defer client.Close()

txCh := make(chan *types.Transaction)
sub, err := client.Subscribe(eth.PendingTransactions(txCh))
if err != nil {
fmt.Printf("Failed to subscribe: %v\n", err)
return
}

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

func TestClientCall(t *testing.T) {
tests := []struct {
Buf *bytes.Buffer
Expand Down Expand Up @@ -480,28 +292,3 @@ func BenchmarkCall_Block100(b *testing.B) {
}
})
}

func ExampleWithRateLimiter() {
// Limit the client to 30 requests per second and allow bursts of up to
// 100 requests.
client := w3.MustDial("https://rpc.ankr.com/eth",
w3.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second/30), 100), nil),
)
defer client.Close()
}

func ExampleWithRateLimiter_costFunc() {
// Limit the client to 30 calls per second and allow bursts of up to
// 100 calls using a cost function. Batch requests have an additional charge.
client := w3.MustDial("https://rpc.ankr.com/eth",
w3.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second/30), 100),
func(methods []string) (cost int) {
cost = len(methods) // charge 1 CU per call
if len(methods) > 1 {
cost += 1 // charge 1 CU extra for the batch itself
}
return cost
},
))
defer client.Close()
}
8 changes: 6 additions & 2 deletions docs/pages/_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {
'404': {
title: '404',
display: 'hidden',
'theme': {
theme: {
breadcrumb: false,
toc: false,
layout: 'full',
Expand Down Expand Up @@ -69,7 +69,11 @@ export default {
examples: {
title: 'Examples',
type: 'page',
href: 'https://github.com/lmittmann/w3/tree/main/examples',
},
releases: {
title: 'Releases',
type: 'page',
href: 'https://github.com/lmittmann/w3/releases',
newWindow: true
},
godoc: {
Expand Down
Empty file added docs/pages/examples.mdx
Empty file.
7 changes: 7 additions & 0 deletions docs/pages/examples/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
index: {
title: 'Examples',
display: 'hidden',
theme: { breadcrumb: false, toc: true, pagination: false },
},
}
34 changes: 34 additions & 0 deletions docs/pages/examples/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Examples

## `w3.Client` Examples

* **Batch Fetch** 1000 blocks ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchBlocks))
* **Batch Call** the name, symbol, decimals, and balanceOf functions of the Wrapped Ether in a single batch ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchCallFunc))
* **Batch Call** the Uniswap V3 Quoter for quotes on swapping 100 WETH for DAI in pools of all fee tiers in a single batch ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchCallFuncUniswapQuoter))
* **Batch Fetch** the nonce and balance of an EOA in a single batch ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchEOAState))
* **Batch Fetch** a transaction and its receipt in a single batch ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchTxDetails))
* **Call** the token balance of an address ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-CallFunc))
* **Call** the token balance of an address, with state override ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-CallFuncWithStateOverride))
* **Handle errors** of individual calls in a batch ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-BatchHandleError))
* **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 ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-RateLimitByComputeUnits))
* **Rate Limit** the number of requests to 10 per second, with bursts of up to 20 requests ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-RateLimitByRequest))
* **Send Ether** transfer ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-SendETHTransfer))
* **Send ERC20 token** transfer (Wrapped Ether) ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-SendTokenTransfer))
* **Subscribe** to pending transactions ([Playground](https://pkg.go.dev/github.com/lmittmann/w3#example-Client-SubscribeToPendingTransactions))

**[See all examples ↗](https://pkg.go.dev/github.com/lmittmann/w3#pkg-examples)**


## `w3vm.VM` Examples

* **Ether transfer** ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-SimpleTransfer))
* **ERC20 token transfer** with faked token balance (Wrapped Ether) ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-FakeTokenBalance))
* **Uniswap V3 swap** ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-UniswapV3Swap))

* **Call ERC20 balanceOf** with raw a `w3types.Message` using the `Message.{Func,Args}` helper ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-Call))
* **Call ERC20 balanceOf**, using the `VM.CallFunc` helper ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-CallFunc))
* **Prank** a sender ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-PrankZeroAddress))
* **Trace** the execution to obtain the access list ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-TraceAccessList))
* **Trace** the execution of all op's in a block ([Playground](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#example-VM-TraceBlock))

**[See all examples ↗](https://pkg.go.dev/github.com/lmittmann/w3/w3vm#pkg-examples)**
10 changes: 9 additions & 1 deletion docs/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ description: 'w3: Enhanced Ethereum Integration for Go'
</a>
</div>

<Image src="/gopher.png" alt="Hello" width={158} height={224} alt="W3 Gopher" align="right" className="ml-2 md:ml-6 mb-2"/>
<Image src="/gopher.png" alt="W3 Gopher" width={158} height={224} align="right" className="ml-2 md:ml-6 mb-2"/>

`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**.

Expand All @@ -35,3 +35,11 @@ go get github.com/lmittmann/w3
* Use `w3vm.VM` to simulate EVM execution with optional tracing and Mainnet state forking, or test Smart Contracts. [learn&nbsp;more&nbsp;➔](/vm-overview)
* Use `w3.Func` and `w3.Event` to create ABI bindings from Solidity function and event signatures. [learn&nbsp;more&nbsp;➔](/helper-abi)
* Use `w3.A`, `w3.H`, and many other utility functions to parse addresses, hashes, and other common types from strings. [learn&nbsp;more&nbsp;➔](/helper-utils)


## Sponsors

<div class="mt-4">
<img src="/ef-logo.svg" alt="EF Logo" class="w-full max-w-72 dark:hidden" />
<img src="/ef-logo-dark.svg" alt="EF Logo Dark" class="w-full max-w-72 hidden dark:block" />
</div>
1 change: 1 addition & 0 deletions docs/pages/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ tr._border-gray-300 {
/* Customize header */
.nextra-nav-container nav a[target="_blank"]:nth-child(2):after,
.nextra-nav-container nav a[target="_blank"]:nth-child(3):after,
.nextra-nav-container nav a[target="_blank"]:nth-child(4):after,
aside.nextra-sidebar-container li a[target="_blank"]:after {
content: url('data:image/svg+xml,<svg width="8" height="8" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.33622 2.84025L1.17647 10L0 8.82354L7.15975 1.66378H0.849212V0H10V9.15081H8.33622V2.84025Z" fill="%239195A6"/></svg>');
@apply pl-2;
Expand Down
1 change: 0 additions & 1 deletion docs/public/_redirects
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
/examples https://github.com/lmittmann/w3/tree/main/examples
Loading