Skip to content

noot/go-relayer

Repository files navigation

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.