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 }