github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/frontend.go (about) 1 // this file is for the implementation of all the frontend-requested service 2 // endpoints for wallets. 3 package stellarsvc 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "sort" 10 "unicode/utf8" 11 12 "github.com/keybase/client/go/chat/msgchecker" 13 "github.com/keybase/client/go/libkb" 14 "github.com/keybase/client/go/protocol/stellar1" 15 "github.com/keybase/client/go/stellar" 16 airdrop "github.com/keybase/client/go/stellar/airdrop" 17 "github.com/keybase/client/go/stellar/remote" 18 "github.com/keybase/client/go/stellar/stellarcommon" 19 "github.com/keybase/stellarnet" 20 ) 21 22 const WorthCurrencyErrorCode = "ERR" 23 24 var ErrAccountIDMissing = errors.New("account id parameter missing") 25 26 func (s *Server) GetWalletAccountsLocal(ctx context.Context, sessionID int) (accts []stellar1.WalletAccountLocal, err error) { 27 mctx, fin, err := s.Preamble(ctx, preambleArg{ 28 RPCName: "GetWalletAccountsLocal", 29 Err: &err, 30 RequireWallet: true, 31 }) 32 defer fin() 33 if err != nil { 34 return nil, err 35 } 36 37 return stellar.AllWalletAccounts(mctx, s.remoter) 38 } 39 40 func (s *Server) GetWalletAccountLocal(ctx context.Context, arg stellar1.GetWalletAccountLocalArg) (acct stellar1.WalletAccountLocal, err error) { 41 mctx, fin, err := s.Preamble(ctx, preambleArg{ 42 RPCName: "GetWalletAccountLocal", 43 Err: &err, 44 RequireWallet: true, 45 }) 46 defer fin() 47 if err != nil { 48 return acct, err 49 } 50 51 if arg.AccountID.IsNil() { 52 mctx.Debug("GetWalletAccountLocal called with an empty account id") 53 return acct, ErrAccountIDMissing 54 } 55 56 return stellar.WalletAccount(mctx, s.remoter, arg.AccountID) 57 } 58 59 func (s *Server) GetAccountAssetsLocal(ctx context.Context, arg stellar1.GetAccountAssetsLocalArg) (assets []stellar1.AccountAssetLocal, err error) { 60 mctx, fin, err := s.Preamble(ctx, preambleArg{ 61 RPCName: "GetAccountAssetsLocal", 62 Err: &err, 63 }) 64 defer fin() 65 if err != nil { 66 return nil, err 67 } 68 69 if arg.AccountID.IsNil() { 70 s.G().Log.CDebugf(ctx, "GetAccountAssetsLocal called with an empty account id") 71 return nil, ErrAccountIDMissing 72 } 73 74 details, err := stellar.AccountDetails(mctx, s.remoter, arg.AccountID) 75 if err != nil { 76 s.G().Log.CDebugf(ctx, "remote.Details failed for %q: %s", arg.AccountID, err) 77 return nil, err 78 } 79 80 if len(details.Balances) == 0 { 81 // add an empty xlm balance 82 s.G().Log.CDebugf(ctx, "Account has no balances - adding default 0 XLM balance") 83 stellar.EmptyAmountStack(mctx) 84 details.Available = "0" 85 details.Balances = []stellar1.Balance{ 86 { 87 Amount: "0", 88 Asset: stellar1.Asset{Type: "native"}, 89 }, 90 } 91 } 92 93 if details.Available == "" { 94 s.G().Log.CDebugf(ctx, "details.Available is empty: %+v", details) 95 stellar.EmptyAmountStack(mctx) 96 details.Available = "0" 97 s.G().Log.CDebugf(ctx, `set details.Available from empty to "0"`) 98 } 99 100 displayCurrency, err := stellar.GetAccountDisplayCurrency(mctx, arg.AccountID) 101 if err != nil { 102 return nil, err 103 } 104 s.G().Log.CDebugf(ctx, "Display currency for account %q is %q", arg.AccountID, displayCurrency) 105 rate, rateErr := s.remoter.ExchangeRate(ctx, displayCurrency) 106 if rateErr != nil { 107 s.G().Log.CDebugf(ctx, "exchange rate error: %s", rateErr) 108 } 109 110 for _, d := range details.Balances { 111 fmtAmount, err := stellar.FormatAmount(mctx, d.Amount, false, stellarnet.Round) 112 if err != nil { 113 s.G().Log.CDebugf(ctx, "FormatAmount error: %s", err) 114 return nil, err 115 } 116 117 if d.Asset.IsNativeXLM() { 118 baseFee := s.walletState.BaseFee(mctx) 119 availableAmount := stellar.SubtractFeeSoft(mctx, details.Available, baseFee) 120 if availableAmount == "" { 121 s.G().Log.CDebugf(ctx, "stellar.SubtractFeeSoft returned empty available amount, setting it to 0") 122 stellar.EmptyAmountStack(mctx) 123 availableAmount = "0" 124 } 125 fmtAvailable, err := stellar.FormatAmount(mctx, availableAmount, false, stellarnet.Round) 126 if err != nil { 127 return nil, err 128 } 129 130 asset := stellar1.AccountAssetLocal{ 131 Name: "Lumens", 132 AssetCode: "XLM", 133 IssuerName: "Stellar network", 134 IssuerAccountID: "", 135 BalanceTotal: fmtAmount, 136 BalanceAvailableToSend: fmtAvailable, 137 WorthCurrency: displayCurrency, 138 } 139 fillWorths := func() (err error) { 140 if rateErr != nil { 141 return fmt.Errorf("rate error: %v", rateErr) 142 } 143 outsideAmount, err := stellarnet.ConvertXLMToOutside(d.Amount, rate.Rate) 144 if err != nil { 145 return fmt.Errorf("converting amount: %v", err) 146 } 147 fmtWorth, err := stellar.FormatCurrencyWithCodeSuffix(mctx, outsideAmount, rate.Currency, stellarnet.Round) 148 if err != nil { 149 return fmt.Errorf("formatting converted amount: %v", err) 150 } 151 asset.Worth = fmtWorth 152 outsideAvailableAmount, err := stellarnet.ConvertXLMToOutside(availableAmount, rate.Rate) 153 if err != nil { 154 return fmt.Errorf("converting available amount: %v", err) 155 } 156 fmtAvailableWorth, err := stellar.FormatCurrencyWithCodeSuffix(mctx, outsideAvailableAmount, rate.Currency, stellarnet.Round) 157 if err != nil { 158 return fmt.Errorf("formatting converted available amount: %v", err) 159 } 160 asset.AvailableToSendWorth = fmtAvailableWorth 161 return nil 162 } 163 err = fillWorths() 164 if err != nil { 165 s.G().Log.CDebugf(ctx, "error populating converted worth fields: %v", err) 166 asset.WorthCurrency = WorthCurrencyErrorCode 167 asset.Worth = "Currency conversion error" 168 asset.AvailableToSendWorth = "Currency conversion error" 169 } 170 // Add account reserves info to main asset. 171 asset.Reserves = details.Reserves 172 assets = append(assets, asset) 173 } else { 174 assets = append(assets, stellar1.AccountAssetLocal{ 175 Name: d.Asset.Code, 176 AssetCode: d.Asset.Code, 177 IssuerName: d.Asset.IssuerName, 178 IssuerAccountID: d.Asset.Issuer, 179 IssuerVerifiedDomain: d.Asset.VerifiedDomain, 180 BalanceTotal: fmtAmount, 181 BalanceAvailableToSend: fmtAmount, 182 WorthCurrency: "", 183 Worth: "", 184 AvailableToSendWorth: "", 185 Desc: d.Asset.Desc, 186 InfoUrl: d.Asset.InfoUrl, 187 InfoUrlText: d.Asset.InfoUrlText, 188 ShowDepositButton: d.Asset.ShowDepositButton, 189 DepositButtonText: d.Asset.DepositButtonText, 190 ShowWithdrawButton: d.Asset.ShowWithdrawButton, 191 WithdrawButtonText: d.Asset.WithdrawButtonText, 192 }) 193 } 194 } 195 196 return assets, nil 197 } 198 199 func (s *Server) GetDisplayCurrenciesLocal(ctx context.Context, sessionID int) (currencies []stellar1.CurrencyLocal, err error) { 200 mctx, fin, err := s.Preamble(ctx, preambleArg{ 201 RPCName: "GetDisplayCurrenciesLocal", 202 Err: &err, 203 }) 204 defer fin() 205 if err != nil { 206 return nil, err 207 } 208 209 conf, err := s.G().GetStellar().GetServerDefinitions(mctx.Ctx()) 210 if err != nil { 211 return nil, err 212 } 213 214 for code := range conf.Currencies { 215 c, ok := conf.GetCurrencyLocal(code) 216 if ok { 217 currencies = append(currencies, c) 218 } 219 } 220 sort.Slice(currencies, func(i, j int) bool { 221 if currencies[i].Code == "USD" { 222 return true 223 } 224 if currencies[j].Code == "USD" { 225 return false 226 } 227 return currencies[i].Code < currencies[j].Code 228 }) 229 230 return currencies, nil 231 } 232 233 func (s *Server) HasAcceptedDisclaimerLocal(ctx context.Context, sessionID int) (accepted bool, err error) { 234 mctx, fin, err := s.Preamble(ctx, preambleArg{ 235 RPCName: "HasAcceptedDisclaimerLocal", 236 Err: &err, 237 }) 238 defer fin() 239 if err != nil { 240 return false, err 241 } 242 243 return stellar.HasAcceptedDisclaimer(mctx.Ctx(), s.G()) 244 } 245 246 func (s *Server) AcceptDisclaimerLocal(ctx context.Context, sessionID int) (err error) { 247 mctx, fin, err := s.Preamble(ctx, preambleArg{ 248 RPCName: "AcceptDisclaimerLocal", 249 Err: &err, 250 }) 251 defer fin() 252 if err != nil { 253 return err 254 } 255 256 err = remote.SetAcceptedDisclaimer(mctx.Ctx(), s.G()) 257 if err != nil { 258 return err 259 } 260 stellar.InformAcceptedDisclaimer(mctx.Ctx(), s.G()) 261 cwg, err := stellar.CreateWalletGated(mctx) 262 if err != nil { 263 return err 264 } 265 if !cwg.HasWallet { 266 return fmt.Errorf("user wallet not created") 267 } 268 269 err = s.walletState.RefreshAll(mctx, "AcceptDisclaimer") 270 if err != nil { 271 mctx.Debug("AcceptDisclaimer RefreshAll error: %s", err) 272 } 273 274 return nil 275 } 276 277 func (s *Server) LinkNewWalletAccountLocal(ctx context.Context, arg stellar1.LinkNewWalletAccountLocalArg) (accountID stellar1.AccountID, err error) { 278 mctx, fin, err := s.Preamble(ctx, preambleArg{ 279 RPCName: "LinkNewWalletAccountLocal", 280 Err: &err, 281 RequireWallet: true, 282 }) 283 defer fin() 284 if err != nil { 285 return "", err 286 } 287 288 _, accountID, _, err = libkb.ParseStellarSecretKey(string(arg.SecretKey)) 289 if err != nil { 290 return "", err 291 } 292 293 err = stellar.ImportSecretKey(mctx, arg.SecretKey, false, arg.Name) 294 if err != nil { 295 return "", err 296 } 297 298 err = s.walletState.RefreshAll(mctx, "LinkNewWalletAccount") 299 if err != nil { 300 mctx.Debug("LinkNewWalletAccountLocal RefreshAll error: %s", err) 301 } 302 303 return accountID, nil 304 } 305 306 func (s *Server) GetPaymentsLocal(ctx context.Context, arg stellar1.GetPaymentsLocalArg) (page stellar1.PaymentsPageLocal, err error) { 307 mctx, fin, err := s.Preamble(ctx, preambleArg{ 308 RPCName: "GetPaymentsLocal", 309 Err: &err, 310 RequireWallet: true, 311 }) 312 defer fin() 313 if err != nil { 314 return page, err 315 } 316 317 if arg.AccountID.IsNil() { 318 s.G().Log.CDebugf(ctx, "GetPaymentsLocal called with an empty account id") 319 return page, ErrAccountIDMissing 320 } 321 322 rpArg := remote.RecentPaymentsArg{ 323 AccountID: arg.AccountID, 324 Cursor: arg.Cursor, 325 SkipPending: true, 326 IncludeAdvanced: true, 327 } 328 srvPayments, err := s.remoter.RecentPayments(ctx, rpArg) 329 if err != nil { 330 return page, err 331 } 332 333 return stellar.RemoteRecentPaymentsToPage(mctx, s.remoter, arg.AccountID, srvPayments) 334 } 335 336 func (s *Server) GetPendingPaymentsLocal(ctx context.Context, arg stellar1.GetPendingPaymentsLocalArg) (payments []stellar1.PaymentOrErrorLocal, err error) { 337 mctx, fin, err := s.Preamble(ctx, preambleArg{ 338 RPCName: "GetPendingPaymentsLocal", 339 Err: &err, 340 RequireWallet: true, 341 }) 342 defer fin() 343 if err != nil { 344 return nil, err 345 } 346 347 if arg.AccountID.IsNil() { 348 s.G().Log.CDebugf(ctx, "GetPendingPaymentsLocal called with an empty account id") 349 return payments, ErrAccountIDMissing 350 } 351 352 pending, err := s.remoter.PendingPayments(ctx, arg.AccountID, 0) 353 if err != nil { 354 return nil, err 355 } 356 357 return stellar.RemotePendingToLocal(mctx, s.remoter, arg.AccountID, pending) 358 } 359 360 func (s *Server) GetPaymentDetailsLocal(ctx context.Context, arg stellar1.GetPaymentDetailsLocalArg) (payment stellar1.PaymentDetailsLocal, err error) { 361 mctx, fin, err := s.Preamble(ctx, preambleArg{ 362 RPCName: "GetPaymentDetailsLocal", 363 Err: &err, 364 }) 365 defer fin() 366 if err != nil { 367 return payment, err 368 } 369 370 if arg.AccountID.IsNil() { 371 return payment, errors.New("AccountID required for GetPaymentDetailsLocal") 372 } 373 374 oc := stellar.NewOwnAccountLookupCache(mctx) 375 details, err := s.remoter.PaymentDetails(ctx, arg.AccountID, stellar1.TransactionIDFromPaymentID(arg.Id).String()) 376 if err != nil { 377 return payment, err 378 } 379 380 summary, err := stellar.TransformPaymentSummaryAccount(mctx, details.Summary, oc, arg.AccountID) 381 if err != nil { 382 return payment, err 383 } 384 385 var fee string 386 if details.FeeCharged != "" { 387 fee, err = stellar.FormatAmountDescriptionXLM(mctx, details.FeeCharged) 388 if err != nil { 389 return payment, err 390 } 391 } 392 393 summary.TxID = stellar1.TransactionIDFromPaymentID(summary.Id) 394 395 return stellar1.PaymentDetailsLocal{ 396 Summary: *summary, 397 Details: stellar1.PaymentDetailsOnlyLocal{ 398 PublicNote: details.Memo, 399 PublicNoteType: details.MemoType, 400 ExternalTxURL: details.ExternalTxURL, 401 FeeChargedDescription: fee, 402 PathIntermediate: details.PathIntermediate, 403 }, 404 }, nil 405 } 406 407 func (s *Server) GetGenericPaymentDetailsLocal(ctx context.Context, arg stellar1.GetGenericPaymentDetailsLocalArg) (payment stellar1.PaymentDetailsLocal, err error) { 408 mctx, fin, err := s.Preamble(ctx, preambleArg{ 409 RPCName: "GetGenericPaymentDetailsLocal", 410 Err: &err, 411 }) 412 defer fin() 413 if err != nil { 414 return payment, err 415 } 416 417 oc := stellar.NewOwnAccountLookupCache(mctx) 418 details, err := s.remoter.PaymentDetailsGeneric(ctx, stellar1.TransactionIDFromPaymentID(arg.Id).String()) 419 if err != nil { 420 return payment, err 421 } 422 423 summary, err := stellar.TransformPaymentSummaryGeneric(mctx, details.Summary, oc) 424 if err != nil { 425 return payment, err 426 } 427 428 summary.TxID = stellar1.TransactionIDFromPaymentID(summary.Id) 429 430 return stellar1.PaymentDetailsLocal{ 431 Summary: *summary, 432 Details: stellar1.PaymentDetailsOnlyLocal{ 433 PublicNote: details.Memo, 434 PublicNoteType: details.MemoType, 435 ExternalTxURL: details.ExternalTxURL, 436 }, 437 }, nil 438 } 439 440 func (s *Server) CancelPaymentLocal(ctx context.Context, arg stellar1.CancelPaymentLocalArg) (res stellar1.RelayClaimResult, err error) { 441 mctx, fin, err := s.Preamble(ctx, preambleArg{ 442 RPCName: "CancelPaymentLocal", 443 Err: &err, 444 RequireWallet: true, 445 }) 446 defer fin() 447 if err != nil { 448 return res, err 449 } 450 451 details, err := s.remoter.PaymentDetailsGeneric(mctx.Ctx(), stellar1.TransactionIDFromPaymentID(arg.PaymentID).String()) 452 if err != nil { 453 return res, err 454 } 455 typ, err := details.Summary.Typ() 456 if err != nil { 457 return res, err 458 } 459 if typ != stellar1.PaymentSummaryType_RELAY { 460 return res, errors.New("tried to cancel a non-relay payment") 461 } 462 relay := details.Summary.Relay() 463 dir := stellar1.RelayDirection_YANK 464 return stellar.Claim(mctx, s.walletState, relay.KbTxID.String(), relay.FromStellar, &dir, nil) 465 } 466 467 func (s *Server) ValidateAccountIDLocal(ctx context.Context, arg stellar1.ValidateAccountIDLocalArg) (err error) { 468 _, fin, err := s.Preamble(ctx, preambleArg{ 469 RPCName: "ValidateAccountIDLocal", 470 Err: &err, 471 }) 472 defer fin() 473 if err != nil { 474 return err 475 } 476 _, err = libkb.ParseStellarAccountID(arg.AccountID.String()) 477 return err 478 } 479 480 func (s *Server) ValidateSecretKeyLocal(ctx context.Context, arg stellar1.ValidateSecretKeyLocalArg) (err error) { 481 _, fin, err := s.Preamble(ctx, preambleArg{ 482 RPCName: "ValidateSecretKeyLocal", 483 Err: &err, 484 }) 485 defer fin() 486 if err != nil { 487 return err 488 } 489 _, _, _, err = libkb.ParseStellarSecretKey(arg.SecretKey.SecureNoLogString()) 490 return err 491 } 492 493 func (s *Server) ValidateAccountNameLocal(ctx context.Context, arg stellar1.ValidateAccountNameLocalArg) (err error) { 494 mctx, fin, err := s.Preamble(ctx, preambleArg{ 495 RPCName: "ValidateAccountNameLocal", 496 Err: &err, 497 }) 498 defer fin() 499 if err != nil { 500 return err 501 } 502 // Make sure to keep this validation in sync with ChangeAccountName. 503 if arg.Name == "" { 504 return fmt.Errorf("name required") 505 } 506 runes := utf8.RuneCountInString(arg.Name) 507 if runes > stellar.AccountNameMaxRunes { 508 return fmt.Errorf("account name can be %v characters at the longest but was %v", stellar.AccountNameMaxRunes, runes) 509 } 510 // If this becomes a bottleneck, cache non-critical wallet info on G.Stellar. 511 currentBundle, err := remote.FetchSecretlessBundle(mctx) 512 if err != nil { 513 s.G().Log.CErrorf(ctx, "error fetching bundle: %v", err) 514 // Return nil since the name is probably fine. 515 return nil 516 } 517 for _, account := range currentBundle.Accounts { 518 if arg.Name == account.Name { 519 return fmt.Errorf("you already have an account with that name") 520 } 521 } 522 return nil 523 } 524 525 func (s *Server) ChangeWalletAccountNameLocal(ctx context.Context, arg stellar1.ChangeWalletAccountNameLocalArg) (acct stellar1.WalletAccountLocal, err error) { 526 mctx, fin, err := s.Preamble(ctx, preambleArg{ 527 RPCName: "ChangeWalletAccountNameLocal", 528 Err: &err, 529 RequireWallet: true, 530 }) 531 defer fin() 532 if err != nil { 533 return acct, err 534 } 535 536 if arg.AccountID.IsNil() { 537 mctx.Debug("ChangeWalletAccountNameLocal called with an empty account id") 538 return acct, ErrAccountIDMissing 539 } 540 541 err = stellar.ChangeAccountName(mctx, s.walletState, arg.AccountID, arg.NewName) 542 if err != nil { 543 return acct, err 544 } 545 return stellar.WalletAccount(mctx, s.remoter, arg.AccountID) 546 } 547 548 func (s *Server) SetWalletAccountAsDefaultLocal(ctx context.Context, arg stellar1.SetWalletAccountAsDefaultLocalArg) (accts []stellar1.WalletAccountLocal, err error) { 549 mctx, fin, err := s.Preamble(ctx, preambleArg{ 550 RPCName: "SetWalletAccountAsDefaultLocal", 551 Err: &err, 552 RequireWallet: true, 553 }) 554 defer fin() 555 if err != nil { 556 return accts, err 557 } 558 559 if arg.AccountID.IsNil() { 560 mctx.Debug("SetWalletAccountAsDefaultLocal called with an empty account id") 561 return accts, ErrAccountIDMissing 562 } 563 564 err = stellar.SetAccountAsPrimary(mctx, s.walletState, arg.AccountID) 565 if err != nil { 566 return accts, err 567 } 568 return stellar.AllWalletAccounts(mctx, s.remoter) 569 } 570 571 func (s *Server) DeleteWalletAccountLocal(ctx context.Context, arg stellar1.DeleteWalletAccountLocalArg) (err error) { 572 mctx, fin, err := s.Preamble(ctx, preambleArg{ 573 RPCName: "DeleteWalletAccountLocal", 574 Err: &err, 575 RequireWallet: true, 576 }) 577 defer fin() 578 if err != nil { 579 return err 580 } 581 582 if arg.UserAcknowledged != "yes" { 583 return errors.New("User did not acknowledge") 584 } 585 586 if arg.AccountID.IsNil() { 587 mctx.Debug("DeleteWalletAccountLocal called with an empty account id") 588 return ErrAccountIDMissing 589 } 590 591 return stellar.DeleteAccount(mctx, arg.AccountID) 592 } 593 594 func (s *Server) ChangeDisplayCurrencyLocal(ctx context.Context, arg stellar1.ChangeDisplayCurrencyLocalArg) (res stellar1.CurrencyLocal, err error) { 595 mctx, fin, err := s.Preamble(ctx, preambleArg{ 596 RPCName: "ChangeDisplayCurrencyLocal", 597 Err: &err, 598 RequireWallet: true, 599 }) 600 defer fin() 601 if err != nil { 602 return res, err 603 } 604 605 if arg.AccountID.IsNil() { 606 return res, ErrAccountIDMissing 607 } 608 err = remote.SetAccountDefaultCurrency(mctx.Ctx(), s.G(), arg.AccountID, string(arg.Currency)) 609 if err != nil { 610 return res, err 611 } 612 return stellar.GetCurrencySetting(mctx, arg.AccountID) 613 } 614 615 func (s *Server) GetDisplayCurrencyLocal(ctx context.Context, arg stellar1.GetDisplayCurrencyLocalArg) (res stellar1.CurrencyLocal, err error) { 616 mctx, fin, err := s.Preamble(ctx, preambleArg{ 617 RPCName: "GetDisplayCurrencyLocal", 618 Err: &err, 619 }) 620 defer fin() 621 if err != nil { 622 return res, err 623 } 624 625 accountID := arg.AccountID 626 if accountID == nil { 627 primaryAccountID, err := stellar.GetOwnPrimaryAccountID(mctx) 628 if err != nil { 629 return res, err 630 } 631 accountID = &primaryAccountID 632 } 633 return stellar.GetCurrencySetting(mctx, *accountID) 634 } 635 636 func (s *Server) GetWalletAccountPublicKeyLocal(ctx context.Context, arg stellar1.GetWalletAccountPublicKeyLocalArg) (res string, err error) { 637 _, fin, err := s.Preamble(ctx, preambleArg{ 638 RPCName: "GetWalletAccountPublicKeyLocal", 639 Err: &err, 640 AllowLoggedOut: true, 641 }) 642 defer fin() 643 if err != nil { 644 return res, err 645 } 646 647 if arg.AccountID.IsNil() { 648 return res, ErrAccountIDMissing 649 } 650 return arg.AccountID.String(), nil 651 } 652 653 func (s *Server) GetWalletAccountSecretKeyLocal(ctx context.Context, arg stellar1.GetWalletAccountSecretKeyLocalArg) (res stellar1.SecretKey, err error) { 654 mctx, fin, err := s.Preamble(ctx, preambleArg{ 655 RPCName: "GetWalletAccountSecretKeyLocal", 656 Err: &err, 657 RequireWallet: true, 658 }) 659 defer fin() 660 if err != nil { 661 return res, err 662 } 663 664 if arg.AccountID.IsNil() { 665 return res, ErrAccountIDMissing 666 } 667 return stellar.ExportSecretKey(mctx, arg.AccountID) 668 } 669 670 func (s *Server) GetSendAssetChoicesLocal(ctx context.Context, arg stellar1.GetSendAssetChoicesLocalArg) (res []stellar1.SendAssetChoiceLocal, err error) { 671 mctx, fin, err := s.Preamble(ctx, preambleArg{ 672 RPCName: "GetSendAssetChoicesLocal", 673 Err: &err, 674 RequireWallet: true, 675 }) 676 defer fin() 677 if err != nil { 678 return res, err 679 } 680 681 return stellar.GetSendAssetChoicesLocal(mctx, s.remoter, arg) 682 } 683 684 func (s *Server) StartBuildPaymentLocal(ctx context.Context, sessionID int) (res stellar1.BuildPaymentID, err error) { 685 mctx, fin, err := s.Preamble(ctx, preambleArg{ 686 RPCName: "StartBuildPaymentLocal", 687 Err: &err, 688 RequireWallet: true, 689 }) 690 defer fin() 691 if err != nil { 692 return res, err 693 } 694 return stellar.StartBuildPaymentLocal(mctx) 695 } 696 697 func (s *Server) StopBuildPaymentLocal(ctx context.Context, arg stellar1.StopBuildPaymentLocalArg) (err error) { 698 mctx, fin, err := s.Preamble(ctx, preambleArg{ 699 RPCName: "StopBuildPaymentLocal", 700 Err: &err, 701 RequireWallet: true, 702 }) 703 defer fin() 704 if err != nil { 705 return err 706 } 707 stellar.StopBuildPaymentLocal(mctx, arg.Bid) 708 return nil 709 } 710 711 func (s *Server) BuildPaymentLocal(ctx context.Context, arg stellar1.BuildPaymentLocalArg) (res stellar1.BuildPaymentResLocal, err error) { 712 mctx, fin, err := s.Preamble(ctx, preambleArg{ 713 RPCName: "BuildPaymentLocal", 714 Err: &err, 715 RequireWallet: true, 716 }) 717 defer fin() 718 if err != nil { 719 return res, err 720 } 721 return stellar.BuildPaymentLocal(mctx, arg) 722 } 723 724 func (s *Server) ReviewPaymentLocal(ctx context.Context, arg stellar1.ReviewPaymentLocalArg) (err error) { 725 mctx, fin, err := s.Preamble(ctx, preambleArg{ 726 RPCName: "ReviewPaymentLocal", 727 Err: &err, 728 RequireWallet: true, 729 }) 730 defer fin() 731 if err != nil { 732 return err 733 } 734 return stellar.ReviewPaymentLocal(mctx, s.uiSource.StellarUI(), arg) 735 } 736 737 func (s *Server) SendPaymentLocal(ctx context.Context, arg stellar1.SendPaymentLocalArg) (res stellar1.SendPaymentResLocal, err error) { 738 mctx, fin, err := s.Preamble(ctx, preambleArg{ 739 RPCName: "SendPaymentLocal", 740 Err: &err, 741 RequireWallet: true, 742 }) 743 defer fin() 744 if err != nil { 745 return res, err 746 } 747 return stellar.SendPaymentLocal(mctx, arg) 748 } 749 750 func (s *Server) SendPathLocal(ctx context.Context, arg stellar1.SendPathLocalArg) (res stellar1.SendPaymentResLocal, err error) { 751 mctx, fin, err := s.Preamble(ctx, preambleArg{ 752 RPCName: "SendPathLocal", 753 Err: &err, 754 RequireWallet: true, 755 }) 756 defer fin() 757 if err != nil { 758 return res, err 759 } 760 761 var pubMemo *stellarnet.Memo 762 if arg.PublicNote != "" { 763 pubMemo = stellarnet.NewMemoText(arg.PublicNote) 764 } 765 766 sendRes, err := stellar.SendPathPaymentGUI(mctx, s.walletState, stellar.SendPathPaymentArg{ 767 From: arg.Source, 768 To: stellarcommon.RecipientInput(arg.Recipient), 769 Path: arg.Path, 770 SecretNote: arg.Note, 771 PublicMemo: pubMemo, 772 QuickReturn: true, 773 }) 774 if err != nil { 775 return res, err 776 } 777 return stellar1.SendPaymentResLocal{ 778 KbTxID: sendRes.KbTxID, 779 Pending: sendRes.Pending, 780 JumpToChat: sendRes.JumpToChat, 781 }, nil 782 } 783 784 func (s *Server) CreateWalletAccountLocal(ctx context.Context, arg stellar1.CreateWalletAccountLocalArg) (res stellar1.AccountID, err error) { 785 mctx, fin, err := s.Preamble(ctx, preambleArg{ 786 RPCName: "CreateWalletAccountLocal", 787 Err: &err, 788 RequireWallet: true, 789 }) 790 defer fin() 791 if err != nil { 792 return res, err 793 } 794 return stellar.CreateNewAccount(mctx, arg.Name) 795 } 796 797 func (s *Server) BuildRequestLocal(ctx context.Context, arg stellar1.BuildRequestLocalArg) (res stellar1.BuildRequestResLocal, err error) { 798 mctx, fin, err := s.Preamble(ctx, preambleArg{ 799 RPCName: "BuildRequestLocal", 800 Err: &err, 801 RequireWallet: true, 802 }) 803 defer fin() 804 if err != nil { 805 return res, err 806 } 807 return stellar.BuildRequestLocal(mctx, arg) 808 } 809 810 func (s *Server) GetRequestDetailsLocal(ctx context.Context, arg stellar1.GetRequestDetailsLocalArg) (res stellar1.RequestDetailsLocal, err error) { 811 mctx, fin, err := s.Preamble(ctx, preambleArg{ 812 RPCName: "GetRequestDetailsLocal", 813 Err: &err, 814 }) 815 defer fin() 816 if err != nil { 817 return stellar1.RequestDetailsLocal{}, err 818 } 819 820 details, err := s.remoter.RequestDetails(mctx.Ctx(), arg.ReqID) 821 if err != nil { 822 return stellar1.RequestDetailsLocal{}, err 823 } 824 825 local, err := stellar.TransformRequestDetails(mctx, details) 826 if err != nil { 827 return stellar1.RequestDetailsLocal{}, err 828 } 829 830 return *local, nil 831 } 832 833 func (s *Server) MakeRequestLocal(ctx context.Context, arg stellar1.MakeRequestLocalArg) (res stellar1.KeybaseRequestID, err error) { 834 mctx, fin, err := s.Preamble(ctx, preambleArg{ 835 RPCName: "MakeRequestLocal", 836 Err: &err, 837 RequireWallet: true, 838 }) 839 defer fin() 840 if err != nil { 841 return "", err 842 } 843 844 return stellar.MakeRequestGUI(mctx, s.remoter, stellar.MakeRequestArg{ 845 To: stellarcommon.RecipientInput(arg.Recipient), 846 Amount: arg.Amount, 847 Asset: arg.Asset, 848 Currency: arg.Currency, 849 Note: arg.Note, 850 }) 851 } 852 853 func (s *Server) CancelRequestLocal(ctx context.Context, arg stellar1.CancelRequestLocalArg) (err error) { 854 mctx, fin, err := s.Preamble(ctx, preambleArg{ 855 RPCName: "CancelRequestLocal", 856 Err: &err, 857 }) 858 defer fin() 859 if err != nil { 860 return err 861 } 862 863 return s.remoter.CancelRequest(mctx.Ctx(), arg.ReqID) 864 } 865 866 func (s *Server) MarkAsReadLocal(ctx context.Context, arg stellar1.MarkAsReadLocalArg) (err error) { 867 mctx, fin, err := s.Preamble(ctx, preambleArg{ 868 RPCName: "MarkAsReadLocal", 869 Err: &err, 870 RequireWallet: true, 871 }) 872 defer fin() 873 if err != nil { 874 return err 875 } 876 877 if arg.AccountID.IsNil() { 878 mctx.Debug("IsAccountMobileOnlyLocal called with an empty account id") 879 return ErrAccountIDMissing 880 } 881 882 err = s.remoter.MarkAsRead(mctx.Ctx(), arg.AccountID, stellar1.TransactionIDFromPaymentID(arg.MostRecentID)) 883 if err != nil { 884 return err 885 } 886 887 go stellar.RefreshUnreadCount(s.G(), arg.AccountID) 888 889 return nil 890 } 891 892 func (s *Server) IsAccountMobileOnlyLocal(ctx context.Context, arg stellar1.IsAccountMobileOnlyLocalArg) (mobileOnly bool, err error) { 893 mctx, fin, err := s.Preamble(ctx, preambleArg{ 894 RPCName: "IsAccountMobileOnlyLocal", 895 Err: &err, 896 }) 897 defer fin() 898 if err != nil { 899 return false, err 900 } 901 902 if arg.AccountID.IsNil() { 903 mctx.Debug("IsAccountMobileOnlyLocal called with an empty account id") 904 return false, ErrAccountIDMissing 905 } 906 907 return s.remoter.IsAccountMobileOnly(mctx.Ctx(), arg.AccountID) 908 } 909 910 func (s *Server) SetAccountMobileOnlyLocal(ctx context.Context, arg stellar1.SetAccountMobileOnlyLocalArg) (err error) { 911 mctx, fin, err := s.Preamble(ctx, preambleArg{ 912 RPCName: "SetAccountMobileOnlyLocal", 913 Err: &err, 914 }) 915 defer fin() 916 if err != nil { 917 return err 918 } 919 920 if arg.AccountID.IsNil() { 921 mctx.Debug("SetAccountMobileOnlyLocal called with an empty account id") 922 return ErrAccountIDMissing 923 } 924 925 if err = s.remoter.SetAccountMobileOnly(mctx.Ctx(), arg.AccountID); err != nil { 926 return err 927 } 928 929 if err = s.walletState.UpdateAccountEntries(mctx, "set account mobile only"); err != nil { 930 return err 931 } 932 933 return nil 934 } 935 936 func (s *Server) SetAccountAllDevicesLocal(ctx context.Context, arg stellar1.SetAccountAllDevicesLocalArg) (err error) { 937 mctx, fin, err := s.Preamble(ctx, preambleArg{ 938 RPCName: "SetAccountAllDevicesLocal", 939 Err: &err, 940 }) 941 defer fin() 942 if err != nil { 943 return err 944 } 945 946 if arg.AccountID.IsNil() { 947 mctx.Debug("SetAccountAllDevicesLocal called with an empty account id") 948 return ErrAccountIDMissing 949 } 950 951 if err = s.remoter.MakeAccountAllDevices(mctx.Ctx(), arg.AccountID); err != nil { 952 return err 953 } 954 955 if err = s.walletState.UpdateAccountEntries(mctx, "set account all devices"); err != nil { 956 return err 957 } 958 959 return nil 960 } 961 962 func (s *Server) GetPredefinedInflationDestinationsLocal(ctx context.Context, sessionID int) (ret []stellar1.PredefinedInflationDestination, err error) { 963 mctx, fin, err := s.Preamble(ctx, preambleArg{ 964 RPCName: "GetPredefinedInflationDestinations", 965 Err: &err, 966 RequireWallet: true, 967 }) 968 defer fin() 969 if err != nil { 970 return ret, err 971 } 972 return stellar.GetPredefinedInflationDestinations(mctx) 973 } 974 975 func (s *Server) SetInflationDestinationLocal(ctx context.Context, arg stellar1.SetInflationDestinationLocalArg) (err error) { 976 mctx, fin, err := s.Preamble(ctx, preambleArg{ 977 RPCName: "SetInflationDestinationLocal", 978 Err: &err, 979 RequireWallet: true, 980 }) 981 defer fin() 982 if err != nil { 983 return err 984 } 985 return stellar.SetInflationDestinationLocal(mctx, arg) 986 } 987 988 func (s *Server) GetInflationDestinationLocal(ctx context.Context, arg stellar1.GetInflationDestinationLocalArg) (res stellar1.InflationDestinationResultLocal, err error) { 989 mctx, fin, err := s.Preamble(ctx, preambleArg{ 990 RPCName: "GetInflationDestinationLocal", 991 Err: &err, 992 RequireWallet: false, 993 }) 994 defer fin() 995 if err != nil { 996 return res, err 997 } 998 999 if arg.AccountID.IsNil() { 1000 mctx.Debug("GetInflationDestinationLocal called with an empty account id") 1001 return res, ErrAccountIDMissing 1002 } 1003 1004 return stellar.GetInflationDestination(mctx, arg.AccountID) 1005 } 1006 1007 func (s *Server) AirdropDetailsLocal(ctx context.Context, sessionID int) (resp stellar1.AirdropDetails, err error) { 1008 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1009 RPCName: "AirdropDetailsLocal", 1010 Err: &err, 1011 RequireWallet: false, 1012 }) 1013 defer fin() 1014 if err != nil { 1015 return stellar1.AirdropDetails{}, err 1016 } 1017 1018 isPromoted, details, disclaimer, err := remote.AirdropDetails(mctx) 1019 if err != nil { 1020 return stellar1.AirdropDetails{}, err 1021 } 1022 return stellar1.AirdropDetails{IsPromoted: isPromoted, Details: details, Disclaimer: disclaimer}, nil 1023 1024 } 1025 1026 func (s *Server) AirdropRegisterLocal(ctx context.Context, arg stellar1.AirdropRegisterLocalArg) (err error) { 1027 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1028 RPCName: "AirdropRegisterLocal", 1029 Err: &err, 1030 RequireWallet: true, 1031 }) 1032 defer fin() 1033 if err != nil { 1034 return err 1035 } 1036 go testExperimentalRegistration(mctx) 1037 return remote.AirdropRegister(mctx, arg.Register) 1038 } 1039 1040 func (s *Server) AirdropStatusLocal(ctx context.Context, sessionID int) (status stellar1.AirdropStatus, err error) { 1041 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1042 RPCName: "AirdropStatusLocal", 1043 Err: &err, 1044 RequireWallet: true, 1045 }) 1046 defer fin() 1047 if err != nil { 1048 return stellar1.AirdropStatus{}, err 1049 } 1050 1051 return stellar.AirdropStatus(mctx) 1052 } 1053 1054 func (s *Server) AddTrustlineLocal(ctx context.Context, arg stellar1.AddTrustlineLocalArg) (err error) { 1055 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1056 RPCName: "AddTrustline", 1057 Err: &err, 1058 RequireWallet: true, 1059 }) 1060 defer fin() 1061 if err != nil { 1062 return err 1063 } 1064 1065 return stellar.AddTrustlineLocal(mctx, arg) 1066 } 1067 1068 func (s *Server) DeleteTrustlineLocal(ctx context.Context, arg stellar1.DeleteTrustlineLocalArg) (err error) { 1069 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1070 RPCName: "AddTrustline", 1071 Err: &err, 1072 RequireWallet: true, 1073 }) 1074 defer fin() 1075 if err != nil { 1076 return err 1077 } 1078 return stellar.DeleteTrustlineLocal(mctx, arg) 1079 } 1080 1081 func (s *Server) ChangeTrustlineLimitLocal(ctx context.Context, arg stellar1.ChangeTrustlineLimitLocalArg) (err error) { 1082 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1083 RPCName: "ChangeTrustlineLimit", 1084 Err: &err, 1085 RequireWallet: true, 1086 }) 1087 defer fin() 1088 if err != nil { 1089 return err 1090 } 1091 return stellar.ChangeTrustlineLimitLocal(mctx, arg) 1092 } 1093 1094 func (s *Server) GetTrustlinesLocal(ctx context.Context, arg stellar1.GetTrustlinesLocalArg) (ret []stellar1.Balance, err error) { 1095 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1096 RPCName: "GetTrustlinesLocal", 1097 Err: &err, 1098 }) 1099 defer fin() 1100 if err != nil { 1101 return ret, err 1102 } 1103 return s.getTrustlinesAccountID(mctx, arg.AccountID) 1104 } 1105 1106 func (s *Server) GetTrustlinesForRecipientLocal(ctx context.Context, arg stellar1.GetTrustlinesForRecipientLocalArg) (ret stellar1.RecipientTrustlinesLocal, err error) { 1107 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1108 RPCName: "GetTrustlinesByRecipientLocal", 1109 Err: &err, 1110 }) 1111 defer fin() 1112 if err != nil { 1113 return ret, err 1114 } 1115 1116 recipient, err := stellar.LookupRecipient(mctx, stellarcommon.RecipientInput(arg.Recipient), false) 1117 if err != nil { 1118 return ret, err 1119 } 1120 if recipient.AccountID == nil { 1121 return ret, errors.New("recipient has no stellar accounts") 1122 } 1123 1124 trustlines, err := s.getTrustlinesAccountID(mctx, stellar1.AccountID(*recipient.AccountID)) 1125 if err != nil { 1126 return ret, err 1127 } 1128 for _, t := range trustlines { 1129 if !t.IsAuthorized { 1130 continue 1131 } 1132 ret.Trustlines = append(ret.Trustlines, t) 1133 } 1134 1135 if recipient.User != nil { 1136 ret.RecipientType = stellar1.ParticipantType_KEYBASE 1137 } else { 1138 ret.RecipientType = stellar1.ParticipantType_STELLAR 1139 } 1140 1141 return ret, nil 1142 } 1143 1144 func (s *Server) getTrustlinesAccountID(mctx libkb.MetaContext, accountID stellar1.AccountID) (ret []stellar1.Balance, err error) { 1145 balances, err := s.remoter.Balances(mctx.Ctx(), accountID) 1146 if err != nil { 1147 return ret, err 1148 } 1149 if len(balances) == 0 { 1150 // Account is not on the network - no balances means no trustlines. 1151 return ret, nil 1152 } 1153 ret = make([]stellar1.Balance, 0, len(balances)-1) 1154 for _, balance := range balances { 1155 if !balance.Asset.IsNativeXLM() { 1156 ret = append(ret, balance) 1157 } 1158 } 1159 return ret, nil 1160 } 1161 1162 func (s *Server) FindPaymentPathLocal(ctx context.Context, arg stellar1.FindPaymentPathLocalArg) (res stellar1.PaymentPathLocal, err error) { 1163 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1164 RPCName: "FindPaymentPathLocal", 1165 Err: &err, 1166 RequireWallet: true, 1167 }) 1168 defer fin() 1169 if err != nil { 1170 return stellar1.PaymentPathLocal{}, err 1171 } 1172 1173 path, err := stellar.FindPaymentPath(mctx, s.remoter, arg.From, arg.To, arg.SourceAsset, arg.DestinationAsset, arg.Amount) 1174 if err != nil { 1175 return stellar1.PaymentPathLocal{}, err 1176 } 1177 1178 res.FullPath = path 1179 1180 res.SourceDisplay, err = stellar.FormatAmount(mctx, path.SourceAmount, false, stellarnet.Round) 1181 if err != nil { 1182 return stellar1.PaymentPathLocal{}, err 1183 } 1184 res.SourceMaxDisplay, err = stellar.FormatAmount(mctx, path.SourceAmountMax, false, stellarnet.Round) 1185 if err != nil { 1186 return stellar1.PaymentPathLocal{}, err 1187 } 1188 res.DestinationDisplay, err = stellar.FormatAmount(mctx, path.DestinationAmount, false, stellarnet.Round) 1189 if err != nil { 1190 return stellar1.PaymentPathLocal{}, err 1191 } 1192 1193 destAmt, err := stellarnet.ParseAmount(path.DestinationAmount) 1194 if err != nil { 1195 return stellar1.PaymentPathLocal{}, err 1196 } 1197 srcAmt, err := stellarnet.ParseAmount(path.SourceAmount) 1198 if err != nil { 1199 return stellar1.PaymentPathLocal{}, err 1200 } 1201 srcAmt.Quo(srcAmt, destAmt) 1202 1203 exchangeRateLeft, err := stellar.FormatAmountDescriptionAsset(mctx, "1", path.DestinationAsset) 1204 if err != nil { 1205 return stellar1.PaymentPathLocal{}, err 1206 } 1207 exchangeRateRight, err := stellar.FormatAmountDescriptionAsset(mctx, srcAmt.FloatString(7), path.SourceAsset) 1208 1209 if err != nil { 1210 return stellar1.PaymentPathLocal{}, err 1211 } 1212 res.ExchangeRate = fmt.Sprintf("%s = %s", exchangeRateLeft, exchangeRateRight) 1213 1214 if len(path.SourceInsufficientBalance) > 0 { 1215 availableToSpend, err := stellar.FormatAmountDescriptionAssetEx2(mctx, path.SourceInsufficientBalance, path.SourceAsset) 1216 if err != nil { 1217 return stellar1.PaymentPathLocal{}, err 1218 } 1219 res.AmountError = fmt.Sprintf("Your balance is not sufficient. You only have %s available to spend.", availableToSpend) 1220 } 1221 1222 return res, nil 1223 } 1224 1225 func (s *Server) FuzzyAssetSearchLocal(ctx context.Context, arg stellar1.FuzzyAssetSearchLocalArg) (res []stellar1.Asset, err error) { 1226 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1227 RPCName: "FuzzyAssetSearchLocal", 1228 Err: &err, 1229 RequireWallet: true, 1230 }) 1231 defer fin() 1232 if err != nil { 1233 return res, err 1234 } 1235 1236 remoteArg := stellar1.FuzzyAssetSearchArg{ 1237 SearchString: arg.SearchString, 1238 } 1239 return stellar.FuzzyAssetSearch(mctx, s.remoter, remoteArg) 1240 } 1241 1242 func (s *Server) ListPopularAssetsLocal(ctx context.Context, sessionID int) (res stellar1.AssetListResult, err error) { 1243 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1244 RPCName: "ListPopularAssetsLocal", 1245 Err: &err, 1246 RequireWallet: true, 1247 }) 1248 defer fin() 1249 if err != nil { 1250 return res, err 1251 } 1252 1253 remoteArg := stellar1.ListPopularAssetsArg{} 1254 return stellar.ListPopularAssets(mctx, s.remoter, remoteArg) 1255 } 1256 1257 func (s *Server) GetStaticConfigLocal(ctx context.Context) (res stellar1.StaticConfig, err error) { 1258 return stellar1.StaticConfig{ 1259 PaymentNoteMaxLength: libkb.MaxStellarPaymentNoteLength, 1260 RequestNoteMaxLength: msgchecker.RequestPaymentTextMaxLength, 1261 PublicMemoMaxLength: libkb.MaxStellarPaymentPublicNoteLength, 1262 }, nil 1263 } 1264 1265 func (s *Server) AssetDepositLocal(ctx context.Context, arg stellar1.AssetDepositLocalArg) (res stellar1.AssetActionResultLocal, err error) { 1266 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1267 RPCName: "AssetDepositLocal", 1268 Err: &err, 1269 RequireWallet: true, 1270 }) 1271 defer fin() 1272 if err != nil { 1273 return res, err 1274 } 1275 1276 ai, err := s.prepareAnchorInteractor(mctx, arg.AccountID, arg.Asset) 1277 if err != nil { 1278 return res, err 1279 } 1280 1281 return ai.Deposit(mctx) 1282 } 1283 1284 func (s *Server) AssetWithdrawLocal(ctx context.Context, arg stellar1.AssetWithdrawLocalArg) (res stellar1.AssetActionResultLocal, err error) { 1285 mctx, fin, err := s.Preamble(ctx, preambleArg{ 1286 RPCName: "AssetWithdrawLocal", 1287 Err: &err, 1288 RequireWallet: true, 1289 }) 1290 defer fin() 1291 if err != nil { 1292 return res, err 1293 } 1294 1295 ai, err := s.prepareAnchorInteractor(mctx, arg.AccountID, arg.Asset) 1296 if err != nil { 1297 return res, err 1298 } 1299 1300 return ai.Withdraw(mctx) 1301 } 1302 1303 func (s *Server) prepareAnchorInteractor(mctx libkb.MetaContext, accountID stellar1.AccountID, asset stellar1.Asset) (*anchorInteractor, error) { 1304 // check that the user owns accountID 1305 own, _, err := stellar.OwnAccountCached(mctx, accountID) 1306 if err != nil { 1307 return nil, err 1308 } 1309 if !own { 1310 return nil, errors.New("caller doesn't own account") 1311 } 1312 1313 // check that accountID has a trustline to the asset 1314 trustlines, err := s.getTrustlinesAccountID(mctx, accountID) 1315 if err != nil { 1316 return nil, err 1317 } 1318 var fullAsset stellar1.Asset 1319 for _, tl := range trustlines { 1320 if tl.Asset.Code == asset.Code && tl.Asset.Issuer == asset.Issuer { 1321 fullAsset = tl.Asset 1322 break 1323 } 1324 } 1325 if fullAsset.Code == "" || fullAsset.Issuer == "" { 1326 return nil, errors.New("caller doesn't have trustline to asset") 1327 } 1328 1329 var seed *stellar1.SecretKey 1330 if fullAsset.AuthEndpoint != "" { 1331 // get the secret key for account id 1332 _, bundle, err := stellar.LookupSender(mctx, accountID) 1333 if err != nil { 1334 return nil, err 1335 } 1336 seed = &bundle.Signers[0] 1337 } 1338 1339 // all good from the user's perspective, proceed... 1340 return newAnchorInteractor(accountID, seed, fullAsset), nil 1341 } 1342 1343 func testExperimentalRegistration(mctx libkb.MetaContext) { 1344 err := airdrop.NewClient().Register(mctx.BackgroundWithLogTags()) 1345 if err != nil { 1346 mctx.Info("Error testExperimentalRegistration: %s", err.Error()) 1347 } 1348 }