Skip to content

Commit

Permalink
Alchemy API reasonably integrated
Browse files Browse the repository at this point in the history
  • Loading branch information
kubasiemion committed Sep 10, 2024
1 parent 1b6e992 commit 7a91bbb
Show file tree
Hide file tree
Showing 18 changed files with 1,313 additions and 98 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/san-lab/go4337
go 1.22.3

require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/ethereum/go-ethereum v1.14.5
github.com/manifoldco/promptui v0.9.0
)
Expand All @@ -12,7 +13,6 @@ require (
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.12.1 // indirect
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
Expand Down
83 changes: 77 additions & 6 deletions rpccalls/alchemy.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package rpccalls

import (
"github.com/san-lab/go4337/state"
"github.com/san-lab/go4337/userop"
)

// USerOpV6 - standard

type AlchemyV7UserOp struct {
Expand All @@ -20,12 +25,6 @@ type AlchemyV7UserOp struct {
PaymasterData string `json:"paymasterData"`
}

type AlchemyEstimateGasCostResponse struct {
PreVerificationGas string `json:"preVerificationGas"`
CallGasLimit string `json:"callGasLimit"`
VerificationGasLimit string `json:"verificationGasLimit"`
}

/*
eth_sendUserOperation
Expand All @@ -42,3 +41,75 @@ eth_supportedEntryPoints
Returns a list of Entrypoint contract addresses supported by the bunder endpoints.
*/

func Alchemy_requestGasAndPaymasterAndData(url, key, policyID, entrypoint, dummysignature string,
usop userop.UserOperation, overrides *AlchemyOverrides) (*AlchemyGasAndPaymasterDataResult, error) {
ar := &APIRequest{
ID: 4338,
Jsonrpc: "2.0",
Method: "alchemy_requestGasAndPaymasterAndData",
Params: []interface{}{AlchemyReqGasAndPMandDataParams{policyID, entrypoint, dummysignature, usop.ToUserOpForApiV6(), overrides}},
}
state.Log("Alchemy Overrides:", overrides)
agapad := &AlchemyGasAndPaymasterDataResult{}
_, err := ApiCall(url, key, ar, agapad)
return agapad, err
}

type AlchemyGasAndPaymasterDataResult struct {
PaymasterAndData string `json:"paymasterAndData"`
CallGasLimit string `json:"callGasLimit"`
VerificationGasLimit string `json:"verificationGasLimit"`
MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"`
MaxFeePerGas string `json:"maxFeePerGas"`
PreVerificationGas string `json:"preVerificationGas"`
}

type AlchemyReqGasAndPMandDataParams struct {
PolicyId string `json:"policyId"`
EntryPoint string `json:"entryPoint"`
DummySignature string `json:"dummySignature"`
UserOperation *userop.UserOpForApiV6 `json:"userOperation"`
Overrides *AlchemyOverrides `json:"overrides"`
}

func Alchemy_requestPaymasterAndData(url, key, policyID, entrypoint string,
usop userop.UserOperation) (*PMandDataResult, error) {
ar := &APIRequest{
ID: 4337,
Jsonrpc: "2.0",
Method: "alchemy_requestPaymasterAndData",
Params: []interface{}{AlchemyReqPMandDatParams{policyID, entrypoint, usop.ToUserOpForApiV6()}},
}
pmad := &PMandDataResult{}
_, err := ApiCall(url, key, ar, pmad)
return pmad, err

}

type AlchemyReqPMandDatParams struct {
PolicyId string `json:"policyId"`
EntryPoint string `json:"entryPoint"`
UserOperation *userop.UserOpForApiV6 `json:"userOperation"`
}

type AlchemyOverrides struct {
/*
{
"maxFeePerGas": "hex string" | { "multiplier": number },
"maxPriorityFeePerGas": "hex string" | { "multiplier": number },
"callGasLimit": "hex string" | { "multiplier": number },
"verificationGasLimit": "hex string" | { "multiplier": number },
"preVerificationGas": "hex string" | { "multiplier": number },
}
*/
MaxFeePerGas interface{} `json:"maxFeePerGas"`
MaxPriorityFeePerGas interface{} `json:"maxPriorityFeePerGas"`
CallGasLimit interface{} `json:"callGasLimit"`
VerificationGasLimit interface{} `json:"verificationGasLimit"`
PreVerificationGas interface{} `json:"preVerificationGas"`
}

type AlchemyOverrideMultiplier struct {
Multiplier float64 `json:"multiplier"`
}
63 changes: 42 additions & 21 deletions rpccalls/apicalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,46 @@ import (
"net/http"

"encoding/json"

"github.com/san-lab/go4337/state"
)

func POSTCall(url, key string, data []byte) (result []byte, aerr *APIError, err error) {
func POSTCall(url, key string, data []byte) (result []byte, err error) {
client := http.Client{}
url = url + "/" + key
if len(key) > 0 {
url = url + "/" + key
}
buf := bytes.NewBuffer(data)
//fmt.Println("POSTing", url, "with", string(data))
state.Log("POSTing", url, "with", string(data))
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return nil, nil, err
return nil, err
}
req.Header.Set("accept", "application/json")
req.Header.Set("content-type", "application/json")
req.Body = io.NopCloser(buf)
resp, err := client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("could not do request: %v", err)
return nil, fmt.Errorf("could not do request: %v", err)
}
defer resp.Body.Close()
resbts, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("could not read response: %v", err)
}
aresp := &APIRPCResponse{}
err = json.Unmarshal(resbts, aresp)
if err != nil {
return nil, nil, fmt.Errorf("could not unmarshal response: %v", err, string(resbts))
return nil, fmt.Errorf("could not read response: %w", err)
}
if aresp.Error.Code != 0 {
return nil, &aresp.Error, nil
}
return aresp.Result, nil, nil
state.Log("RAW Response:", string(resbts))
return resbts, nil
}

