services/rfq/relayer/chain/chain.go
// Package chain defines the interface for interacting with a blockchain.
package chain
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/synapsecns/sanguine/core"
"github.com/synapsecns/sanguine/ethergo/client"
"github.com/synapsecns/sanguine/ethergo/listener"
"github.com/synapsecns/sanguine/ethergo/submitter"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
"github.com/synapsecns/sanguine/services/rfq/util"
)
// Chain is a chain helper for relayer.
// lowercase fields are private, uppercase are public.
// the plan is to move this out of relayer which is when this distinction will matter.
type Chain struct {
ChainID uint32
Bridge *fastbridge.FastBridgeRef
Client client.EVM
Confirmations uint64
listener listener.ContractListener
submitter submitter.TransactionSubmitter
}
// NewChain creates a new chain.
func NewChain(ctx context.Context, cfg relconfig.Config, chainClient client.EVM, chainListener listener.ContractListener, ts submitter.TransactionSubmitter) (*Chain, error) {
chainID, err := chainClient.ChainID(ctx)
if err != nil {
return nil, fmt.Errorf("could not get chain id: %w", err)
}
addr, err := cfg.GetRFQAddress(int(chainID.Int64()))
if err != nil {
return nil, fmt.Errorf("could not get rfq address: %w", err)
}
bridge, err := fastbridge.NewFastBridgeRef(addr, chainClient)
if err != nil {
return nil, fmt.Errorf("could not create bridge contract: %w", err)
}
confirmations, err := cfg.GetConfirmations(int(chainID.Int64()))
if err != nil {
return nil, fmt.Errorf("could not get confirmations: %w", err)
}
return &Chain{
ChainID: uint32(chainID.Int64()),
Bridge: bridge,
Client: chainClient,
Confirmations: confirmations,
listener: chainListener,
submitter: ts,
}, nil
}
// SubmitTransaction submits a transaction to the chain.
func (c Chain) SubmitTransaction(ctx context.Context, call submitter.ContractCallType) (nonce uint64, _ error) {
//nolint: wrapcheck
return c.submitter.SubmitTransaction(ctx, big.NewInt(int64(c.ChainID)), call)
}
// LatestBlock returns the latest block.
func (c Chain) LatestBlock() uint64 {
return c.listener.LatestBlock()
}
// SubmitRelay submits a relay transaction to the destination chain after evaluating gas amount.
func (c Chain) SubmitRelay(ctx context.Context, request reldb.QuoteRequest) (uint64, *big.Int, error) {
gasAmount := big.NewInt(0)
var err error
// Check to see if ETH should be sent to destination
if util.IsGasToken(request.Transaction.DestToken) {
gasAmount = request.Transaction.DestAmount
} else if request.Transaction.SendChainGas {
gasAmount, err = c.Bridge.ChainGasAmount(&bind.CallOpts{Context: ctx})
if err != nil {
return 0, nil, fmt.Errorf("could not get chain gas amount: %w", err)
}
}
nonce, err := c.SubmitTransaction(ctx, func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
transactor.Value = core.CopyBigInt(gasAmount)
tx, err = c.Bridge.Relay(transactor, request.RawRequest)
if err != nil {
return nil, fmt.Errorf("could not relay: %w", err)
}
return tx, nil
})
if err != nil {
return 0, nil, fmt.Errorf("could not submit transaction: %w", err)
}
return nonce, gasAmount, nil
}