synapsecns/sanguine

View on GitHub
services/rfq/relayer/cmd/commands.go

Summary

Maintainability
A
0 mins
Test Coverage
// Package cmd provides the command line interface for the RFQ relayer service
package cmd

import (
    "context"
    "fmt"
    "math/big"
    "time"

    "github.com/charmbracelet/huh/spinner"
    "github.com/ethereum/go-ethereum/common"
    "github.com/synapsecns/sanguine/core"
    "github.com/synapsecns/sanguine/core/commandline"
    "github.com/synapsecns/sanguine/core/metrics"
    "github.com/synapsecns/sanguine/core/retry"
    "github.com/synapsecns/sanguine/services/rfq/relayer/relapi"
    "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
    "github.com/synapsecns/sanguine/services/rfq/relayer/service"
    "github.com/urfave/cli/v2"
)

var configFlag = &cli.StringFlag{
    Name:      "config",
    Usage:     "path to the config file",
    TakesFile: true,
}

// runCommand runs the rfq relayer.
var runCommand = &cli.Command{
    Name:        "run",
    Description: "run the relayer",
    Flags:       []cli.Flag{configFlag, &commandline.LogLevel},
    Action: func(c *cli.Context) (err error) {
        commandline.SetLogLevel(c)

        metricsProvider := metrics.Get()

        cfg, err := relconfig.LoadConfig(core.ExpandOrReturnPath(c.String(configFlag.Name)))
        if err != nil {
            return fmt.Errorf("could not read config file: %w", err)
        }

        relayer, err := service.NewRelayer(c.Context, metricsProvider, cfg)
        if err != nil {
            return fmt.Errorf("could not create relayer: %w", err)
        }

        err = relayer.Start(c.Context)
        if err != nil {
            return fmt.Errorf("could not start relayer: %w", err)
        }
        return nil
    },
}

var relayerURLFlag = &cli.StringFlag{
    Name:  "relayer-url",
    Usage: "relayer url",
}

var chainIDFlag = &cli.StringFlag{
    Name:  "chain-id",
    Usage: "chain id",
}

var amountFlag = &cli.StringFlag{
    Name:  "amount",
    Usage: "amount",
}

var tokenAddressFlag = &cli.StringFlag{
    Name:  "token-address",
    Usage: "token address",
}

var toFlag = &cli.StringFlag{
    Name:  "to",
    Usage: "to",
}

// runCommand runs the rfq relayer.
var withdrawCommand = &cli.Command{
    Name:        "withdraw",
    Description: "run the withdrawal tool",
    Flags:       []cli.Flag{relayerURLFlag, chainIDFlag, amountFlag, tokenAddressFlag, toFlag, &commandline.LogLevel},
    Action: func(c *cli.Context) (err error) {
        if c.String(relayerURLFlag.Name) == "" {
            return fmt.Errorf("relayer URL is required")
        }

        client := relapi.NewRelayerClient(metrics.Get(), c.String(relayerURLFlag.Name))
        if err != nil {
            return fmt.Errorf("could not create relayer: %w", err)
        }

        chainID := big.NewInt(c.Int64(chainIDFlag.Name))
        if chainID.Cmp(big.NewInt(0)) == 0 {
            return fmt.Errorf("valid chain ID is required")
        }

        amount := c.String(amountFlag.Name)
        if amount == "" {
            return fmt.Errorf("amount is required")
        }

        tokenAddress := c.String(tokenAddressFlag.Name)
        if !common.IsHexAddress(tokenAddress) {
            return fmt.Errorf("valid token address is required")
        }

        to := c.String(toFlag.Name)
        if !common.IsHexAddress(to) {
            return fmt.Errorf("valid recipient address is required")
        }

        withdrawRequest := relapi.WithdrawRequest{
            ChainID:      uint32(chainID.Uint64()),
            Amount:       amount,
            TokenAddress: common.HexToAddress(tokenAddress),
            To:           common.HexToAddress(to),
        }
        res, err := client.Withdraw(c.Context, &withdrawRequest)
        if err != nil {
            return fmt.Errorf("could not start relayer: %w", err)
        }

        var errClient error
        var status *relapi.TxHashByNonceResponse

        ctx, cancel := context.WithTimeout(c.Context, 30*time.Second)
        defer cancel()

        action := func() {
            err = retry.WithBackoff(ctx, func(_ context.Context) error {
                status, err = client.GetTxHashByNonce(
                    c.Context,
                    &relapi.GetTxByNonceRequest{
                        ChainID: uint32(chainID.Uint64()),
                        Nonce:   res.Nonce,
                    })
                if err != nil {
                    errClient = err
                    return fmt.Errorf("could not get withdrawal tx hash: %w", err)
                }
                return nil
            }, retry.WithMaxTotalTime(1*time.Minute))

            if err != nil {
                return
            }
        }

        err = spinner.New().
            Title("Getting withdrawal tx hash...").
            Action(action).Run()

        if err != nil {
            return fmt.Errorf("could not get withdrawal tx hash: %w", err)
        }
        if errClient != nil {
            return fmt.Errorf("client error: could not get withdrawal tx hash: %w", errClient)
        }

        fmt.Printf("Withdraw Tx Hash: %s\n", status.Hash)

        return nil
    },
}