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  }