Skip to content

Commit

Permalink
feat: client sdk for eoa-paymaster (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
reneecok authored Sep 5, 2024
1 parent be9954e commit e8d8c35
Show file tree
Hide file tree
Showing 9 changed files with 685 additions and 1 deletion.
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
go.work.sum

.idea
157 changes: 156 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,157 @@
# megafuel-go-sdk
Golang SDK for the eoa-paymaster clients

This Golang SDK is thin wrapper of MegaFuel clients, offering a streamlined interface to interact with the [MegaFuel](https://docs.nodereal.io/docs/megafuel-overview).

## Network Endpoint

| Network | [Paymaster]( https://docs.nodereal.io/reference/pm-issponsorable) | [Sponsor](https://docs.nodereal.io/reference/pm-addtowhitelist) |
|:-------------:|:-------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:|
| BSC mainnet | https://bsc-megafuel.nodereal.io | https://open-platform.nodereal.io/{YOUR_API_KEY}/megafuel |
| BSC testnet | https://bsc-megafuel-testnet.nodereal.io | https://open-platform.nodereal.io/{YOUR_API_KEY}/megafuel-testnet |
| opBNB mainnet | https://opbnb-megafuel.nodereal.io | https://open-platform.nodereal.io/{YOUR_API_KEY}/megafuel |
| opBNB testnet | https://opbnb-megafuel-testnet.nodereal.io | https://open-platform.nodereal.io/{YOUR_API_KEY}/megafuel-testnet |


## Quick Start

1. Install dependency

```shell
$ go get -u github.com/nodereal/megafuel-go-sdk
```

2. Example

```go
package main

import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gofrs/uuid"
"github.com/node-real/megafuel-go-sdk/pkg/paymasterclient"
"github.com/node-real/megafuel-go-sdk/pkg/sponsorclient"
)

const PAYMASTER_URL = "https://bsc-megafuel-testnet.nodereal.io"
const CHAIN_URL = "https://data-seed-prebsc-2-s1.binance.org:8545/"
const SPONSOR_URL = "https://open-platform.nodereal.io/<api-key>/megafuel-testnet"

const POLICY_UUID = "a2381160-xxxx-xxxx-xxxxceca86556834"
const RECIPIENT_ADDRESS = "0x8e9......3EA2"
const YOUR_PRIVATE_KEY = "69......929"

func main() {
sponsorClient, err := sponsorclient.New(context.Background(), SPONSOR_URL)
if err != nil {
panic(err)
}

policyUUID, _ := uuid.FromString(POLICY_UUID)

success, err := sponsorClient.AddToWhitelist(context.Background(), sponsorclient.WhiteListArgs{
PolicyUUID: policyUUID,
WhitelistType: sponsorclient.ToAccountWhitelist,
Values: []string{RECIPIENT_ADDRESS},
})
if err != nil || !success {
panic("failed to add token contract whitelist")
}

// Connect to an Ethereum node (for transaction assembly)
client, err := ethclient.Dial(CHAIN_URL)
if err != nil {
log.Fatalf("Failed to connect to the Ethereum network: %v", err)
}
// Create a PaymasterClient (for transaction sending)
paymasterClient, err := paymasterclient.New(context.Background(), PAYMASTER_URL)
if err != nil {
log.Fatalf("Failed to create PaymasterClient: %v", err)
}

// Load your private key
privateKey, err := crypto.HexToECDSA(YOUR_PRIVATE_KEY)
if err != nil {
log.Fatalf("Failed to load private key: %v", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("Error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

// Get the latest nonce for the from address
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatalf("Failed to get nonce: %v", err)
}

toAddress := common.HexToAddress(RECIPIENT_ADDRESS)

tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
GasPrice: big.NewInt(0),
Gas: 21000,
To: &toAddress,
Value: big.NewInt(1e18),
})

// Convert to Transaction struct for IsSponsorable check
gasLimit := tx.Gas()
sponsorableTx := paymasterclient.TransactionArgs{
To: &toAddress,
From: fromAddress,
Value: (*hexutil.Big)(big.NewInt(1e18)),
Gas: (*hexutil.Uint64)(&gasLimit),
}

// Check if the transaction is sponsorable
sponsorableInfo, err := paymasterClient.IsSponsorable(context.Background(), sponsorableTx)
if err != nil {
log.Fatalf("Error checking sponsorable status: %v", err)
}

fmt.Printf("Sponsorable Information:\n%+v\n", sponsorableInfo)

if sponsorableInfo.Sponsorable {
// Get the chain ID
chainID, err := client.ChainID(context.Background())
if err != nil {
log.Fatalf("Failed to get chain ID: %v", err)
}

// Sign the transaction
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatalf("Failed to sign transaction: %v", err)
}

txInput, err := signedTx.MarshalBinary()
if err != nil {
log.Fatalf("Failed to marshal transaction: %v", err)
}

// Send the transaction using PaymasterClient
_, err = paymasterClient.SendRawTransaction(context.Background(), txInput)
if err != nil {
log.Fatalf("Failed to send sponsorable transaction: %v", err)
}
fmt.Printf("Sponsorable transaction sent: %s\n", signedTx.Hash())
} else {
fmt.Println("Transaction is not sponsorable. You may need to send it as a regular transaction.")
}
}
```

More examples can be found in the [examples](https://github.com/node-real/megafuel-client-example).

24 changes: 24 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module github.com/node-real/megafuel-go-sdk

go 1.21

require (
github.com/ethereum/go-ethereum v1.14.8
github.com/gofrs/uuid v4.3.0+incompatible
)

require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/holiman/uint256 v1.3.1 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.20.0 // indirect
)
63 changes: 63 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88=
github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ1VTIig=
github.com/ethereum/go-ethereum v1.14.8/go.mod h1:TJhyuDq0JDppAkFXgqjwpdlQApywnu/m10kFPxh8vvs=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs=
github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
115 changes: 115 additions & 0 deletions pkg/paymasterclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package paymasterclient

import (
"context"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gofrs/uuid"
)

type Client interface {
// ChainID returns the chain ID of the connected domain
ChainID(ctx context.Context) (string, error)
// IsSponsorable checks if a transaction is sponsorable
IsSponsorable(ctx context.Context, tx TransactionArgs) (*IsSponsorableResponse, error)
// SendRawTransaction sends a raw transaction to the connected domain
SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error)
// GetGaslessTransactionByHash returns a gasless transaction by hash
GetGaslessTransactionByHash(ctx context.Context, txHash common.Hash) (userTx *TransactionResponse, err error)

// GetSponsorTxByTxHash returns a sponsor transaction by hash
GetSponsorTxByTxHash(ctx context.Context, txHash common.Hash) (sponsorTx *SponsorTx, err error)
// GetSponsorTxByBundleUUID returns a sponsor transaction by bundle UUID
GetSponsorTxByBundleUUID(ctx context.Context, bundleUUID uuid.UUID) (sponsorTx *SponsorTx, err error)
// GetBundleByUUID returns a bundle by UUID
GetBundleByUUID(ctx context.Context, bundleUUID uuid.UUID) (bundle *Bundle, err error)
// GetTransactionCount returns the number of transactions sent from an address
GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error)
}

type client struct {
c *rpc.Client
}

func New(ctx context.Context, url string, options ...rpc.ClientOption) (Client, error) {
c, err := rpc.DialOptions(ctx, url, options...)
if err != nil {
return nil, err
}

return &client{c}, nil
}

func (c *client) ChainID(ctx context.Context) (string, error) {
var result string
err := c.c.CallContext(ctx, &result, "eth_chainId")
if err != nil {
return "", err
}
return result, nil
}

func (c *client) IsSponsorable(ctx context.Context, tx TransactionArgs) (*IsSponsorableResponse, error) {
var result IsSponsorableResponse
err := c.c.CallContext(ctx, &result, "pm_isSponsorable", tx)
if err != nil {
return nil, err
}
return &result, nil
}

func (c *client) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
var result common.Hash
err := c.c.CallContext(ctx, &result, "eth_sendRawTransaction", input)
if err != nil {
return common.Hash{}, err
}
return result, nil
}