func ApiFreeHandCall(url, key, methodTemplate string, params ...interface{}) (result []byte, aerr *APIError, err error) {
func ApiFreeHandCall(url, key, methodTemplate string, params ...interface{}) (result []byte, err error) {
sparams := make([]any, len(params))
for i, p := range params {
if p == nil {
continue
}
sparams[i], err = json.Marshal(p)
if err != nil {
return nil, nil, fmt.Errorf("could not marshal param: %v", err)
return nil, fmt.Errorf("could not marshal param: %v", err)
}
}
calldat := fmt.Sprintf(methodTemplate, sparams...)
Expand All @@ -55,14 +55,31 @@ func ApiFreeHandCall(url, key, methodTemplate string, params ...interface{}) (re
}

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

return POSTCall(url, key, data)

bt, err := POSTCall(url, key, data)
if err != nil {
return nil, fmt.Errorf("Error in POSTCall: %v", err)
}
aresp := &APIRPCResponse{}
err = json.Unmarshal(bt, aresp)
if err != nil {
return nil, fmt.Errorf("could not unmarshal response: %w", err)
}
if aresp.Error.Code != 0 {
return nil, &aresp.Error
}
if result != nil {
err = json.Unmarshal(aresp.Result, result)
if err != nil {
return nil, fmt.Errorf("could not unmarshal result: %w", err)
}
}
return aresp.Result, nil
}

type APIRequest struct {
Expand All @@ -88,3 +105,7 @@ type APIError struct {
Data json.RawMessage `json:"data"`
Message string `json:"message"`
}

func (ae *APIError) Error() string {
return fmt.Sprintf("APIError: %s (%d)", ae.Message, ae.Code)
}
144 changes: 144 additions & 0 deletions rpccalls/eth_calls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package rpccalls

import (
"fmt"
"strconv"

"github.com/san-lab/go4337/userop"
)

// providers
const AlchemyProvider = "alchemy"
const StackUpProvider = "stackup"
const PimlicoProvider = "pimlico"
const BiconomyProvider = "biconomy"

func Eth_sendUserOperation(url, key string, usop *userop.UserOperation, entrypoint string, entrypointVersion int, provider string) (*string, error) {
var usopdata interface{}
switch entrypointVersion {
case 6:
usopdata = usop.ToUserOpForApiV6()
case 7:
usopdata = usop.ToUserOpForApiV6()
default:
return nil, fmt.Errorf("Unsupported entrypoint version: %d", entrypointVersion)
}

ar := &APIRequest{
ID: 1,
Jsonrpc: "2.0",
Method: "eth_sendUserOperation",
Params: []interface{}{usopdata, entrypoint},
}
result := new(string)
_, err := ApiCall(url, key, ar, result)
if err != nil {
return nil, fmt.Errorf("API Call error: %w", err)
}
return result, nil

}

func Eth_estimateUserOperationGas(url, key string, usop *userop.UserOperation, entrypoint string, entrypointVersion int, provider string) (*EthEstimateUserOperationGasResult, error) {
var usopdata interface{}
switch entrypointVersion {
case 6:
usopdata = usop.ToUserOpForApiV6()
case 7:
usopdata = usop.ToUserOpForApiV6()
default:
return nil, fmt.Errorf("Unsupported entrypoint version: %d", entrypointVersion)
}

ar := &APIRequest{
ID: 1,
Jsonrpc: "2.0",
Method: "eth_estimateUserOperationGas",
Params: []interface{}{usopdata, entrypoint},
}
var result interface{}
var finalResult = &EthEstimateUserOperationGasResult{}
switch provider {
case AlchemyProvider, PimlicoProvider:
result = &AlchemyEstimateGasCostResponse{}
default:
result = finalResult
}
_, err := ApiCall(url, key, ar, result)
if err != nil {
return nil, fmt.Errorf("API Call error: %w", err)
}

//Transcode
switch provider {
case AlchemyProvider, PimlicoProvider:
finalResult.CallGasLimit, _ = strconv.ParseUint(result.(*AlchemyEstimateGasCostResponse).CallGasLimit[2:], 16, 64)
finalResult.VerificationGasLimit, _ = strconv.ParseUint(result.(*AlchemyEstimateGasCostResponse).VerificationGasLimit[2:], 16, 64)
finalResult.PreVerificationGas, _ = strconv.ParseUint(result.(*AlchemyEstimateGasCostResponse).PreVerificationGas[2:], 16, 64)
default:
finalResult = result.(*EthEstimateUserOperationGasResult)
}

return finalResult, nil

}

type EthEstimateUserOperationGasResult struct {
CallGasLimit uint64 `json:"callGasLimit"`
VerificationGasLimit uint64 `json:"verificationGasLimit"`
PreVerificationGas uint64 `json:"preVerificationGas"`
ValidUntil string `json:"validUntil"`
ValidAfter string `json:"validAfter"`
MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"`
MaxFeePerGas string `json:"maxFeePerGas"`
}

type AlchemyEstimateGasCostResponse struct {
PreVerificationGas string `json:"preVerificationGas"`
CallGasLimit string `json:"callGasLimit"`
VerificationGasLimit string `json:"verificationGasLimit"`
}

func Eth_getUserOperationByHash(url, key string, hash string, provider string) ([]byte, error) {
ar := &APIRequest{
ID: 1,
Jsonrpc: "2.0",
Method: "eth_getUserOperationByHash",
Params: []interface{}{hash},
}
bt, err := ApiCall(url, key, ar, nil)
if err != nil {
return nil, fmt.Errorf("API Call error: %w", err)
}
return bt, nil

}

func Eth_getUserOperationReceipt(url, key string, hash string, provider string) ([]byte, error) {
//fmt.Println("eth_getUserOperationReceipt not implemented")
ar := &APIRequest{
ID: 1,
Jsonrpc: "2.0",
Method: "eth_getUserOperationReceipt",
Params: []interface{}{hash},
}

return ApiCall(url, key, ar, nil)

}

func Eth_supportedEntryPoints(url, key string) (*[]string, error) {
ar := &APIRequest{
ID: 1,
Jsonrpc: "2.0",
Method: "eth_supportedEntryPoints",
Params: []interface{}{},
}
result := &[]string{}
_, err := ApiCall(url, key, ar, result)
if err != nil {
return nil, fmt.Errorf("API Call error: %w", err)
}
return result, nil

}
Loading

0 comments on commit 7a91bbb

Please sign in to comment.