github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/send.go (about) 1 package stellar 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/keybase/client/go/libkb" 9 "github.com/keybase/client/go/protocol/stellar1" 10 "github.com/keybase/client/go/stellar/stellarcommon" 11 "github.com/keybase/stellarnet" 12 ) 13 14 func SendPaymentLocal(mctx libkb.MetaContext, arg stellar1.SendPaymentLocalArg) (res stellar1.SendPaymentResLocal, err error) { 15 if arg.Bid.IsNil() && !arg.BypassBid { 16 return res, fmt.Errorf("missing payment ID") 17 } 18 19 if !arg.Bid.IsNil() { 20 // Finalize the payment way up here so that it's predicatble 21 // that when an error is returned the payment has been canceled. 22 data, err := getGlobal(mctx.G()).finalizeBuildPayment(mctx, arg.Bid) 23 if err != nil { 24 return res, err 25 } 26 if data == nil { 27 // Not expected. 28 return res, fmt.Errorf("the payment to send was not found") 29 } 30 mctx.Debug("got state readyToReview:%v readyToSend:%v set:%v", 31 data.ReadyToReview, data.ReadyToSend, data.Frozen != nil) 32 if arg.BypassReview { 33 // Pretend that a review occurred and succeeded. 34 // Mutating this without the DataLock is not great, but nothing 35 // should access this `data` ever again, so should be safe. 36 data.ReadyToSend = data.ReadyToSend || data.ReadyToReview 37 } 38 err = data.CheckReadyToSend(arg) 39 if err != nil { 40 return res, err 41 } 42 } 43 44 if len(arg.From) == 0 { 45 return res, fmt.Errorf("missing from account ID parameter") 46 } 47 48 to := arg.To 49 if arg.ToIsAccountID { 50 toAccountID, err := libkb.ParseStellarAccountID(arg.To) 51 if err != nil { 52 if verr, ok := err.(libkb.VerboseError); ok { 53 mctx.Debug(verr.Verbose()) 54 } 55 return res, fmt.Errorf("recipient: %v", err) 56 } 57 to = toAccountID.String() 58 } 59 60 if !arg.Asset.IsNativeXLM() { 61 return res, fmt.Errorf("sending non-XLM assets is not supported") 62 } 63 64 var displayBalance DisplayBalance 65 if arg.WorthAmount != "" { 66 if arg.WorthCurrency == nil { 67 return res, fmt.Errorf("missing worth currency") 68 } 69 displayBalance = DisplayBalance{ 70 Amount: arg.WorthAmount, 71 Currency: arg.WorthCurrency.String(), 72 } 73 } 74 75 var cancel func() 76 mctx, cancel = mctx.WithTimeout(30 * time.Second) 77 defer cancel() 78 79 var pubMemo *stellarnet.Memo 80 if arg.PublicMemo != "" { 81 pubMemo = stellarnet.NewMemoText(arg.PublicMemo) 82 } 83 84 sendRes, err := SendPaymentGUI(mctx, getGlobal(mctx.G()).walletState, SendPaymentArg{ 85 From: arg.From, 86 To: stellarcommon.RecipientInput(to), 87 Amount: arg.Amount, 88 DisplayBalance: displayBalance, 89 SecretNote: arg.SecretNote, 90 PublicMemo: pubMemo, 91 ForceRelay: false, 92 QuickReturn: arg.QuickReturn, 93 }) 94 if err != nil { 95 if isTimeoutError(err) { 96 return res, fmt.Errorf("Timed out while sending payment. Look at your payment history and maybe try again.") 97 } 98 return res, err 99 } 100 return stellar1.SendPaymentResLocal{ 101 KbTxID: sendRes.KbTxID, 102 Pending: sendRes.Pending, 103 JumpToChat: sendRes.JumpToChat, 104 }, nil 105 } 106 107 func isTimeoutError(err error) bool { 108 if err == nil { 109 return false 110 } 111 if err == context.DeadlineExceeded { 112 return true 113 } 114 if err, ok := err.(libkb.APINetError); ok && err.Err == context.DeadlineExceeded { 115 return true 116 } 117 return false 118 }