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 }