github.com/lmittmann/w3@v0.20.0/example_test.go (about)

     1  package w3_test
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"time"
     9  
    10  	"github.com/ethereum/go-ethereum/common"
    11  	"github.com/ethereum/go-ethereum/core/types"
    12  	"github.com/ethereum/go-ethereum/params"
    13  	"github.com/lmittmann/w3"
    14  	"github.com/lmittmann/w3/module/eth"
    15  	"github.com/lmittmann/w3/w3types"
    16  	"github.com/lmittmann/w3/w3vm"
    17  	"golang.org/x/time/rate"
    18  )
    19  
    20  var (
    21  	funcName      = w3.MustNewFunc("name()", "string")
    22  	funcSymbol    = w3.MustNewFunc("symbol()", "string")
    23  	funcDecimals  = w3.MustNewFunc("decimals()", "uint8")
    24  	funcBalanceOf = w3.MustNewFunc("balanceOf(address)", "uint256")
    25  
    26  	addrA = common.Address{0x0a}
    27  	addrB = common.Address{0x0b}
    28  
    29  	prvA *ecdsa.PrivateKey // dummy private key for addrA
    30  
    31  	addrWETH = w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
    32  	addrDAI  = w3.A("0x6B175474E89094C44Da98b954EedeAC495271d0F")
    33  
    34  	client = w3.MustDial("https://ethereum-rpc.publicnode.com")
    35  )
    36  
    37  // Call the name, symbol, decimals, and balanceOf functions of the Wrapped Ether
    38  // in a single batch.
    39  func ExampleClient_batchCallFunc() {
    40  	blockNumber := big.NewInt(20_000_000)
    41  
    42  	var (
    43  		name, symbol string
    44  		decimals     uint8
    45  		balance      big.Int
    46  	)
    47  	if err := client.Call(
    48  		eth.CallFunc(addrWETH, funcName).Returns(&name),
    49  		eth.CallFunc(addrWETH, funcSymbol).Returns(&symbol),
    50  		eth.CallFunc(addrWETH, funcDecimals).Returns(&decimals),
    51  		eth.CallFunc(addrWETH, funcBalanceOf, addrWETH).AtBlock(blockNumber).Returns(&balance),
    52  	); err != nil {
    53  		// ...
    54  	}
    55  
    56  	fmt.Printf("%s's own balance: %s %s\n", name, w3.FromWei(&balance, decimals), symbol)
    57  	// Output:
    58  	// Wrapped Ether's own balance: 748.980125465356473638 WETH
    59  }
    60  
    61  // Call the Uniswap V3 Quoter for quotes on swapping 100 WETH for DAI in pools
    62  // of all fee tiers in a single batch.
    63  func ExampleClient_batchCallFuncUniswapQuoter() {
    64  	blockNumber := big.NewInt(20_000_000)
    65  
    66  	var (
    67  		addrUniswapV3Quoter = w3.A("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6")
    68  		addrTokenIn         = addrWETH
    69  		addrTokenOut        = addrDAI
    70  
    71  		funcQuote = w3.MustNewFunc(`quoteExactInputSingle(
    72  			address tokenIn,
    73  			address tokenOut,
    74  			uint24 fee,
    75  			uint256 amountIn,
    76  			uint160 sqrtPriceLimitX96clear
    77  		)`, "uint256 amountOut")
    78  	)
    79  
    80  	var (
    81  		amountIn       = w3.I("100 ether")
    82  		amountOut100   *big.Int
    83  		amountOut500   *big.Int
    84  		amountOut3000  *big.Int
    85  		amountOut10000 *big.Int
    86  	)
    87  	if err := client.Call(
    88  		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(100), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut100),
    89  		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(500), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut500),
    90  		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(3000), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut3000),
    91  		eth.CallFunc(addrUniswapV3Quoter, funcQuote, addrTokenIn, addrTokenOut, big.NewInt(10000), amountIn, w3.Big0).AtBlock(blockNumber).Returns(&amountOut10000),
    92  	); err != nil {
    93  		// ...
    94  	}
    95  	fmt.Println("Swap 100 WETH for DAI:")
    96  	fmt.Printf("Pool with 0.01%% fee: %s DAI\n", w3.FromWei(amountOut100, 18))
    97  	fmt.Printf("Pool with 0.05%% fee: %s DAI\n", w3.FromWei(amountOut500, 18))
    98  	fmt.Printf("Pool with  0.3%% fee: %s DAI\n", w3.FromWei(amountOut3000, 18))
    99  	fmt.Printf("Pool with    1%% fee: %s DAI\n", w3.FromWei(amountOut10000, 18))
   100  	// Output:
   101  	// Swap 100 WETH for DAI:
   102  	// Pool with 0.01% fee: 0.840975419471618588 DAI
   103  	// Pool with 0.05% fee: 371877.453117609415215338 DAI
   104  	// Pool with  0.3% fee: 378532.856217317782434539 DAI
   105  	// Pool with    1% fee: 3447.634026125332130689 DAI
   106  }
   107  
   108  // Fetch the nonce and balance of an EOA in a single batch.
   109  func ExampleClient_batchEOAState() {
   110  	var (
   111  		nonce   uint64
   112  		balance *big.Int
   113  	)
   114  	if err := client.Call(
   115  		eth.Nonce(addrA, nil).Returns(&nonce),
   116  		eth.Balance(addrA, nil).Returns(&balance),
   117  	); err != nil {
   118  		// ...
   119  	}
   120  
   121  	fmt.Printf("Nonce: %d\nBalance: %d\n", nonce, balance)
   122  }
   123  
   124  // Fetch a transaction and its receipt in a single batch.
   125  func ExampleClient_batchTxDetails() {
   126  	txHash := w3.H("0xc31d7e7e85cab1d38ce1b8ac17e821ccd47dbde00f9d57f2bd8613bff9428396")
   127  
   128  	var (
   129  		tx      *types.Transaction
   130  		receipt *types.Receipt
   131  	)
   132  	if err := client.Call(
   133  		eth.Tx(txHash).Returns(&tx),
   134  		eth.TxReceipt(txHash).Returns(&receipt),
   135  	); err != nil {
   136  		// ...
   137  	}
   138  
   139  	fmt.Printf("Tx: %#v\nReceipt: %#v\n", tx, receipt)
   140  }
   141  
   142  // Fetch 1000 blocks in batches.
   143  func ExampleClient_batchBlocks() {
   144  	const (
   145  		startBlock = 20_000_000
   146  		nBlocks    = 1000
   147  		batchSize  = 100
   148  	)
   149  
   150  	blocks := make([]*types.Block, nBlocks)
   151  	calls := make([]w3types.RPCCaller, batchSize)
   152  	for i := 0; i < nBlocks; i += batchSize {
   153  		for j := 0; j < batchSize; j++ {
   154  			blockNumber := new(big.Int).SetUint64(uint64(startBlock + i + j))
   155  			calls[j] = eth.BlockByNumber(blockNumber).Returns(&blocks[i+j])
   156  		}
   157  		if err := client.Call(calls...); err != nil {
   158  			// ...
   159  		}
   160  		fmt.Printf("Fetched %d blocks\n", i+batchSize)
   161  	}
   162  }
   163  
   164  // Handle errors of individual calls in a batch.
   165  func ExampleClient_batchHandleError() {
   166  	tokens := []common.Address{addrWETH, addrA, addrB}
   167  	symbols := make([]string, len(tokens))
   168  
   169  	// build rpc calls
   170  	calls := make([]w3types.RPCCaller, len(tokens))
   171  	for i, token := range tokens {
   172  		calls[i] = eth.CallFunc(token, funcSymbol).Returns(&symbols[i])
   173  	}
   174  
   175  	var batchErr w3.CallErrors
   176  	if err := client.Call(calls...); errors.As(err, &batchErr) {
   177  	} else if err != nil {
   178  		// all calls failed
   179  	}
   180  
   181  	for i, symbol := range symbols {
   182  		if len(batchErr) > 0 && batchErr[i] != nil {
   183  			symbol = "call failed"
   184  		}
   185  		fmt.Printf("%s: %s\n", tokens[i], symbol)
   186  	}
   187  	// Output:
   188  	// 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2: WETH
   189  	// 0x0a00000000000000000000000000000000000000: call failed
   190  	// 0x0B00000000000000000000000000000000000000: call failed
   191  }
   192  
   193  // Fetch the token balance of an address.
   194  func ExampleClient_callFunc() {
   195  	var balance *big.Int
   196  	if err := client.Call(
   197  		eth.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balance),
   198  	); err != nil {
   199  		// ...
   200  	}
   201  
   202  	fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
   203  	// Output:
   204  	// Balance: 0 WETH
   205  }
   206  
   207  // Fetch the token balance of an address, with state override.
   208  func ExampleClient_callFuncWithStateOverride() {
   209  	var balance *big.Int
   210  	if err := client.Call(
   211  		eth.CallFunc(addrWETH, funcBalanceOf, addrA).Overrides(w3types.State{
   212  			addrWETH: {Storage: w3types.Storage{
   213  				w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
   214  			}},
   215  		}).Returns(&balance),
   216  	); err != nil {
   217  		// ...
   218  	}
   219  
   220  	fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
   221  	// Output:
   222  	// Balance: 100 WETH
   223  }
   224  
   225  // Send Ether transfer.
   226  func ExampleClient_sendETHTransfer() {
   227  	var (
   228  		nonce    uint64
   229  		gasPrice *big.Int
   230  	)
   231  	if err := client.Call(
   232  		eth.Nonce(addrA, nil).Returns(&nonce),
   233  		eth.GasPrice().Returns(&gasPrice),
   234  	); err != nil {
   235  		// ...
   236  	}
   237  
   238  	signer := types.LatestSigner(params.MainnetChainConfig)
   239  	tx := types.MustSignNewTx(prvA, signer, &types.LegacyTx{
   240  		Nonce:    nonce,
   241  		Gas:      21_000,
   242  		GasPrice: gasPrice,
   243  		To:       &addrB,
   244  		Value:    w3.I("1 ether"),
   245  	})
   246  
   247  	var txHash common.Hash
   248  	if err := client.Call(eth.SendTx(tx).Returns(&txHash)); err != nil {
   249  		// ...
   250  	}
   251  
   252  	fmt.Printf("Sent tx: %s\n", txHash)
   253  }
   254  
   255  // Send ERC20 token transfer (Wrapped Ether).
   256  func ExampleClient_sendTokenTransfer() {
   257  	var (
   258  		nonce    uint64
   259  		gasPrice *big.Int
   260  	)
   261  	if err := client.Call(
   262  		eth.Nonce(addrA, nil).Returns(&nonce),
   263  		eth.GasPrice().Returns(&gasPrice),
   264  	); err != nil {
   265  		// ...
   266  	}
   267  
   268  	funcTransfer := w3.MustNewFunc("transfer(address receiver, uint256 amount)", "bool")
   269  	data, err := funcTransfer.EncodeArgs(addrB, w3.I("1 ether"))
   270  	if err != nil {
   271  		// ...
   272  	}
   273  
   274  	signer := types.LatestSigner(params.MainnetChainConfig)
   275  	tx := types.MustSignNewTx(prvA, signer, &types.LegacyTx{
   276  		Nonce:    nonce,
   277  		Gas:      100_000,
   278  		GasPrice: gasPrice,
   279  		To:       &addrWETH,
   280  		Data:     data,
   281  	})
   282  
   283  	var txHash common.Hash
   284  	if err := client.Call(eth.SendTx(tx).Returns(&txHash)); err != nil {
   285  		// ...
   286  	}
   287  
   288  	fmt.Printf("Sent tx: %s\n", txHash)
   289  }
   290  
   291  // Subscribe to pending transactions.
   292  func ExampleClient_subscribeToPendingTransactions() {
   293  	client, err := w3.Dial("wss://mainnet.gateway.tenderly.co")
   294  	if err != nil {
   295  		// ...
   296  	}
   297  	defer client.Close()
   298  
   299  	pendingTxCh := make(chan *types.Transaction)
   300  	sub, err := client.Subscribe(eth.PendingTransactions(pendingTxCh))
   301  	if err != nil {
   302  		// ...
   303  	}
   304  
   305  	for {
   306  		select {
   307  		case tx := <-pendingTxCh:
   308  			fmt.Printf("New pending tx: %s\n", tx.Hash())
   309  		case err := <-sub.Err():
   310  			fmt.Printf("Subscription error: %v\n", err)
   311  			return
   312  		}
   313  	}
   314  }
   315  
   316  // Rate Limit the number of requests to 10 per second, with bursts of up to 20
   317  // requests.
   318  func ExampleClient_rateLimitByRequest() {
   319  	client, err := w3.Dial("https://ethereum-rpc.publicnode.com",
   320  		w3.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second/10), 20), nil),
   321  	)
   322  	if err != nil {
   323  		// ...
   324  	}
   325  	defer client.Close()
   326  }
   327  
   328  // Rate Limit the number of requests to 300 compute units (CUs) per second, with
   329  // bursts of up to 300 CUs.
   330  // An individual CU can be charged per RPC method call.
   331  func ExampleClient_rateLimitByComputeUnits() {
   332  	// cu returns the CU cost for all method calls in a batch.
   333  	cu := func(methods []string) (cost int) {
   334  		for _, method := range methods {
   335  			switch method {
   336  			case "eth_blockNumber":
   337  				cost += 5
   338  			case "eth_getBalance",
   339  				"eth_getBlockByNumber",
   340  				"eth_getCode",
   341  				"eth_getStorageAt",
   342  				"eth_getTransactionByHash",
   343  				"eth_getTransactionReceipt":
   344  				cost += 15
   345  			case "eth_call":
   346  				cost += 20
   347  			case "eth_getTransactionCount":
   348  				cost += 25
   349  			default:
   350  				panic(fmt.Sprintf("unknown costs for %q", method))
   351  			}
   352  		}
   353  		return cost
   354  	}
   355  
   356  	client, err := w3.Dial("https://ethereum-rpc.publicnode.com",
   357  		w3.WithRateLimiter(rate.NewLimiter(rate.Every(time.Second/300), 300), cu),
   358  	)
   359  	if err != nil {
   360  		// ...
   361  	}
   362  	defer client.Close()
   363  }
   364  
   365  // ABI bindings for the ERC20 functions.
   366  func ExampleFunc_erc20() {
   367  	var (
   368  		funcTotalSupply  = w3.MustNewFunc("totalSupply()", "uint256")
   369  		funcBalanceOf    = w3.MustNewFunc("balanceOf(address)", "uint256")
   370  		funcTransfer     = w3.MustNewFunc("transfer(address to, uint256 amount)", "bool")
   371  		funcAllowance    = w3.MustNewFunc("allowance(address owner, address spender)", "uint256")
   372  		funcApprove      = w3.MustNewFunc("approve(address spender, uint256 amount)", "bool")
   373  		funcTransferFrom = w3.MustNewFunc("transferFrom(address from, address to, uint256 amount)", "bool")
   374  	)
   375  	_ = funcTotalSupply
   376  	_ = funcBalanceOf
   377  	_ = funcTransfer
   378  	_ = funcAllowance
   379  	_ = funcApprove
   380  	_ = funcTransferFrom
   381  }
   382  
   383  // Encode and decode the arguments of the balanceOf function.
   384  func ExampleFunc_balanceOf() {
   385  	// encode
   386  	input, err := funcBalanceOf.EncodeArgs(addrA)
   387  	if err != nil {
   388  		// ...
   389  	}
   390  	fmt.Printf("encoded: 0x%x\n", input)
   391  
   392  	// decode
   393  	var who common.Address
   394  	if err := funcBalanceOf.DecodeArgs(input, &who); err != nil {
   395  		// ...
   396  	}
   397  	fmt.Printf("decoded: balanceOf(%s)\n", who)
   398  	// Output:
   399  	// encoded: 0x70a082310000000000000000000000000a00000000000000000000000000000000000000
   400  	// decoded: balanceOf(0x0a00000000000000000000000000000000000000)
   401  }
   402  
   403  // ABI bindings for the Uniswap v4 swap function.
   404  func ExampleFunc_uniswapV4Swap() {
   405  	// ABI binding for the PoolKey struct.
   406  	type PoolKey struct {
   407  		Currency0   common.Address
   408  		Currency1   common.Address
   409  		Fee         *big.Int `abitype:"uint24"`
   410  		TickSpacing *big.Int `abitype:"int24"`
   411  		Hooks       common.Address
   412  	}
   413  
   414  	// ABI binding for the SwapParams struct.
   415  	type SwapParams struct {
   416  		ZeroForOne        bool
   417  		AmountSpecified   *big.Int `abitype:"int256"`
   418  		SqrtPriceLimitX96 *big.Int `abitype:"uint160"`
   419  	}
   420  
   421  	funcSwap := w3.MustNewFunc(`swap(PoolKey key, SwapParams params, bytes hookData)`, "int256 delta",
   422  		PoolKey{}, SwapParams{},
   423  	)
   424  
   425  	// encode
   426  	input, _ := funcSwap.EncodeArgs(
   427  		&PoolKey{
   428  			Currency0:   addrWETH,
   429  			Currency1:   addrDAI,
   430  			Fee:         big.NewInt(0),
   431  			TickSpacing: big.NewInt(0),
   432  		},
   433  		&SwapParams{
   434  			ZeroForOne:        false,
   435  			AmountSpecified:   big.NewInt(0),
   436  			SqrtPriceLimitX96: big.NewInt(0),
   437  		},
   438  		[]byte{},
   439  	)
   440  	fmt.Printf("encoded: 0x%x\n", input)
   441  	// Output:
   442  	// encoded: 0xf3cd914c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000000
   443  }
   444  
   445  func ExampleFunc_DecodeReturns_getReserves() {
   446  	funcGetReserves := w3.MustNewFunc("getReserves()", "uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast")
   447  	output := w3.B(
   448  		"0x00000000000000000000000000000000000000000000003635c9adc5dea00000",
   449  		"0x0000000000000000000000000000000000000000000000a2a15d09519be00000",
   450  		"0x0000000000000000000000000000000000000000000000000000000064373057",
   451  	)
   452  
   453  	var (
   454  		reserve0, reserve1 *big.Int
   455  		blockTimestampLast uint32
   456  	)
   457  	if err := funcGetReserves.DecodeReturns(output, &reserve0, &reserve1, &blockTimestampLast); err != nil {
   458  		// ...
   459  	}
   460  	fmt.Println("Reserve0:", reserve0)
   461  	fmt.Println("Reserve1:", reserve1)
   462  	fmt.Println("BlockTimestampLast:", blockTimestampLast)
   463  	// Output:
   464  	// Reserve0: 1000000000000000000000
   465  	// Reserve1: 3000000000000000000000
   466  	// BlockTimestampLast: 1681338455
   467  }
   468  
   469  func ExampleEvent_decodeTransferEvent() {
   470  	var (
   471  		eventTransfer = w3.MustNewEvent("Transfer(address indexed from, address indexed to, uint256 value)")
   472  		log           = &types.Log{
   473  			Address: w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
   474  			Topics: []common.Hash{
   475  				w3.H("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
   476  				w3.H("0x000000000000000000000000000000000000000000000000000000000000c0fe"),
   477  				w3.H("0x000000000000000000000000000000000000000000000000000000000000dead"),
   478  			},
   479  			Data: w3.B("0x0000000000000000000000000000000000000000000000001111d67bb1bb0000"),
   480  		}
   481  
   482  		from  common.Address
   483  		to    common.Address
   484  		value big.Int
   485  	)
   486  
   487  	if err := eventTransfer.DecodeArgs(log, &from, &to, &value); err != nil {
   488  		fmt.Printf("Failed to decode event log: %v\n", err)
   489  		return
   490  	}
   491  	fmt.Printf("Transferred %s WETH9 from %s to %s", w3.FromWei(&value, 18), from, to)
   492  	// Output:
   493  	// Transferred 1.23 WETH9 from 0x000000000000000000000000000000000000c0Fe to 0x000000000000000000000000000000000000dEaD
   494  }