-
Notifications
You must be signed in to change notification settings - Fork 0
/
bundler.go
147 lines (123 loc) · 4.34 KB
/
bundler.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package voltasdk
import (
"context"
"encoding/json"
"fmt"
"github.com/go-resty/resty/v2"
"math/big"
)
type BundlerClient interface {
SendUserOp(ctx context.Context, userOp *UserOperation) (userOpHash string, err error)
EstimateUserOpGas(ctx context.Context, userOperation *UserOperation) (preVerificationGas, verificationGas, callGasLimit *big.Int, err error)
GetUserOpReceipt(ctx context.Context, userOpHash string) (GetUserOpReceiptResponse, error)
}
func NewBundlerClient(chain Blockchain) (BundlerClient, error) {
return newBundlerClient(chain)
}
func NewBundlerClientWithUrl(bundlerUrl string) BundlerClient {
return &bundlerClient{
r: resty.New().SetBaseURL(bundlerUrl),
}
}
type bundlerClient struct {
r *resty.Client
}
func newBundlerClient(chain Blockchain) (*bundlerClient, error) {
if !chain.IsValid() {
return nil, ErrInvalidBlockchain
}
return &bundlerClient{
r: resty.New().SetBaseURL(chain.BundlerURL()),
}, nil
}
func (b bundlerClient) SendUserOp(ctx context.Context, userOp *UserOperation) (string, error) {
params := []any{userOp, userOp.EntryPointAddress}
response, err := b.doJSONRPCRequest(ctx, "eth_sendUserOperation", params)
if err != nil {
return "", err
}
var userOpHash string
return userOpHash, json.Unmarshal(response.Result, &userOpHash)
}
type estimateUserOpGasResult struct {
PreVerificationGas *big.Int `json:"PreVerificationGas"`
VerificationGas *big.Int `json:"VerificationGas"`
CallGasLimit *big.Int `json:"CallGasLimit"`
}
func (b bundlerClient) EstimateUserOpGas(ctx context.Context, userOperation *UserOperation) (preVerificationGas,
verificationGas, callGasLimit *big.Int, err error) {
params := []any{userOperation, userOperation.EntryPointAddress}
response, err := b.doJSONRPCRequest(ctx, "eth_estimateUserOperationGas", params)
if err != nil {
return nil, nil, nil, err
}
var result estimateUserOpGasResult
if err = json.Unmarshal(response.Result, &result); err != nil {
return nil, nil, nil, err
}
return result.PreVerificationGas, result.VerificationGas, result.CallGasLimit, nil
}
func (b bundlerClient) GetUserOpReceipt(ctx context.Context, userOpHash string) (out GetUserOpReceiptResponse, err error) {
params := []any{userOpHash}
response, err := b.doJSONRPCRequest(ctx, "eth_getUserOperationReceipt", params)
if err != nil {
return out, err
}
return out, json.Unmarshal(response.Result, &out)
}
type jsonrpcMessage struct {
Version string `json:"jsonrpc"`
Method string `json:"method"`
Params any `json:"params"`
Result json.RawMessage `json:"result,omitempty"`
Error json.RawMessage `json:"error,omitempty"`
ID int `json:"id,omitempty"`
}
type jsonrpcErrorMessage struct {
Code int `json:"code,omitempty"`
Data any `json:"data,omitempty"`
Message string `json:"message,omitempty"`
}
func (b bundlerClient) doJSONRPCRequest(ctx context.Context, method string, params interface{}) (jsonrpcMessage, error) {
var (
request = jsonrpcMessage{
Version: "2.0",
Method: method,
Params: params,
ID: 1,
}
response jsonrpcMessage
)
// Do request, check error messages and status code
resp, err := b.r.R().
SetContext(ctx).
SetBody(request).
SetResult(&response).
SetError(&response).
Post("/")
if err != nil {
return jsonrpcMessage{}, err
}
if len(response.Error) > 0 {
// Switch on first byte to determine if the error is an object or string
// NOTE: this was necessary for Stackup's hosted bundler. We should check if this is still necessary
// when we run our own bundler
switch response.Error[0] {
case '{':
var errorMessage jsonrpcErrorMessage
if err = json.Unmarshal(response.Error, &errorMessage); err != nil {
return jsonrpcMessage{}, fmt.Errorf("failed to unmarshal error message: %w", err)
}
return jsonrpcMessage{}, fmt.Errorf("error response: %s statusCode: %d", errorMessage.Message, resp.StatusCode())
case '"':
var errString string
if err = json.Unmarshal(response.Error, &errString); err != nil {
return jsonrpcMessage{}, fmt.Errorf("failed to unmarshal error message: %w", err)
}
return jsonrpcMessage{}, fmt.Errorf("error response: %s statusCode: %d", errString, resp.StatusCode())
default:
return jsonrpcMessage{}, fmt.Errorf("unknown error response: %s statusCode: %d", response.Error, resp.StatusCode())
}
}
return response, nil
}