/
handleops.go
133 lines (115 loc) · 3.67 KB
/
handleops.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package transaction
import (
"context"
"errors"
"math"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts"
"github.com/stackup-wallet/stackup-bundler/pkg/signer"
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
)
// Opts contains all the fields required for submitting a transaction to call HandleOps on the EntryPoint
// contract.
type Opts struct {
// Options for the network
EOA *signer.EOA
Eth *ethclient.Client
ChainID *big.Int
// Options for the EntryPoint
EntryPoint common.Address
Batch []*userop.UserOperation
Beneficiary common.Address
// Options for the EOA transaction
BaseFee *big.Int
Tip *big.Int
GasPrice *big.Int
GasLimit uint64
NoSend bool
WaitTimeout time.Duration
}
func toAbiType(batch []*userop.UserOperation) []entrypoint.UserOperation {
ops := []entrypoint.UserOperation{}
for _, op := range batch {
ops = append(ops, entrypoint.UserOperation(*op))
}
return ops
}
// EstimateHandleOpsGas returns a gas estimate required to call handleOps() with a given batch. A failed call
// will return the cause of the revert.
func EstimateHandleOpsGas(opts *Opts) (gas uint64, revert *reverts.FailedOpRevert, err error) {
ep, err := entrypoint.NewEntrypoint(opts.EntryPoint, opts.Eth)
if err != nil {
return 0, nil, err
}
auth, err := bind.NewKeyedTransactorWithChainID(opts.EOA.PrivateKey, opts.ChainID)
if err != nil {
return 0, nil, err
}
auth.GasLimit = math.MaxUint64
auth.NoSend = true
tx, err := ep.HandleOps(auth, toAbiType(opts.Batch), opts.Beneficiary)
if err != nil {
return 0, nil, err
}
est, err := opts.Eth.EstimateGas(context.Background(), ethereum.CallMsg{
From: opts.EOA.Address,
To: tx.To(),
Gas: tx.Gas(),
GasPrice: tx.GasPrice(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
})
if err != nil {
revert, err := reverts.NewFailedOp(err)
if err != nil {
return 0, nil, err
}
return 0, revert, nil
}
return est, nil, nil
}
// HandleOps submits a transaction to send a batch of UserOperations to the EntryPoint.
func HandleOps(opts *Opts) (txn *types.Transaction, err error) {
ep, err := entrypoint.NewEntrypoint(opts.EntryPoint, opts.Eth)
if err != nil {
return nil, err
}
auth, err := bind.NewKeyedTransactorWithChainID(opts.EOA.PrivateKey, opts.ChainID)
if err != nil {
return nil, err
}
auth.GasLimit = opts.GasLimit
auth.NoSend = opts.NoSend
nonce, err := opts.Eth.NonceAt(context.Background(), opts.EOA.Address, nil)
if err != nil {
return nil, err
}
auth.Nonce = big.NewInt(int64(nonce))
if opts.BaseFee != nil && opts.Tip != nil {
auth.GasTipCap = SuggestMeanGasTipCap(opts.Tip, opts.Batch)
auth.GasFeeCap = SuggestMeanGasFeeCap(opts.BaseFee, opts.Tip, opts.Batch)
} else if opts.GasPrice != nil {
auth.GasPrice = SuggestMeanGasPrice(opts.GasPrice, opts.Batch)
} else {
return nil, errors.New("transaction: either the dynamic or legacy gas fees must be set")
}
txn, err = ep.HandleOps(auth, toAbiType(opts.Batch), opts.Beneficiary)
if err != nil {
return nil, err
} else if opts.WaitTimeout == 0 || opts.NoSend {
// Don't wait for transaction to be included. All userOps in the current batch will be dropped
// regardless of the transaction status.
return txn, nil
}
return Wait(txn, opts.Eth, opts.WaitTimeout)
}