github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/client/cmd_wallet_send.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/keybase/cli"
     9  	"github.com/keybase/client/go/libcmdline"
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/protocol/stellar1"
    12  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    13  	"github.com/keybase/stellarnet"
    14  	"golang.org/x/net/context"
    15  )
    16  
    17  type CmdWalletSend struct {
    18  	libkb.Contextified
    19  	Recipient     string
    20  	Amount        string
    21  	Note          string
    22  	LocalCurrency string
    23  	ForceRelay    bool
    24  	FromAccountID stellar1.AccountID
    25  	Memo          string
    26  	MemoType      stellar1.PublicNoteType
    27  }
    28  
    29  func newCmdWalletSend(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
    30  	flags := []cli.Flag{
    31  		cli.StringFlag{
    32  			Name:  "m, message",
    33  			Usage: "Include an encrypted message with the payment.",
    34  		},
    35  		cli.StringFlag{
    36  			Name:  "from",
    37  			Usage: "Specify the source account for the payment.",
    38  		},
    39  		cli.StringFlag{
    40  			Name:  "memo",
    41  			Usage: "Include a public memo in the stellar transaction.",
    42  		},
    43  		cli.StringFlag{
    44  			Name:  "memo_type",
    45  			Usage: "Specify the type of memo (text, id, hash, return). hash and return should be hex-encoded.",
    46  		},
    47  	}
    48  	if develUsage {
    49  		flags = append(flags, cli.BoolFlag{
    50  			Name:  "relay",
    51  			Usage: "Force a relay transfer (dev-only)",
    52  		})
    53  	}
    54  	cmd := &CmdWalletSend{
    55  		Contextified: libkb.NewContextified(g),
    56  	}
    57  	return cli.Command{
    58  		Name:         "send",
    59  		Usage:        "Send XLM to a keybase user or stellar address",
    60  		ArgumentHelp: "<recipient> <amount> <local currency> [-m message]",
    61  		Action: func(c *cli.Context) {
    62  			cl.ChooseCommand(cmd, "send", c)
    63  		},
    64  		Flags: flags,
    65  	}
    66  }
    67  
    68  func (c *CmdWalletSend) ParseArgv(ctx *cli.Context) error {
    69  	if len(ctx.Args()) > 3 {
    70  		return errors.New("send expects at most three arguments")
    71  	} else if len(ctx.Args()) < 2 {
    72  		return errors.New("send expects at least two arguments (recipient and amount)")
    73  	}
    74  
    75  	c.Recipient = ctx.Args()[0]
    76  	// TODO ensure amount is numeric and does not contain escape characters
    77  	c.Amount = ctx.Args()[1]
    78  	if len(ctx.Args()) == 3 {
    79  		c.LocalCurrency = strings.ToUpper(ctx.Args()[2])
    80  		if len(c.LocalCurrency) != 3 {
    81  			return errors.New("Invalid currency code")
    82  		}
    83  	}
    84  	c.Note = ctx.String("message")
    85  
    86  	c.Memo = ctx.String("memo")
    87  	memoType := ctx.String("memo_type")
    88  	if memoType == "" {
    89  		if c.Memo != "" {
    90  			memoType = "text"
    91  		} else {
    92  			memoType = "none"
    93  		}
    94  	}
    95  	var ok bool
    96  	c.MemoType, ok = stellar1.PublicNoteTypeMap[strings.ToUpper(memoType)]
    97  	if !ok {
    98  		return errors.New("invalid memo type")
    99  	}
   100  
   101  	c.ForceRelay = ctx.Bool("relay")
   102  	c.FromAccountID = stellar1.AccountID(ctx.String("from"))
   103  	return nil
   104  }
   105  
   106  func (c *CmdWalletSend) Run() (err error) {
   107  	defer transformStellarCLIError(&err)
   108  	cli, err := GetWalletClient(c.G())
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	protocols := []rpc.Protocol{
   114  		NewIdentifyUIProtocol(c.G()),
   115  	}
   116  	if err := RegisterProtocolsWithContext(protocols, c.G()); err != nil {
   117  		return err
   118  	}
   119  
   120  	ui := c.G().UI.GetTerminalUI()
   121  
   122  	amount := c.Amount
   123  	amountDesc := fmt.Sprintf("%s XLM", amount)
   124  
   125  	var displayAmount, displayCurrency string
   126  
   127  	if c.LocalCurrency != "" && c.LocalCurrency != "XLM" {
   128  		exchangeRate, err := cli.ExchangeRateLocal(context.Background(), stellar1.OutsideCurrencyCode(c.LocalCurrency))
   129  		if err != nil {
   130  			return fmt.Errorf("Unable to get exchange rate for %q: %s", c.LocalCurrency, err)
   131  		}
   132  
   133  		amount, err = stellarnet.ConvertOutsideToXLM(c.Amount, exchangeRate.Rate)
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		ui.Printf("Current exchange rate: ~ %s %s / XLM\n", exchangeRate.Rate, c.LocalCurrency)
   139  		amountDesc = fmt.Sprintf("%s XLM (~%s %s)", amount, c.Amount, c.LocalCurrency)
   140  		displayAmount = c.Amount
   141  		displayCurrency = c.LocalCurrency
   142  	}
   143  
   144  	_, err = stellarnet.ParseStellarAmount(amount)
   145  	if err != nil {
   146  		return fmt.Errorf("invalid amount of XLM: %q", amount)
   147  	}
   148  
   149  	if err := ui.PromptForConfirmation(fmt.Sprintf("Send %s to %s?", ColorString(c.G(), "green", amountDesc), ColorString(c.G(), "yellow", c.Recipient))); err != nil {
   150  		return err
   151  	}
   152  
   153  	arg := stellar1.SendCLILocalArg{
   154  		Recipient:       c.Recipient,
   155  		Amount:          amount,
   156  		Asset:           stellar1.AssetNative(),
   157  		Note:            c.Note,
   158  		DisplayAmount:   displayAmount,
   159  		DisplayCurrency: displayCurrency,
   160  		ForceRelay:      c.ForceRelay,
   161  		FromAccountID:   c.FromAccountID,
   162  		PublicNote:      c.Memo,
   163  		PublicNoteType:  c.MemoType,
   164  	}
   165  	res, err := cli.SendCLILocal(context.Background(), arg)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	ui.Printf("Sent!\nKeybase Transaction ID: %v\nStellar Transaction ID: %v\n", res.KbTxID, res.TxID)
   171  
   172  	return nil
   173  }
   174  
   175  func (c *CmdWalletSend) GetUsage() libkb.Usage {
   176  	return libkb.Usage{
   177  		Config:    true,
   178  		API:       true,
   179  		KbKeyring: true,
   180  	}
   181  }