go-relayer
EIP2771-compatible transaction relayer library for Ethereum. The core of the library is bindings to a compatible forwarder contract.
Components:
cmd/relayer
: example CLI that uses impls/minimal_forwarder
impls
: example implementations of forwarder and recipient contracts, as well as Go
bindings, required interface implementations, and unit tests
examples/mock_recipient
: an implementation of a EIP2771-compatible recipient contract
which can receive calls from a forwarder.
relayer
: functionality to call forwarder contracts
rpc
: rpc server for an end-user to submit txs to, accepts a *relayer.Relayer
Requirements
- go 1.20+
- abigen: can install using
./scripts/install-abigen.sh
Usage
As an application
See cmd/relayer/main.go
for an example app using the impls/gsnforwarder
package.
The app can be built using make build
.
First, install and run ganache using:
ganache --deterministic --accounts=50
Create a key file using a deterministic ganache key
echo "4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" > eth.key
To run and automatically deploy the relayer contract to the ganache network, you can use:
$ ./bin/relayer --deploy
2022-09-24T07:43:32.499-0400 INFO main cmd/main.go:131 starting relayer with ethereum endpoint http://localhost:8545 and chain ID 1337
2022-09-24T07:43:32.541-0400 INFO main cmd/main.go:207 deployed Forwarder.sol to 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
2022-09-24T07:43:32.542-0400 INFO rpc rpc/server.go:62 starting RPC server on http://localhost:7799
By default, the relayer server runs on port 7799
.
Implementing a custom forwarder
See the forwarder examples in impls/
for a full implementation.
There are three main components needed:
- Go bindings to the forwarder contract (generated by abigen)
- implementing the
Forwarder
interface below (by the forwarder contract)
- implementing the
ForwardRequest
interface below (by the request type contained in the contract)
// Forwarder must be implemented by a forwarder contract used by a *relayer.Relayer.
// These methods are wrappers around the methods auto-generated by abigen.
//
// See `impls/gsn_forwarder/i_forwarder_wrapped.go` or
// `impls/minimal_forwarder/i_minimal_forwarder_wrapped.go`for examples.
type Forwarder interface {
GetNonce(opts *bind.CallOpts, from ethcommon.Address) (*big.Int, error)
Verify(
opts *bind.CallOpts,
req ForwardRequest,
domainSeparator,
requestTypeHash [32]byte,
suffixData,
signature []byte,
) (bool, error)
Execute(
opts *bind.TransactOpts,
req ForwardRequest,
domainSeparator,
requestTypeHash [32]byte,
suffixData,
signature []byte,
) (*types.Transaction, error)
}
// ForwardRequest must be implemented by a request type used by a forwarder contract.
//
// See `impls/gsn_forwarder/request.go` or `impls/minimal_forwarder/request.go`
// for examples.
type ForwardRequest interface {
// FromSubmitTransactionRequest set the type underlying the ForwardRequest
// using a *SubmitTransactionRequest.
//
// Note: not all fields in the *SubmitTransactionRequest need be used depending
// on the implementation.
FromSubmitTransactionRequest(*SubmitTransactionRequest)
// Pack uses ABI encoding to pack the underlying ForwardRequest, appending
// optional `suffixData` to the end.
//
// See examples/gsn_forwarder/IForwarderForwardRequest.Pack() or
// examples/minimal_forwarder/IMinimalForwarderForwardRequest.Pack()
// for details.
Pack(suffixData []byte) ([]byte, error)
}
Additionally, to create a new *relayer.Relayer
, you need to implement a function that returns your ForwardRequest
. For example:
func NewIForwarderForwardRequest() common.ForwardRequest {
return &IForwarderForwardRequest{}
}
cfg := &relayer.Config{
// fields omitted
NewForwardRequestFunc: NewIForwarderForwardRequest,
}
_, _ = relayer.NewRelayer(cfg)
Interacting with the relayer
When running a relayer with an RPC server, it accepts transactions submission requests, returning a transaction hash if the transaction is successfully submitted:
func (s *RelayerService) SubmitTransaction(
_ *http.Request,
req *common.SubmitTransactionRequest,
resp *common.SubmitTransactionResponse,
) error
See go-relayer-client for client library and example usage.