func (c *client) GetGaslessTransactionByHash(ctx context.Context, txHash common.Hash) (*TransactionResponse, error) {
var result TransactionResponse
err := c.c.CallContext(ctx, &result, "eth_getGaslessTransactionByHash", txHash)
if err != nil {
return nil, err
}
return &result, nil
}

func (c *client) GetSponsorTxByTxHash(ctx context.Context, txHash common.Hash) (*SponsorTx, error) {
var result SponsorTx
err := c.c.CallContext(ctx, &result, "pm_getSponsorTxByTxHash", txHash)
if err != nil {
return nil, err
}
return &result, nil
}

func (c *client) GetSponsorTxByBundleUUID(ctx context.Context, bundleUUID uuid.UUID) (*SponsorTx, error) {
var result SponsorTx
err := c.c.CallContext(ctx, &result, "pm_getSponsorTxByBundleUuid", bundleUUID)
if err != nil {
return nil, err
}
return &result, nil
}

func (c *client) GetBundleByUUID(ctx context.Context, bundleUUID uuid.UUID) (*Bundle, error) {
var result Bundle
err := c.c.CallContext(ctx, &result, "pm_getBundleByUuid", bundleUUID)
if err != nil {
return nil, err
}
return &result, nil
}

func (c *client) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
var result hexutil.Uint64
err := c.c.CallContext(ctx, &result, "eth_getTransactionCount", address, blockNrOrHash)
if err != nil {
return nil, err
}
return &result, nil
}
Loading

0 comments on commit e8d8c35

Please sign in to comment.