mirror of
https://github.com/forta-network/go-multicall.git
synced 2026-02-26 15:47:23 +00:00
Merge c1a22458bf into 9467c4ddaa
This commit is contained in:
commit
ebcc51bd9c
3 changed files with 107 additions and 6 deletions
65
README.md
65
README.md
|
|
@ -19,6 +19,7 @@ go get github.com/forta-network/go-multicall
|
|||
|
||||
(See other examples under the `examples` directory!)
|
||||
|
||||
#### Multicall
|
||||
```go
|
||||
package main
|
||||
|
||||
|
|
@ -91,3 +92,67 @@ func main() {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### SingleCall
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/forta-network/go-multicall"
|
||||
)
|
||||
|
||||
const (
|
||||
APIURL = "https://cloudflare-eth.com"
|
||||
ERC20ABI = `[
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"tokenOwner",
|
||||
"type":"address"
|
||||
}
|
||||
],
|
||||
"name":"balanceOf",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"balance",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
]`
|
||||
)
|
||||
|
||||
func main() {
|
||||
caller, err := multicall.Dial(context.Background(), APIURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
contract, err := multicall.NewContract(ERC20ABI, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
single, err := caller.CallSingle(nil,
|
||||
contract.NewCall(
|
||||
nil,
|
||||
"balanceOf",
|
||||
common.HexToAddress("0xcEe284F754E854890e311e3280b767F80797180d"), // Arbitrum One gateway
|
||||
).Name("Arbitrum One gateway balance").SetExtend(map[string]string{
|
||||
"account": "0xcEe284F754E854890e311e3280b767F80797180d",
|
||||
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(single.CallName, ":", single.UnpackResult()[0].(*big.Int), single.Extend)
|
||||
}
|
||||
```
|
||||
|
|
|
|||
28
call.go
28
call.go
|
|
@ -42,6 +42,7 @@ type Call struct {
|
|||
CallName string
|
||||
Contract *Contract
|
||||
Method string
|
||||
Extend any
|
||||
Inputs []any
|
||||
Outputs any
|
||||
CanFail bool
|
||||
|
|
@ -67,6 +68,18 @@ func (call *Call) Name(name string) *Call {
|
|||
return call
|
||||
}
|
||||
|
||||
func (call *Call) SetExtend(ext any) *Call {
|
||||
call.Extend = ext
|
||||
return call
|
||||
}
|
||||
|
||||
func (call *Call) UnpackResult() []interface{} {
|
||||
if call.Outputs == nil {
|
||||
return nil
|
||||
}
|
||||
return call.Outputs.([]interface{})
|
||||
}
|
||||
|
||||
// AllowFailure sets if the call is allowed to fail. This helps avoiding a revert
|
||||
// when one of the calls in the array fails.
|
||||
func (call *Call) AllowFailure() *Call {
|
||||
|
|
@ -76,19 +89,24 @@ func (call *Call) AllowFailure() *Call {
|
|||
|
||||
// Unpack unpacks and converts EVM outputs and sets struct fields.
|
||||
func (call *Call) Unpack(b []byte) error {
|
||||
out, err := call.Contract.ABI.Unpack(call.Method, b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unpack '%s' outputs: %v", call.Method, err)
|
||||
}
|
||||
if call.Outputs == nil {
|
||||
call.Outputs = out
|
||||
return nil
|
||||
}
|
||||
|
||||
t := reflect.ValueOf(call.Outputs)
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("outputs type is not a struct")
|
||||
}
|
||||
|
||||
out, err := call.Contract.ABI.Unpack(call.Method, b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unpack '%s' outputs: %v", call.Method, err)
|
||||
}
|
||||
|
||||
fieldCount := t.NumField()
|
||||
for i := 0; i < fieldCount; i++ {
|
||||
field := t.Field(i)
|
||||
|
|
|
|||
20
caller.go
20
caller.go
|
|
@ -3,6 +3,7 @@ package multicall
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
|
|
@ -47,6 +48,19 @@ func Dial(ctx context.Context, rawUrl string, multicallAddr ...string) (*Caller,
|
|||
|
||||
// Call makes multicalls.
|
||||
func (caller *Caller) Call(opts *bind.CallOpts, calls ...*Call) ([]*Call, error) {
|
||||
return caller.calls(opts, calls...)
|
||||
}
|
||||
|
||||
func (caller *Caller) CallSingle(opts *bind.CallOpts, call *Call) (*Call, error) {
|
||||
|
||||
calls, err := caller.calls(opts, call)
|
||||
if err != nil {
|
||||
return call, fmt.Errorf("CallSingle failed: %v", err)
|
||||
}
|
||||
return calls[0], nil
|
||||
}
|
||||
|
||||
func (caller *Caller) calls(opts *bind.CallOpts, calls ...*Call) ([]*Call, error) {
|
||||
var multiCalls []contract_multicall.Multicall3Call3
|
||||
|
||||
for i, call := range calls {
|
||||
|
|
@ -70,6 +84,10 @@ func (caller *Caller) Call(opts *bind.CallOpts, calls ...*Call) ([]*Call, error)
|
|||
call := calls[i] // index always matches
|
||||
call.Failed = !result.Success
|
||||
if err := call.Unpack(result.ReturnData); err != nil {
|
||||
if call.CanFail {
|
||||
log.Println(fmt.Errorf("failed to unpack call outputs at index [%d]: %v", i, err))
|
||||
continue
|
||||
}
|
||||
return calls, fmt.Errorf("failed to unpack call outputs at index [%d]: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +104,7 @@ func (caller *Caller) CallChunked(opts *bind.CallOpts, chunkSize int, cooldown t
|
|||
time.Sleep(cooldown)
|
||||
}
|
||||
|
||||
chunk, err := caller.Call(opts, chunk...)
|
||||
chunk, err := caller.calls(opts, chunk...)
|
||||
if err != nil {
|
||||
return calls, fmt.Errorf("call chunk [%d] failed: %v", i, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue