// 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 }, }