Skip to content

Commit 7a91bbb

Browse files
committed
Alchemy API reasonably integrated
1 parent 1b6e992 commit 7a91bbb

18 files changed

Lines changed: 1313 additions & 98 deletions

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/san-lab/go4337
33
go 1.22.3
44

55
require (
6+
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
67
github.com/ethereum/go-ethereum v1.14.5
78
github.com/manifoldco/promptui v0.9.0
89
)
@@ -12,7 +13,6 @@ require (
1213
github.com/StackExchange/wmi v1.2.1 // indirect
1314
github.com/bits-and-blooms/bitset v1.10.0 // indirect
1415
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
15-
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
1616
github.com/consensys/bavard v0.1.13 // indirect
1717
github.com/consensys/gnark-crypto v0.12.1 // indirect
1818
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect

rpccalls/alchemy.go

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package rpccalls
22

3+
import (
4+
"github.com/san-lab/go4337/state"
5+
"github.com/san-lab/go4337/userop"
6+
)
7+
38
// USerOpV6 - standard
49

510
type AlchemyV7UserOp struct {
@@ -20,12 +25,6 @@ type AlchemyV7UserOp struct {
2025
PaymasterData string `json:"paymasterData"`
2126
}
2227

23-
type AlchemyEstimateGasCostResponse struct {
24-
PreVerificationGas string `json:"preVerificationGas"`
25-
CallGasLimit string `json:"callGasLimit"`
26-
VerificationGasLimit string `json:"verificationGasLimit"`
27-
}
28-
2928
/*
3029
3130
eth_sendUserOperation
@@ -42,3 +41,75 @@ eth_supportedEntryPoints
4241
Returns a list of Entrypoint contract addresses supported by the bunder endpoints.
4342
4443
*/
44+
45+
func Alchemy_requestGasAndPaymasterAndData(url, key, policyID, entrypoint, dummysignature string,
46+
usop userop.UserOperation, overrides *AlchemyOverrides) (*AlchemyGasAndPaymasterDataResult, error) {
47+
ar := &APIRequest{
48+
ID: 4338,
49+
Jsonrpc: "2.0",
50+
Method: "alchemy_requestGasAndPaymasterAndData",
51+
Params: []interface{}{AlchemyReqGasAndPMandDataParams{policyID, entrypoint, dummysignature, usop.ToUserOpForApiV6(), overrides}},
52+
}
53+
state.Log("Alchemy Overrides:", overrides)
54+
agapad := &AlchemyGasAndPaymasterDataResult{}
55+
_, err := ApiCall(url, key, ar, agapad)
56+
return agapad, err
57+
}
58+
59+
type AlchemyGasAndPaymasterDataResult struct {
60+
PaymasterAndData string `json:"paymasterAndData"`
61+
CallGasLimit string `json:"callGasLimit"`
62+
VerificationGasLimit string `json:"verificationGasLimit"`
63+
MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"`
64+
MaxFeePerGas string `json:"maxFeePerGas"`
65+
PreVerificationGas string `json:"preVerificationGas"`
66+
}
67+
68+
type AlchemyReqGasAndPMandDataParams struct {
69+
PolicyId string `json:"policyId"`
70+
EntryPoint string `json:"entryPoint"`
71+
DummySignature string `json:"dummySignature"`
72+
UserOperation *userop.UserOpForApiV6 `json:"userOperation"`
73+
Overrides *AlchemyOverrides `json:"overrides"`
74+
}
75+
76+
func Alchemy_requestPaymasterAndData(url, key, policyID, entrypoint string,
77+
usop userop.UserOperation) (*PMandDataResult, error) {
78+
ar := &APIRequest{
79+
ID: 4337,
80+
Jsonrpc: "2.0",
81+
Method: "alchemy_requestPaymasterAndData",
82+
Params: []interface{}{AlchemyReqPMandDatParams{policyID, entrypoint, usop.ToUserOpForApiV6()}},
83+
}
84+
pmad := &PMandDataResult{}
85+
_, err := ApiCall(url, key, ar, pmad)
86+
return pmad, err
87+
88+
}
89+
90+
type AlchemyReqPMandDatParams struct {
91+
PolicyId string `json:"policyId"`
92+
EntryPoint string `json:"entryPoint"`
93+
UserOperation *userop.UserOpForApiV6 `json:"userOperation"`
94+
}
95+
96+
type AlchemyOverrides struct {
97+
/*
98+
{
99+
"maxFeePerGas": "hex string" | { "multiplier": number },
100+
"maxPriorityFeePerGas": "hex string" | { "multiplier": number },
101+
"callGasLimit": "hex string" | { "multiplier": number },
102+
"verificationGasLimit": "hex string" | { "multiplier": number },
103+
"preVerificationGas": "hex string" | { "multiplier": number },
104+
}
105+
*/
106+
MaxFeePerGas interface{} `json:"maxFeePerGas"`
107+
MaxPriorityFeePerGas interface{} `json:"maxPriorityFeePerGas"`
108+
CallGasLimit interface{} `json:"callGasLimit"`
109+
VerificationGasLimit interface{} `json:"verificationGasLimit"`
110+
PreVerificationGas interface{} `json:"preVerificationGas"`
111+
}
112+
113+
type AlchemyOverrideMultiplier struct {
114+
Multiplier float64 `json:"multiplier"`
115+
}

rpccalls/apicalls.go

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,46 @@ import (
77
"net/http"
88

99
"encoding/json"
10+
11+
"github.com/san-lab/go4337/state"
1012
)
1113

12-
func POSTCall(url, key string, data []byte) (result []byte, aerr *APIError, err error) {
14+
func POSTCall(url, key string, data []byte) (result []byte, err error) {
1315
client := http.Client{}
14-
url = url + "/" + key
16+
if len(key) > 0 {
17+
url = url + "/" + key
18+
}
1519
buf := bytes.NewBuffer(data)
16-
//fmt.Println("POSTing", url, "with", string(data))
20+
state.Log("POSTing", url, "with", string(data))
1721
req, err := http.NewRequest("POST", url, nil)
1822
if err != nil {
19-
return nil, nil, err
23+
return nil, err
2024
}
2125
req.Header.Set("accept", "application/json")
2226
req.Header.Set("content-type", "application/json")
2327
req.Body = io.NopCloser(buf)
2428
resp, err := client.Do(req)
2529
if err != nil {
26-
return nil, nil, fmt.Errorf("could not do request: %v", err)
30+
return nil, fmt.Errorf("could not do request: %v", err)
2731
}
2832
defer resp.Body.Close()
2933
resbts, err := io.ReadAll(resp.Body)
3034
if err != nil {
31-
return nil, nil, fmt.Errorf("could not read response: %v", err)
32-
}
33-
aresp := &APIRPCResponse{}
34-
err = json.Unmarshal(resbts, aresp)
35-
if err != nil {
36-
return nil, nil, fmt.Errorf("could not unmarshal response: %v", err, string(resbts))
35+
return nil, fmt.Errorf("could not read response: %w", err)
3736
}
38-
if aresp.Error.Code != 0 {
39-
return nil, &aresp.Error, nil
40-
}
41-
return aresp.Result, nil, nil
37+
state.Log("RAW Response:", string(resbts))
38+
return resbts, nil
4239
}
4340

44-
func ApiFreeHandCall(url, key, methodTemplate string, params ...interface{}) (result []byte, aerr *APIError, err error) {
41+
func ApiFreeHandCall(url, key, methodTemplate string, params ...interface{}) (result []byte, err error) {
4542
sparams := make([]any, len(params))
4643
for i, p := range params {
44+
if p == nil {
45+
continue
46+
}
4747
sparams[i], err = json.Marshal(p)
4848
if err != nil {
49-
return nil, nil, fmt.Errorf("could not marshal param: %v", err)
49+
return nil, fmt.Errorf("could not marshal param: %v", err)
5050
}
5151
}
5252
calldat := fmt.Sprintf(methodTemplate, sparams...)
@@ -55,14 +55,31 @@ func ApiFreeHandCall(url, key, methodTemplate string, params ...interface{}) (re
5555
}
5656

5757
// Returns unparsed "Result" if successful, otherwise nil and either *APIError or error
58-
func ApiCall(url, key string, ar *APIRequest) ([]byte, *APIError, error) {
58+
func ApiCall(url, key string, ar *APIRequest, result interface{}) ([]byte, error) {
5959
data, err := ar.ToJSON()
6060
if err != nil {
61-
return nil, nil, fmt.Errorf("could not marshal APIRequest: %v", err, ar)
61+
return nil, fmt.Errorf("could not marshal APIRequest: %w", err)
6262
}
6363

64-
return POSTCall(url, key, data)
65-
64+
bt, err := POSTCall(url, key, data)
65+
if err != nil {
66+
return nil, fmt.Errorf("Error in POSTCall: %v", err)
67+
}
68+
aresp := &APIRPCResponse{}
69+
err = json.Unmarshal(bt, aresp)
70+
if err != nil {
71+
return nil, fmt.Errorf("could not unmarshal response: %w", err)
72+
}
73+
if aresp.Error.Code != 0 {
74+
return nil, &aresp.Error
75+
}
76+
if result != nil {
77+
err = json.Unmarshal(aresp.Result, result)
78+
if err != nil {
79+
return nil, fmt.Errorf("could not unmarshal result: %w", err)
80+
}
81+
}
82+
return aresp.Result, nil
6683
}
6784

6885
type APIRequest struct {
@@ -88,3 +105,7 @@ type APIError struct {
88105
Data json.RawMessage `json:"data"`
89106
Message string `json:"message"`
90107
}
108+
109+
func (ae *APIError) Error() string {
110+
return fmt.Sprintf("APIError: %s (%d)", ae.Message, ae.Code)
111+
}

rpccalls/eth_calls.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package rpccalls
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/san-lab/go4337/userop"
8+
)
9+
10+
// providers
11+
const AlchemyProvider = "alchemy"
12+
const StackUpProvider = "stackup"
13+
const PimlicoProvider = "pimlico"
14+
const BiconomyProvider = "biconomy"
15+
16+
func Eth_sendUserOperation(url, key string, usop *userop.UserOperation, entrypoint string, entrypointVersion int, provider string) (*string, error) {
17+
var usopdata interface{}
18+
switch entrypointVersion {
19+
case 6:
20+
usopdata = usop.ToUserOpForApiV6()
21+
case 7:
22+
usopdata = usop.ToUserOpForApiV6()
23+
default:
24+
return nil, fmt.Errorf("Unsupported entrypoint version: %d", entrypointVersion)
25+
}
26+
27+
ar := &APIRequest{
28+
ID: 1,
29+
Jsonrpc: "2.0",
30+
Method: "eth_sendUserOperation",
31+
Params: []interface{}{usopdata, entrypoint},
32+
}
33+
result := new(string)
34+
_, err := ApiCall(url, key, ar, result)
35+
if err != nil {
36+
return nil, fmt.Errorf("API Call error: %w", err)
37+
}
38+
return result, nil
39+
40+
}
41+
42+
func Eth_estimateUserOperationGas(url, key string, usop *userop.UserOperation, entrypoint string, entrypointVersion int, provider string) (*EthEstimateUserOperationGasResult, error) {
43+
var usopdata interface{}
44+
switch entrypointVersion {
45+
case 6:
46+
usopdata = usop.ToUserOpForApiV6()
47+
case 7:
48+
usopdata = usop.ToUserOpForApiV6()
49+
default:
50+
return nil, fmt.Errorf("Unsupported entrypoint version: %d", entrypointVersion)
51+
}
52+
53+
ar := &APIRequest{
54+
ID: 1,
55+
Jsonrpc: "2.0",
56+
Method: "eth_estimateUserOperationGas",
57+
Params: []interface{}{usopdata, entrypoint},
58+
}
59+
var result interface{}
60+
var finalResult = &EthEstimateUserOperationGasResult{}
61+
switch provider {
62+
case AlchemyProvider, PimlicoProvider:
63+
result = &AlchemyEstimateGasCostResponse{}
64+
default:
65+
result = finalResult
66+
}
67+
_, err := ApiCall(url, key, ar, result)
68+
if err != nil {
69+
return nil, fmt.Errorf("API Call error: %w", err)
70+
}
71+
72+
//Transcode
73+
switch provider {
74+
case AlchemyProvider, PimlicoProvider:
75+
finalResult.CallGasLimit, _ = strconv.ParseUint(result.(*AlchemyEstimateGasCostResponse).CallGasLimit[2:], 16, 64)
76+
finalResult.VerificationGasLimit, _ = strconv.ParseUint(result.(*AlchemyEstimateGasCostResponse).VerificationGasLimit[2:], 16, 64)
77+
finalResult.PreVerificationGas, _ = strconv.ParseUint(result.(*AlchemyEstimateGasCostResponse).PreVerificationGas[2:], 16, 64)
78+
default:
79+
finalResult = result.(*EthEstimateUserOperationGasResult)
80+
}
81+
82+
return finalResult, nil
83+
84+
}
85+
86+
type EthEstimateUserOperationGasResult struct {
87+
CallGasLimit uint64 `json:"callGasLimit"`
88+
VerificationGasLimit uint64 `json:"verificationGasLimit"`
89+
PreVerificationGas uint64 `json:"preVerificationGas"`
90+
ValidUntil string `json:"validUntil"`
91+
ValidAfter string `json:"validAfter"`
92+
MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"`
93+
MaxFeePerGas string `json:"maxFeePerGas"`
94+
}
95+
96+
type AlchemyEstimateGasCostResponse struct {
97+
PreVerificationGas string `json:"preVerificationGas"`
98+
CallGasLimit string `json:"callGasLimit"`
99+
VerificationGasLimit string `json:"verificationGasLimit"`
100+
}
101+
102+
func Eth_getUserOperationByHash(url, key string, hash string, provider string) ([]byte, error) {
103+
ar := &APIRequest{
104+
ID: 1,
105+
Jsonrpc: "2.0",
106+
Method: "eth_getUserOperationByHash",
107+
Params: []interface{}{hash},
108+
}
109+
bt, err := ApiCall(url, key, ar, nil)
110+
if err != nil {
111+
return nil, fmt.Errorf("API Call error: %w", err)
112+
}
113+
return bt, nil
114+
115+
}
116+
117+
func Eth_getUserOperationReceipt(url, key string, hash string, provider string) ([]byte, error) {
118+
//fmt.Println("eth_getUserOperationReceipt not implemented")
119+
ar := &APIRequest{
120+
ID: 1,
121+
Jsonrpc: "2.0",
122+
Method: "eth_getUserOperationReceipt",
123+
Params: []interface{}{hash},
124+
}
125+
126+
return ApiCall(url, key, ar, nil)
127+
128+
}
129+
130+
func Eth_supportedEntryPoints(url, key string) (*[]string, error) {
131+
ar := &APIRequest{
132+
ID: 1,
133+
Jsonrpc: "2.0",
134+
Method: "eth_supportedEntryPoints",
135+
Params: []interface{}{},
136+
}
137+
result := &[]string{}
138+
_, err := ApiCall(url, key, ar, result)
139+
if err != nil {
140+
return nil, fmt.Errorf("API Call error: %w", err)
141+
}
142+
return result, nil
143+
144+
}

0 commit comments

Comments
 (0)