github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/frontend_test.go (about)

     1  package stellarsvc
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"sort"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/davecgh/go-spew/spew"
    12  	"github.com/keybase/client/go/engine"
    13  	"github.com/keybase/client/go/kbtest"
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/protocol/chat1"
    16  	"github.com/keybase/client/go/protocol/keybase1"
    17  	"github.com/keybase/client/go/protocol/stellar1"
    18  	"github.com/keybase/client/go/stellar"
    19  	"github.com/keybase/client/go/stellar/remote"
    20  	"github.com/keybase/stellarnet"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func acceptDisclaimer(tc *TestContext) {
    26  	// NOTE: this also creates a wallet
    27  	err := tc.Srv.AcceptDisclaimerLocal(context.Background(), 0)
    28  	require.NoError(tc.T, err)
    29  }
    30  
    31  func TestGetWalletAccountsLocal(t *testing.T) {
    32  	tcs, cleanup := setupNTests(t, 1)
    33  	defer cleanup()
    34  
    35  	acceptDisclaimer(tcs[0])
    36  
    37  	accountID := tcs[0].Backend.AddAccount(tcs[0].Fu.GetUID())
    38  
    39  	err := tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
    40  		SecretKey:   tcs[0].Backend.SecretKey(accountID),
    41  		MakePrimary: true,
    42  		Name:        "qq",
    43  	})
    44  	require.NoError(t, err)
    45  
    46  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
    47  
    48  	accts, err := tcs[0].Srv.GetWalletAccountsLocal(context.Background(), 0)
    49  	require.NoError(t, err)
    50  
    51  	require.Len(t, accts, 2)
    52  	require.Equal(t, accountID, accts[0].AccountID, accountID)
    53  	require.True(t, accts[0].IsDefault)
    54  	require.Equal(t, "qq", accts[0].Name)
    55  	require.Equal(t, stellar1.AccountMode_USER, accts[0].AccountMode)
    56  	require.Equal(t, false, accts[0].AccountModeEditable)
    57  	require.Equal(t, false, accts[0].DeviceReadOnly)
    58  	require.True(t, accts[0].CanAddTrustline)
    59  	require.Equal(t, "10,000.00 XLM", accts[0].BalanceDescription)
    60  	currencyLocal := accts[0].CurrencyLocal
    61  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), currencyLocal.Code)
    62  	require.Equal(t, "US Dollar", currencyLocal.Name)
    63  	require.Equal(t, "USD ($)", currencyLocal.Description)
    64  	require.Equal(t, "$", currencyLocal.Symbol)
    65  	require.NotEmpty(t, accts[0].Seqno)
    66  
    67  	require.False(t, accts[1].IsDefault)
    68  	require.Equal(t, firstAccountName(t, tcs[0]), accts[1].Name)
    69  	require.Equal(t, stellar1.AccountMode_USER, accts[1].AccountMode)
    70  	require.Equal(t, false, accts[1].AccountModeEditable)
    71  	require.Equal(t, false, accts[1].DeviceReadOnly)
    72  	require.Equal(t, "0 XLM", accts[1].BalanceDescription)
    73  	require.False(t, accts[1].CanAddTrustline)
    74  	currencyLocal = accts[1].CurrencyLocal
    75  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), currencyLocal.Code)
    76  	require.NotEmpty(t, accts[1].Seqno)
    77  
    78  	// test the singular version as well
    79  	argDetails := stellar1.GetWalletAccountLocalArg{AccountID: accountID}
    80  	details, err := tcs[0].Srv.GetWalletAccountLocal(context.Background(), argDetails)
    81  	require.NoError(t, err)
    82  	require.Equal(t, "qq", details.Name)
    83  	require.Equal(t, stellar1.AccountMode_USER, details.AccountMode)
    84  	require.Equal(t, false, accts[1].AccountModeEditable)
    85  	require.Equal(t, false, accts[1].DeviceReadOnly)
    86  	require.True(t, details.IsDefault)
    87  	require.Equal(t, "10,000.00 XLM", details.BalanceDescription)
    88  	require.NotEmpty(t, details.Seqno)
    89  	currencyLocal = details.CurrencyLocal
    90  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), currencyLocal.Code)
    91  	require.True(t, details.IsFunded)
    92  	require.True(t, details.CanSubmitTx)
    93  	require.True(t, details.CanAddTrustline)
    94  
    95  	argDetails.AccountID = accts[1].AccountID
    96  	details, err = tcs[0].Srv.GetWalletAccountLocal(context.Background(), argDetails)
    97  	require.NoError(t, err)
    98  	require.Equal(t, firstAccountName(t, tcs[0]), details.Name)
    99  	require.False(t, details.IsDefault)
   100  	require.Equal(t, "0 XLM", details.BalanceDescription)
   101  	require.NotEmpty(t, details.Seqno)
   102  	currencyLocal = details.CurrencyLocal
   103  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), currencyLocal.Code)
   104  	require.False(t, details.IsFunded)
   105  	require.False(t, details.CanSubmitTx)
   106  	require.False(t, details.CanAddTrustline)
   107  
   108  	// Add another account that we will add 1 XLM, enough to be funded but not
   109  	// enough to make any txs out of it.
   110  	anotherAccountID := tcs[0].Backend.AddAccountEmpty(t, tcs[0].Fu.GetUID())
   111  	err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   112  		SecretKey:   tcs[0].Backend.SecretKey(anotherAccountID),
   113  		MakePrimary: false,
   114  		Name:        "funded but 0 avail.",
   115  	})
   116  	require.NoError(t, err)
   117  
   118  	tcs[0].Backend.Gift(anotherAccountID, "1")
   119  
   120  	argDetails.AccountID = anotherAccountID
   121  	details, err = tcs[0].Srv.GetWalletAccountLocal(context.Background(), argDetails)
   122  	require.NoError(t, err)
   123  	require.Equal(t, "1 XLM", details.BalanceDescription)
   124  	require.True(t, details.IsFunded)
   125  	require.False(t, details.CanSubmitTx)
   126  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), details.CurrencyLocal.Code)
   127  	require.False(t, details.CanAddTrustline)
   128  
   129  	// Add another account that we will add 10 XLM, enough to be funded and can create trustlines
   130  	yetAnotherAccountID := tcs[0].Backend.AddAccountEmpty(t, tcs[0].Fu.GetUID())
   131  	err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   132  		SecretKey:   tcs[0].Backend.SecretKey(yetAnotherAccountID),
   133  		MakePrimary: false,
   134  		Name:        "another funded but 0 avail.",
   135  	})
   136  	require.NoError(t, err)
   137  
   138  	tcs[0].Backend.Gift(yetAnotherAccountID, "10")
   139  	argDetails.AccountID = yetAnotherAccountID
   140  	details, err = tcs[0].Srv.GetWalletAccountLocal(context.Background(), argDetails)
   141  	require.NoError(t, err)
   142  	require.Equal(t, "10 XLM", details.BalanceDescription)
   143  	require.True(t, details.IsFunded)
   144  	require.True(t, details.CanSubmitTx)
   145  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), details.CurrencyLocal.Code)
   146  	require.True(t, details.CanAddTrustline)
   147  }
   148  
   149  func TestGetAccountAssetsLocalWithBalance(t *testing.T) {
   150  	tcs, cleanup := setupNTests(t, 1)
   151  	defer cleanup()
   152  
   153  	acceptDisclaimer(tcs[0])
   154  
   155  	accountID := tcs[0].Backend.AddAccount(tcs[0].Fu.GetUID())
   156  
   157  	err := tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   158  		SecretKey:   tcs[0].Backend.SecretKey(accountID),
   159  		MakePrimary: true,
   160  		Name:        "qq",
   161  	})
   162  	require.NoError(t, err)
   163  
   164  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   165  
   166  	assets, err := tcs[0].Srv.GetAccountAssetsLocal(context.Background(), stellar1.GetAccountAssetsLocalArg{AccountID: accountID})
   167  	require.NoError(t, err)
   168  
   169  	require.Len(t, assets, 1)
   170  	require.Equal(t, "Lumens", assets[0].Name)
   171  	require.Equal(t, "XLM", assets[0].AssetCode)
   172  	require.Equal(t, "Stellar network", assets[0].IssuerName)
   173  	require.Equal(t, "", assets[0].IssuerAccountID)
   174  	require.Equal(t, "10,000.00", assets[0].BalanceTotal)
   175  	require.Equal(t, "9,998.9999900", assets[0].BalanceAvailableToSend)
   176  	require.Equal(t, "USD", assets[0].WorthCurrency)
   177  	require.Equal(t, "$3,183.28 USD", assets[0].Worth)
   178  	require.Equal(t, "$3,182.96 USD", assets[0].AvailableToSendWorth)
   179  }
   180  
   181  func TestGetAccountAssetsLocalWithCHFBalance(t *testing.T) {
   182  	tcs, cleanup := setupNTests(t, 1)
   183  	defer cleanup()
   184  
   185  	acceptDisclaimer(tcs[0])
   186  
   187  	accountID := tcs[0].Backend.AddAccount(tcs[0].Fu.GetUID())
   188  
   189  	err := tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   190  		SecretKey:   tcs[0].Backend.SecretKey(accountID),
   191  		MakePrimary: true,
   192  		Name:        "qq",
   193  	})
   194  	require.NoError(t, err)
   195  
   196  	curr, err := tcs[0].Srv.ChangeDisplayCurrencyLocal(context.Background(), stellar1.ChangeDisplayCurrencyLocalArg{
   197  		AccountID: accountID,
   198  		Currency:  stellar1.OutsideCurrencyCode("CHF"),
   199  	})
   200  	require.NoError(t, err)
   201  	require.Equal(t, stellar1.OutsideCurrencyCode("CHF"), curr.Code)
   202  
   203  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   204  
   205  	assets, err := tcs[0].Srv.GetAccountAssetsLocal(context.Background(), stellar1.GetAccountAssetsLocalArg{AccountID: accountID})
   206  	require.NoError(t, err)
   207  
   208  	require.Len(t, assets, 1)
   209  	require.Equal(t, "Lumens", assets[0].Name)
   210  	require.Equal(t, "XLM", assets[0].AssetCode)
   211  	require.Equal(t, "Stellar network", assets[0].IssuerName)
   212  	require.Equal(t, "", assets[0].IssuerAccountID)
   213  	require.Equal(t, "10,000.00", assets[0].BalanceTotal)
   214  	require.Equal(t, "9,998.9999900", assets[0].BalanceAvailableToSend)
   215  	require.Equal(t, "CHF", assets[0].WorthCurrency)
   216  	require.Equal(t, "3,183.28 CHF", assets[0].Worth)
   217  	require.Equal(t, "3,182.96 CHF", assets[0].AvailableToSendWorth)
   218  
   219  	// changing currency also updates DisplayCurrency in GetWalletAccountLocal
   220  	argDetails := stellar1.GetWalletAccountLocalArg{AccountID: accountID}
   221  	details, err := tcs[0].Srv.GetWalletAccountLocal(context.Background(), argDetails)
   222  	require.NoError(t, err)
   223  	currencyLocal := details.CurrencyLocal
   224  	require.Equal(t, stellar1.OutsideCurrencyCode("CHF"), currencyLocal.Code)
   225  	require.Equal(t, "Swiss Franc", currencyLocal.Name)
   226  	require.Equal(t, "CHF (CHF)", currencyLocal.Description)
   227  	require.Equal(t, "CHF", currencyLocal.Symbol)
   228  }
   229  
   230  func TestGetAccountAssetsLocalEmptyBalance(t *testing.T) {
   231  	tcs, cleanup := setupNTests(t, 1)
   232  	defer cleanup()
   233  
   234  	acceptDisclaimer(tcs[0])
   235  
   236  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   237  
   238  	accounts, err := tcs[0].Srv.GetWalletAccountsLocal(context.Background(), 0)
   239  	require.NoError(t, err)
   240  	accountID := accounts[0].AccountID
   241  
   242  	assets, err := tcs[0].Srv.GetAccountAssetsLocal(context.Background(), stellar1.GetAccountAssetsLocalArg{AccountID: accountID})
   243  	require.NoError(t, err)
   244  
   245  	require.Len(t, assets, 1)
   246  	require.Equal(t, "Lumens", assets[0].Name)
   247  	require.Equal(t, "XLM", assets[0].AssetCode)
   248  	require.Equal(t, "Stellar network", assets[0].IssuerName)
   249  	require.Equal(t, "", assets[0].IssuerAccountID)
   250  	require.Equal(t, "0", assets[0].BalanceTotal)
   251  	require.Equal(t, "0", assets[0].BalanceAvailableToSend)
   252  	require.Equal(t, "USD", assets[0].WorthCurrency)
   253  	require.Equal(t, "$0.00 USD", assets[0].Worth)
   254  	require.Equal(t, "$0.00 USD", assets[0].AvailableToSendWorth)
   255  }
   256  
   257  func TestGetDisplayCurrenciesLocal(t *testing.T) {
   258  	tcs, cleanup := setupNTests(t, 1)
   259  	defer cleanup()
   260  
   261  	currencies, err := tcs[0].Srv.GetDisplayCurrenciesLocal(context.Background(), 0)
   262  	require.NoError(t, err)
   263  
   264  	require.Len(t, currencies, 32)
   265  	// USD should go first.
   266  	require.Equal(t, "USD ($)", currencies[0].Description)
   267  	require.Equal(t, stellar1.OutsideCurrencyCode("USD"), currencies[0].Code)
   268  	require.Equal(t, "$", currencies[0].Symbol)
   269  	// Rest is in alphabetical order.
   270  	require.Equal(t, "AUD ($)", currencies[1].Description)
   271  	require.Equal(t, stellar1.OutsideCurrencyCode("AUD"), currencies[1].Code)
   272  	require.Equal(t, "$", currencies[1].Symbol)
   273  }
   274  
   275  func TestValidateAccountID(t *testing.T) {
   276  	tcs, cleanup := setupNTests(t, 1)
   277  	defer cleanup()
   278  
   279  	units := []struct {
   280  		s  string
   281  		ok bool
   282  	}{
   283  		{"GCTXTGK45J3PAPOPK7XLZVZVJYAFTSTWRDWOCRPN5ICQWHQLTBLNN4AQ", true},
   284  		{"gctxtgk45j3papopk7xlzvzvjyaftstwrdwocrpn5icqwhqltblnn4aq", true},
   285  		{"GCTXTGK45J3PAPOPK7XLZVZVJYAFTSTWRDWOCRPN5ICQWHQLTBLNN4A", false},
   286  		{"GCTXTGK45J3PAPOPK7XLZVZVJYAFTSTWRDWOCRPN5ICQWHQLTBLNN4AQQ", false},
   287  		{"GCTXTGK45J3PAPOPK7XLZVZVJYAFTSTWRDWOCRPN5ICQWHQLTBLNN4AR", false},
   288  		{"", false},
   289  		{"a", false},
   290  	}
   291  	for _, u := range units {
   292  		t.Logf("unit: %v", u)
   293  		err := tcs[0].Srv.ValidateAccountIDLocal(context.Background(), stellar1.ValidateAccountIDLocalArg{AccountID: stellar1.AccountID(u.s)})
   294  		if u.ok {
   295  			require.NoError(t, err)
   296  		} else {
   297  			require.Error(t, err)
   298  		}
   299  	}
   300  }
   301  
   302  func TestValidateSecretKey(t *testing.T) {
   303  	tcs, cleanup := setupNTests(t, 1)
   304  	defer cleanup()
   305  
   306  	units := []struct {
   307  		s  string
   308  		ok bool
   309  	}{
   310  		{"SDXUQS3V6JVO7IN6ZGYEGAUMHJBZK7O7644XIRSCSQ5PFONFK3LO2SCY", true},
   311  		{"sdxuqs3v6jvo7in6zgyegaumhjbzk7o7644xirscsq5pfonfk3lo2scy", true},
   312  		{"SDXUQS3V6JVO7IN6ZGYEGAUMHJBZK7O7644XIRSCSQ5PFONFK3LO2SC", false},
   313  		{"SDXUQS3V6JVO7IN6ZGYEGAUMHJBZK7O7644XIRSCSQ5PFONFK3LO2SCYY", false},
   314  		{"SDXUQS3V6JVO7IN6ZGYEGAUMHJBZK7O7644XIRSCSQ5PFONFK3LO2SCZ", false},
   315  		{"", false},
   316  		{"a", false},
   317  	}
   318  	for _, u := range units {
   319  		t.Logf("unit: %v", u)
   320  		err := tcs[0].Srv.ValidateSecretKeyLocal(context.Background(), stellar1.ValidateSecretKeyLocalArg{SecretKey: stellar1.SecretKey(u.s)})
   321  		if u.ok {
   322  			require.NoError(t, err)
   323  		} else {
   324  			require.Error(t, err)
   325  		}
   326  	}
   327  }
   328  
   329  func TestChangeWalletName(t *testing.T) {
   330  	tcs, cleanup := setupNTests(t, 1)
   331  	defer cleanup()
   332  
   333  	acceptDisclaimer(tcs[0])
   334  
   335  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   336  
   337  	accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
   338  	require.NoError(t, err)
   339  	require.Len(t, accs, 1)
   340  	require.Equal(t, accs[0].Name, firstAccountName(t, tcs[0]))
   341  
   342  	chk := func(name string, expected string) {
   343  		err = tcs[0].Srv.ValidateAccountNameLocal(context.Background(), stellar1.ValidateAccountNameLocalArg{Name: name})
   344  		if expected == "" {
   345  			require.NoError(t, err)
   346  		} else {
   347  			require.Error(t, err)
   348  			require.Equal(t, expected, err.Error())
   349  		}
   350  	}
   351  
   352  	chk("", "name required")
   353  	chk("office lunch money", "")
   354  	chk("savings", "")
   355  	res, err := tcs[0].Srv.ChangeWalletAccountNameLocal(context.Background(), stellar1.ChangeWalletAccountNameLocalArg{
   356  		AccountID: accs[0].AccountID,
   357  		NewName:   "office lunch money",
   358  	})
   359  	require.NoError(t, err)
   360  	require.Equal(t, "office lunch money", res.Name)
   361  	chk("office lunch money", "you already have an account with that name")
   362  	chk("career debter", "")
   363  
   364  	// check to make sure that the stored entry in wallet state also changed.
   365  	name, err := tcs[0].Srv.walletState.AccountName(accs[0].AccountID)
   366  	require.NoError(t, err)
   367  	require.Equal(t, "office lunch money", name)
   368  
   369  	accs, err = tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
   370  	require.NoError(t, err)
   371  	require.Len(t, accs, 1)
   372  	require.Equal(t, accs[0].Name, "office lunch money")
   373  
   374  	// Try invalid argument
   375  	invalidAccID, _ := randomStellarKeypair()
   376  	_, err = tcs[0].Srv.ChangeWalletAccountNameLocal(context.Background(), stellar1.ChangeWalletAccountNameLocalArg{
   377  		AccountID: invalidAccID,
   378  		NewName:   "savings",
   379  	})
   380  	require.Error(t, err)
   381  	chk("savings", "")
   382  
   383  	chk("an account used for savi", "")
   384  	chk("an account used for savin", "account name can be 24 characters at the longest but was 25")
   385  }
   386  
   387  func TestSetAccountAsDefault(t *testing.T) {
   388  	tcs, cleanup := setupNTests(t, 2)
   389  	defer cleanup()
   390  
   391  	acceptDisclaimer(tcs[0])
   392  
   393  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   394  
   395  	accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
   396  	require.NoError(t, err)
   397  	require.Len(t, accs, 1)
   398  
   399  	require.True(t, accs[0].IsPrimary)
   400  
   401  	// Should work for accounts that are already primary and not post
   402  	// a bundle.
   403  	res, err := tcs[0].Srv.SetWalletAccountAsDefaultLocal(context.Background(), stellar1.SetWalletAccountAsDefaultLocalArg{
   404  		AccountID: accs[0].AccountID,
   405  	})
   406  	require.NoError(t, err)
   407  	require.Equal(t, accs[0].AccountID, res[0].AccountID)
   408  
   409  	mctx := libkb.NewMetaContextBackground(tcs[0].G)
   410  	bundle, err := remote.FetchSecretlessBundle(mctx)
   411  	require.NoError(t, err)
   412  	require.EqualValues(t, 1, bundle.Revision)
   413  
   414  	// Test invalid arguments
   415  	invalidAccID, _ := randomStellarKeypair()
   416  	_, err = tcs[0].Srv.SetWalletAccountAsDefaultLocal(context.Background(), stellar1.SetWalletAccountAsDefaultLocalArg{
   417  		AccountID: invalidAccID,
   418  	})
   419  	require.Error(t, err)
   420  
   421  	_, err = tcs[0].Srv.SetWalletAccountAsDefaultLocal(context.Background(), stellar1.SetWalletAccountAsDefaultLocalArg{
   422  		AccountID: stellar1.AccountID(""),
   423  	})
   424  	require.Error(t, err)
   425  
   426  	additionalAccs := []stellar1.AccountID{
   427  		tcs[0].Backend.AddAccountEmpty(t, tcs[0].Fu.GetUID()),
   428  		tcs[0].Backend.AddAccountEmpty(t, tcs[0].Fu.GetUID()),
   429  	}
   430  
   431  	for _, v := range additionalAccs {
   432  		err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   433  			SecretKey:   tcs[0].Backend.SecretKey(v),
   434  			MakePrimary: false,
   435  			Name:        "qq",
   436  		})
   437  		require.NoError(t, err)
   438  	}
   439  
   440  	for i := len(additionalAccs) - 1; i >= 0; i-- {
   441  		v := additionalAccs[i]
   442  		arg := stellar1.SetWalletAccountAsDefaultLocalArg{
   443  			AccountID: v,
   444  		}
   445  		_, err := tcs[0].Srv.SetWalletAccountAsDefaultLocal(context.Background(), arg)
   446  		require.NoError(t, err)
   447  
   448  		accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
   449  		require.NoError(t, err)
   450  		require.Len(t, accs, 3)
   451  		for _, acc := range accs {
   452  			require.Equal(t, acc.IsPrimary, acc.AccountID == v)
   453  		}
   454  	}
   455  
   456  	// Expecting additionalAccs[0] to be default account. Lookup
   457  	// public Stellar address as another user.
   458  	u0, err := tcs[1].G.LoadUserByUID(tcs[0].Fu.User.GetUID())
   459  	require.NoError(t, err)
   460  	u0addr := u0.StellarAccountID()
   461  	require.NotNil(t, u0addr)
   462  	require.Equal(t, additionalAccs[0], *u0addr)
   463  
   464  	isPrimary, err := tcs[0].Srv.walletState.IsPrimary(additionalAccs[0])
   465  	require.NoError(t, err)
   466  	require.True(t, isPrimary)
   467  }
   468  
   469  func testCreateOrLinkNewAccount(t *testing.T, create bool) {
   470  	tcs, cleanup := setupNTests(t, 1)
   471  	defer cleanup()
   472  
   473  	acceptDisclaimer(tcs[0])
   474  
   475  	// link a new account
   476  	var accID stellar1.AccountID
   477  	var err error
   478  	accName := "my other account"
   479  	if create {
   480  		// create new account
   481  		arg := stellar1.CreateWalletAccountLocalArg{
   482  			Name: accName,
   483  		}
   484  		accID, err = tcs[0].Srv.CreateWalletAccountLocal(context.Background(), arg)
   485  		require.NoError(t, err)
   486  	} else {
   487  		a1, s1 := randomStellarKeypair()
   488  		arg := stellar1.LinkNewWalletAccountLocalArg{
   489  			SecretKey: s1,
   490  			Name:      accName,
   491  		}
   492  		accID, err = tcs[0].Srv.LinkNewWalletAccountLocal(context.Background(), arg)
   493  		require.NoError(t, err)
   494  		require.Equal(t, a1, accID)
   495  	}
   496  
   497  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   498  
   499  	accts, err := tcs[0].Srv.GetWalletAccountsLocal(context.Background(), 0)
   500  	require.NoError(t, err)
   501  
   502  	require.Len(t, accts, 2)
   503  	require.True(t, accts[0].IsDefault)
   504  	require.Equal(t, firstAccountName(t, tcs[0]), accts[0].Name)
   505  	require.Equal(t, "0 XLM", accts[0].BalanceDescription)
   506  	require.False(t, accts[1].IsDefault)
   507  	require.Equal(t, accID, accts[1].AccountID)
   508  	require.Equal(t, accName, accts[1].Name)
   509  	require.Equal(t, "0 XLM", accts[1].BalanceDescription)
   510  }
   511  
   512  func TestLinkNewWalletAccountLocal(t *testing.T) {
   513  	testCreateOrLinkNewAccount(t, false /* create */)
   514  }
   515  
   516  func TestCreateNewWalletAccountLocal(t *testing.T) {
   517  	testCreateOrLinkNewAccount(t, true /* create */)
   518  }
   519  
   520  func TestDeleteWallet(t *testing.T) {
   521  	tcs, cleanup := setupNTests(t, 1)
   522  	defer cleanup()
   523  
   524  	acceptDisclaimer(tcs[0])
   525  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   526  	accID := getPrimaryAccountID(tcs[0])
   527  
   528  	// Cannot delete the only account (also primary).
   529  	err := tcs[0].Srv.DeleteWalletAccountLocal(context.Background(), stellar1.DeleteWalletAccountLocalArg{
   530  		AccountID:        accID,
   531  		UserAcknowledged: "yes",
   532  	})
   533  	require.Error(t, err)
   534  
   535  	// Cannot delete account that doesnt exist.
   536  	invalidAccID, _ := randomStellarKeypair()
   537  	err = tcs[0].Srv.DeleteWalletAccountLocal(context.Background(), stellar1.DeleteWalletAccountLocalArg{
   538  		AccountID:        invalidAccID,
   539  		UserAcknowledged: "yes",
   540  	})
   541  	require.Error(t, err)
   542  
   543  	// Add new account, make it primary, now first account should be
   544  	// deletable.
   545  	accID2 := tcs[0].Backend.AddAccountEmpty(t, tcs[0].Fu.GetUID())
   546  	err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   547  		SecretKey:   tcs[0].Backend.SecretKey(accID2),
   548  		MakePrimary: true,
   549  		Name:        "qq",
   550  	})
   551  	require.NoError(t, err)
   552  
   553  	// First try without `UserAcknowledged`.
   554  	err = tcs[0].Srv.DeleteWalletAccountLocal(context.Background(), stellar1.DeleteWalletAccountLocalArg{
   555  		AccountID: accID,
   556  	})
   557  	require.Error(t, err)
   558  
   559  	err = tcs[0].Srv.DeleteWalletAccountLocal(context.Background(), stellar1.DeleteWalletAccountLocalArg{
   560  		AccountID:        accID,
   561  		UserAcknowledged: "yes",
   562  	})
   563  	require.NoError(t, err)
   564  
   565  	accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
   566  	require.NoError(t, err)
   567  	require.Len(t, accs, 1)
   568  	require.Equal(t, accs[0].AccountID, accID2)
   569  	require.True(t, accs[0].IsPrimary)
   570  }
   571  
   572  func TestChangeDisplayCurrency(t *testing.T) {
   573  	tcs, cleanup := setupNTests(t, 2)
   574  	defer cleanup()
   575  
   576  	acceptDisclaimer(tcs[0])
   577  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   578  	accID := getPrimaryAccountID(tcs[0])
   579  
   580  	// Try invalid currency first.
   581  	_, err := tcs[0].Srv.ChangeDisplayCurrencyLocal(context.Background(), stellar1.ChangeDisplayCurrencyLocalArg{
   582  		AccountID: accID,
   583  		Currency:  stellar1.OutsideCurrencyCode("ZZZ"),
   584  	})
   585  	require.Error(t, err)
   586  
   587  	// Try empty account id.
   588  	_, err = tcs[0].Srv.ChangeDisplayCurrencyLocal(context.Background(), stellar1.ChangeDisplayCurrencyLocalArg{
   589  		AccountID: stellar1.AccountID(""),
   590  		Currency:  stellar1.OutsideCurrencyCode("USD"),
   591  	})
   592  	require.Error(t, err)
   593  
   594  	// Try non-existant account id.
   595  	invalidAccID, _ := randomStellarKeypair()
   596  	_, err = tcs[0].Srv.ChangeDisplayCurrencyLocal(context.Background(), stellar1.ChangeDisplayCurrencyLocalArg{
   597  		AccountID: invalidAccID,
   598  		Currency:  stellar1.OutsideCurrencyCode("USD"),
   599  	})
   600  	require.Error(t, err)
   601  
   602  	// Make wallet as other user, and try to change the currency as
   603  	// first user.
   604  	acceptDisclaimer(tcs[1])
   605  	tcs[1].Backend.ImportAccountsForUser(tcs[1])
   606  	accID2 := getPrimaryAccountID(tcs[1])
   607  	_, err = tcs[0].Srv.ChangeDisplayCurrencyLocal(context.Background(), stellar1.ChangeDisplayCurrencyLocalArg{
   608  		AccountID: accID2,
   609  		Currency:  stellar1.OutsideCurrencyCode("EUR"),
   610  	})
   611  	require.Error(t, err)
   612  
   613  	// Finally, a happy path.
   614  	res, err := tcs[0].Srv.ChangeDisplayCurrencyLocal(context.Background(), stellar1.ChangeDisplayCurrencyLocalArg{
   615  		AccountID: accID,
   616  		Currency:  stellar1.OutsideCurrencyCode("EUR"),
   617  	})
   618  	require.NoError(t, err)
   619  	require.Equal(t, stellar1.OutsideCurrencyCode("EUR"), res.Code)
   620  
   621  	// Check both CLI and Frontend RPCs.
   622  	accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
   623  	require.NoError(t, err)
   624  	require.Len(t, accs, 1)
   625  	require.NotNil(t, accs[0].ExchangeRate)
   626  	require.EqualValues(t, "EUR", accs[0].ExchangeRate.Currency)
   627  
   628  	balances, err := tcs[0].Srv.GetAccountAssetsLocal(context.Background(), stellar1.GetAccountAssetsLocalArg{
   629  		AccountID: accID,
   630  	})
   631  	require.NoError(t, err)
   632  	require.Len(t, balances, 1)
   633  	require.EqualValues(t, "EUR", balances[0].WorthCurrency)
   634  }
   635  
   636  func TestAcceptDisclaimer(t *testing.T) {
   637  	tcs, cleanup := setupNTests(t, 1)
   638  	defer cleanup()
   639  
   640  	accepted, err := tcs[0].Srv.HasAcceptedDisclaimerLocal(context.Background(), 0)
   641  	require.NoError(t, err)
   642  	require.Equal(t, false, accepted)
   643  
   644  	t.Logf("can't create wallet before disclaimer")
   645  	mctx := tcs[0].MetaContext()
   646  	_, err = stellar.CreateWallet(mctx)
   647  	require.Error(t, err)
   648  	require.True(t, libkb.IsAppStatusCode(err, keybase1.StatusCode_SCStellarNeedDisclaimer))
   649  
   650  	accepted, err = tcs[0].Srv.HasAcceptedDisclaimerLocal(context.Background(), 0)
   651  	require.NoError(t, err)
   652  	require.Equal(t, false, accepted)
   653  
   654  	err = tcs[0].Srv.AcceptDisclaimerLocal(context.Background(), 0)
   655  	require.NoError(t, err)
   656  
   657  	accepted, err = tcs[0].Srv.HasAcceptedDisclaimerLocal(context.Background(), 0)
   658  	require.NoError(t, err)
   659  	require.Equal(t, true, accepted)
   660  }
   661  
   662  func TestPublicKeyExporting(t *testing.T) {
   663  	tcs, cleanup := setupNTests(t, 1)
   664  	defer cleanup()
   665  
   666  	acceptDisclaimer(tcs[0])
   667  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   668  	accID := getPrimaryAccountID(tcs[0])
   669  
   670  	// Try empty argument.
   671  	_, err := tcs[0].Srv.GetWalletAccountPublicKeyLocal(context.Background(), stellar1.GetWalletAccountPublicKeyLocalArg{
   672  		AccountID: stellar1.AccountID(""),
   673  	})
   674  	require.Error(t, err)
   675  
   676  	// Anything should work - even accounts that don't exist or are
   677  	// not ours.
   678  	randomAccID, _ := randomStellarKeypair()
   679  	pubKey, err := tcs[0].Srv.GetWalletAccountPublicKeyLocal(context.Background(), stellar1.GetWalletAccountPublicKeyLocalArg{
   680  		AccountID: randomAccID,
   681  	})
   682  	require.NoError(t, err)
   683  	require.EqualValues(t, randomAccID, pubKey)
   684  
   685  	// Try account of our own.
   686  	pubKey, err = tcs[0].Srv.GetWalletAccountPublicKeyLocal(context.Background(), stellar1.GetWalletAccountPublicKeyLocalArg{
   687  		AccountID: accID,
   688  	})
   689  	require.NoError(t, err)
   690  	require.EqualValues(t, accID, pubKey)
   691  }
   692  
   693  func TestPrivateKeyExporting(t *testing.T) {
   694  	tcs, cleanup := setupNTests(t, 1)
   695  	defer cleanup()
   696  
   697  	acceptDisclaimer(tcs[0])
   698  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
   699  	accID := getPrimaryAccountID(tcs[0])
   700  
   701  	// Try empty argument.
   702  	_, err := tcs[0].Srv.GetWalletAccountSecretKeyLocal(context.Background(), stellar1.GetWalletAccountSecretKeyLocalArg{
   703  		AccountID: stellar1.AccountID(""),
   704  	})
   705  	require.Error(t, err)
   706  
   707  	// Try random account ID.
   708  	randomAccID, _ := randomStellarKeypair()
   709  	_, err = tcs[0].Srv.GetWalletAccountSecretKeyLocal(context.Background(), stellar1.GetWalletAccountSecretKeyLocalArg{
   710  		AccountID: randomAccID,
   711  	})
   712  	require.Error(t, err)
   713  
   714  	// Happy path.
   715  	privKey, err := tcs[0].Srv.GetWalletAccountSecretKeyLocal(context.Background(), stellar1.GetWalletAccountSecretKeyLocalArg{
   716  		AccountID: accID,
   717  	})
   718  	require.NoError(t, err)
   719  	require.EqualValues(t, tcs[0].Backend.SecretKey(accID), privKey)
   720  }
   721  
   722  func TestGetPaymentsLocal(t *testing.T) {
   723  	tcs, cleanup := setupNTests(t, 2)
   724  	defer cleanup()
   725  
   726  	acceptDisclaimer(tcs[0])
   727  	acceptDisclaimer(tcs[1])
   728  
   729  	srvSender := tcs[0].Srv
   730  	rm := tcs[0].Backend
   731  	accountIDSender := rm.AddAccount(tcs[0].Fu.GetUID())
   732  	accountIDRecip := rm.AddAccount(tcs[1].Fu.GetUID())
   733  	accountIDRecip2 := rm.AddAccount(tcs[1].Fu.GetUID())
   734  
   735  	srvRecip := tcs[1].Srv
   736  
   737  	err := srvSender.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   738  		SecretKey:   rm.SecretKey(accountIDSender),
   739  		MakePrimary: true,
   740  		Name:        "qq",
   741  	})
   742  	require.NoError(t, err)
   743  
   744  	_, err = srvSender.ChangeWalletAccountNameLocal(context.Background(), stellar1.ChangeWalletAccountNameLocalArg{
   745  		AccountID: accountIDSender,
   746  		NewName:   "office lunch money",
   747  	})
   748  	require.NoError(t, err)
   749  
   750  	err = srvRecip.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   751  		SecretKey:   rm.SecretKey(accountIDRecip),
   752  		MakePrimary: true,
   753  		Name:        "uu",
   754  	})
   755  	require.NoError(t, err)
   756  
   757  	err = srvRecip.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
   758  		SecretKey:   rm.SecretKey(accountIDRecip2),
   759  		MakePrimary: false,
   760  		Name:        "vv",
   761  	})
   762  	require.NoError(t, err)
   763  
   764  	// Try some payments that should fail locally
   765  	{
   766  		_, err := srvSender.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
   767  			BypassBid:     true,
   768  			From:          accountIDRecip, // From the wrong account
   769  			To:            tcs[1].Fu.Username,
   770  			ToIsAccountID: false,
   771  			Amount:        "1011.123",
   772  			Asset:         stellar1.AssetNative(),
   773  			WorthAmount:   "321.87",
   774  			WorthCurrency: &usd,
   775  			SecretNote:    "here you go",
   776  			PublicMemo:    "public note",
   777  		})
   778  		require.Error(t, err)
   779  		require.Contains(t, err.Error(), "Sender account not found")
   780  
   781  		_, err = srvSender.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
   782  			BypassBid:     true,
   783  			From:          accountIDSender,
   784  			To:            tcs[1].Fu.Username,
   785  			ToIsAccountID: true, // fail to parse account ID
   786  			Amount:        "1011.123",
   787  			Asset:         stellar1.AssetNative(),
   788  			WorthAmount:   "321.87",
   789  			WorthCurrency: &usd,
   790  			SecretNote:    "here you go",
   791  			PublicMemo:    "public note",
   792  		})
   793  		require.Error(t, err)
   794  		require.Equal(t, "recipient: Invalid Stellar address.", err.Error())
   795  	}
   796  
   797  	// set up notification listeners
   798  	listenerSender := newChatListener()
   799  	listenerRecip := newChatListener()
   800  	tcs[0].G.NotifyRouter.AddListener(listenerSender)
   801  	tcs[1].G.NotifyRouter.AddListener(listenerRecip)
   802  
   803  	sendRes, err := srvSender.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
   804  		BypassBid:     true,
   805  		From:          accountIDSender,
   806  		To:            tcs[1].Fu.Username,
   807  		ToIsAccountID: false,
   808  		Amount:        "1011.123",
   809  		Asset:         stellar1.AssetNative(),
   810  		WorthAmount:   "321.87",
   811  		WorthCurrency: &usd,
   812  		SecretNote:    "here you go",
   813  		PublicMemo:    "public note",
   814  	})
   815  	require.NoError(t, err)
   816  	require.Len(t, sendRes.KbTxID, 32)
   817  	require.False(t, sendRes.Pending)
   818  
   819  	// simulate exchange rate changing
   820  	tcs[0].Backend.UseAlternateExchangeRate()
   821  
   822  	checkPayment := func(p stellar1.PaymentLocal, sender bool) {
   823  		require.NotEmpty(t, p.Id)
   824  		require.NotZero(t, p.Time)
   825  		require.Equal(t, stellar1.PaymentStatus_COMPLETED, p.StatusSimplified)
   826  		require.Equal(t, "completed", p.StatusDescription)
   827  		require.Empty(t, p.StatusDetail)
   828  		if sender {
   829  			require.Equal(t, "1,011.1230000 XLM", p.AmountDescription, "Amount")
   830  			require.Equal(t, stellar1.BalanceDelta_DECREASE, p.Delta)
   831  		} else {
   832  			require.Equal(t, "1,011.1230000 XLM", p.AmountDescription, "Amount")
   833  			require.Equal(t, stellar1.BalanceDelta_INCREASE, p.Delta)
   834  		}
   835  		require.Equal(t, "$321.87 USD", p.Worth, "Worth")
   836  		require.Equal(t, "", p.WorthAtSendTime, "WorthAtSendTIme")
   837  
   838  		require.Equal(t, stellar1.ParticipantType_KEYBASE, p.FromType)
   839  		require.Equal(t, accountIDSender, p.FromAccountID)
   840  		var fromAccountName string
   841  		if sender {
   842  			fromAccountName = "office lunch money"
   843  		}
   844  		require.Equal(t, fromAccountName, p.FromAccountName, "FromAccountName")
   845  		require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
   846  		require.Equal(t, stellar1.ParticipantType_KEYBASE, p.ToType)
   847  		require.Equal(t, accountIDRecip, *p.ToAccountID)
   848  		var toAccountName string
   849  		if !sender {
   850  			toAccountName = "uu"
   851  		}
   852  		require.Equal(t, toAccountName, p.ToAccountName, "ToAccountName")
   853  		require.Equal(t, tcs[1].Fu.Username, p.ToUsername)
   854  		require.Equal(t, "", p.ToAssertion)
   855  
   856  		require.Equal(t, "here you go", p.Note)
   857  		require.Empty(t, p.NoteErr)
   858  	}
   859  	senderPaymentsPage, err := srvSender.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: accountIDSender})
   860  	require.NoError(t, err)
   861  	senderPayments := senderPaymentsPage.Payments
   862  	require.Len(t, senderPayments, 1)
   863  	t.Logf("senderPayments: %v", spew.Sdump(senderPayments))
   864  	if senderPayments[0].Err != nil {
   865  		t.Logf("senderPayments error: %+v", *senderPayments[0].Err)
   866  	}
   867  	require.NotNil(t, senderPayments[0].Payment)
   868  	checkPayment(*senderPayments[0].Payment, true)
   869  
   870  	recipPaymentsPage, err := srvRecip.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: accountIDRecip})
   871  	require.NoError(t, err)
   872  	recipPayments := recipPaymentsPage.Payments
   873  	require.Len(t, recipPayments, 1)
   874  	require.NotNil(t, recipPayments[0].Payment)
   875  	checkPayment(*recipPayments[0].Payment, false)
   876  
   877  	// pretend that the chat message was unboxed and call the payment loader to load the info:
   878  	loader := stellar.DefaultLoader(tcs[0].G)
   879  	convID := chat1.ConversationID("abcd")
   880  	msgID := chat1.MessageID(987)
   881  	loader.LoadPayment(context.Background(), convID, msgID, tcs[0].Fu.Username, senderPayments[0].Payment.Id)
   882  
   883  	// for the recipient too
   884  	recipLoader := stellar.NewLoader(tcs[1].G)
   885  	recipLoader.LoadPayment(context.Background(), convID, msgID, tcs[0].Fu.Username, senderPayments[0].Payment.Id)
   886  
   887  	// check the sender chat notification
   888  	select {
   889  	case info := <-listenerSender.paymentInfos:
   890  		t.Logf("info from listener: %+v", info)
   891  		require.NotNil(t, info)
   892  		require.Equal(t, info.Uid, tcs[0].Fu.User.GetUID())
   893  		require.Equal(t, info.MsgID, msgID)
   894  		require.True(t, info.ConvID.Eq(convID))
   895  		require.Equal(t, info.Info.AmountDescription, "1,011.1230000 XLM")
   896  		require.Equal(t, stellar1.BalanceDelta_DECREASE, info.Info.Delta)
   897  		require.Equal(t, "$321.87 USD", info.Info.Worth)
   898  		require.Equal(t, info.Info.Note, "here you go")
   899  		require.Equal(t, info.Info.Status, stellar1.PaymentStatus_COMPLETED)
   900  		require.Equal(t, info.Info.StatusDescription, "completed")
   901  	case <-time.After(20 * time.Second):
   902  		t.Fatal("timed out waiting for chat payment info notification to sender")
   903  	}
   904  
   905  	// check the recipient chat notification
   906  	select {
   907  	case info := <-listenerRecip.paymentInfos:
   908  		t.Logf("info from listener: %+v", info)
   909  		require.NotNil(t, info)
   910  		require.Equal(t, info.Uid, tcs[1].Fu.User.GetUID())
   911  		require.Equal(t, info.MsgID, msgID)
   912  		require.True(t, info.ConvID.Eq(convID))
   913  		require.Equal(t, info.Info.AmountDescription, "1,011.1230000 XLM")
   914  		require.Equal(t, info.Info.Delta, stellar1.BalanceDelta_INCREASE)
   915  		require.Equal(t, info.Info.Worth, "$321.87 USD")
   916  		require.Equal(t, info.Info.Note, "here you go")
   917  		require.Equal(t, info.Info.Status, stellar1.PaymentStatus_COMPLETED)
   918  		require.Equal(t, info.Info.StatusDescription, "completed")
   919  	case <-time.After(20 * time.Second):
   920  		t.Fatal("timed out waiting for chat payment info notification to sender")
   921  	}
   922  
   923  	// check the details
   924  	checkPaymentDetails := func(pd stellar1.PaymentDetailsLocal, sender bool) {
   925  		p := pd.Summary
   926  		require.NotEmpty(t, p.Id)
   927  		require.NotZero(t, p.Time)
   928  		require.Equal(t, stellar1.PaymentStatus_COMPLETED, p.StatusSimplified)
   929  		require.Equal(t, "completed", p.StatusDescription)
   930  		require.Empty(t, p.StatusDetail)
   931  		if sender {
   932  			require.Equal(t, "1,011.1230000 XLM", p.AmountDescription, "Amount")
   933  			require.Equal(t, stellar1.BalanceDelta_DECREASE, p.Delta)
   934  		} else {
   935  			require.Equal(t, "1,011.1230000 XLM", p.AmountDescription, "Amount")
   936  			require.Equal(t, stellar1.BalanceDelta_INCREASE, p.Delta)
   937  		}
   938  		require.Equal(t, "$321.87 USD", p.Worth, "Worth")
   939  		require.Equal(t, "", p.WorthAtSendTime, "WorthAtSendTime")
   940  
   941  		require.Equal(t, stellar1.ParticipantType_KEYBASE, p.FromType)
   942  		require.Equal(t, accountIDSender, p.FromAccountID)
   943  		var fromAccountName string
   944  		if sender {
   945  			fromAccountName = "office lunch money"
   946  		}
   947  		require.Equal(t, fromAccountName, p.FromAccountName)
   948  		require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
   949  		require.Equal(t, stellar1.ParticipantType_KEYBASE, p.ToType)
   950  		require.Equal(t, accountIDRecip, *p.ToAccountID)
   951  		var toAccountName string
   952  		if !sender {
   953  			toAccountName = "uu"
   954  		}
   955  		require.Equal(t, toAccountName, p.ToAccountName)
   956  		require.Equal(t, tcs[1].Fu.Username, p.ToUsername)
   957  		require.Equal(t, "", p.ToAssertion)
   958  
   959  		require.Equal(t, "here you go", p.Note)
   960  		require.Empty(t, p.NoteErr)
   961  		require.Equal(t, "public note", pd.Details.PublicNote)
   962  		require.Equal(t, "text", pd.Details.PublicNoteType)
   963  		t.Logf("details: %+v", p)
   964  		require.Equal(t, fmt.Sprintf("https://stellar.expert/explorer/public/tx/%s", p.TxID), pd.Details.ExternalTxURL)
   965  	}
   966  	details, err := srvSender.GetPaymentDetailsLocal(context.Background(), stellar1.GetPaymentDetailsLocalArg{
   967  		Id:        senderPayments[0].Payment.Id,
   968  		AccountID: accountIDSender,
   969  	})
   970  	require.NoError(t, err)
   971  	checkPaymentDetails(details, true)
   972  
   973  	details, err = srvRecip.GetPaymentDetailsLocal(context.Background(), stellar1.GetPaymentDetailsLocalArg{
   974  		Id:        recipPayments[0].Payment.Id,
   975  		AccountID: accountIDRecip,
   976  	})
   977  	require.NoError(t, err)
   978  	checkPaymentDetails(details, false)
   979  
   980  	// use default exchange rate again since about to send new payments.
   981  	tcs[0].Backend.UseDefaultExchangeRate()
   982  
   983  	// Send again with FromSeqno set.
   984  	// Does not test whether it has any effect.
   985  	_, err = srvSender.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
   986  		BypassBid:     true,
   987  		From:          accountIDSender,
   988  		To:            tcs[1].Fu.Username,
   989  		ToIsAccountID: false,
   990  		Amount:        "1011.123",
   991  		Asset:         stellar1.AssetNative(),
   992  		WorthAmount:   "321.87",
   993  		WorthCurrency: &usd,
   994  		SecretNote:    "here you go",
   995  		PublicMemo:    "public note",
   996  	})
   997  	require.NoError(t, err)
   998  
   999  	// send to stellar account ID to check target in PaymentLocal
  1000  	sendRes, err = srvSender.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  1001  		BypassBid: true,
  1002  		From:      accountIDSender,
  1003  		// Use a secondary account so that LookupRecipient can't resolve it to the user
  1004  		To:            accountIDRecip2.String(),
  1005  		ToIsAccountID: true,
  1006  		Amount:        "101.456",
  1007  		Asset:         stellar1.AssetNative(),
  1008  		WorthAmount:   "321.87",
  1009  		WorthCurrency: &usd,
  1010  		SecretNote:    "here you go",
  1011  		PublicMemo:    "public note",
  1012  	})
  1013  	require.NoError(t, err)
  1014  	require.Len(t, sendRes.KbTxID, 32)
  1015  	require.False(t, sendRes.Pending)
  1016  	senderPaymentsPage, err = srvSender.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: accountIDSender})
  1017  	require.NoError(t, err)
  1018  	senderPayments = senderPaymentsPage.Payments
  1019  	require.Len(t, senderPayments, 3)
  1020  	t.Logf("senderPayments: %+v", senderPayments)
  1021  	if senderPayments[0].Err != nil {
  1022  		t.Logf("senderPayments error: %+v", *senderPayments[0].Err)
  1023  	}
  1024  	p := senderPayments[0].Payment
  1025  	require.NotNil(t, p)
  1026  	require.Equal(t, stellar1.ParticipantType_KEYBASE, p.FromType)
  1027  	require.Equal(t, accountIDSender, p.FromAccountID)
  1028  	require.Equal(t, "office lunch money", p.FromAccountName)
  1029  	require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
  1030  	require.Equal(t, stellar1.ParticipantType_STELLAR, p.ToType)
  1031  	require.Equal(t, accountIDRecip2, *p.ToAccountID)
  1032  	require.Equal(t, "", p.ToAccountName)
  1033  	require.Equal(t, "", p.ToUsername)
  1034  	require.Equal(t, "", p.ToAssertion)
  1035  
  1036  	recipPaymentsPage, err = srvRecip.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: accountIDRecip2})
  1037  	require.NoError(t, err)
  1038  	recipPayments = recipPaymentsPage.Payments
  1039  	require.Len(t, recipPayments, 1)
  1040  	p = recipPayments[0].Payment
  1041  	t.Logf("recipPayments[0]: %+v", p)
  1042  	require.NotNil(t, p)
  1043  	require.Equal(t, stellar1.ParticipantType_KEYBASE, p.FromType)
  1044  	require.Equal(t, accountIDSender, p.FromAccountID)
  1045  	require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
  1046  	require.Equal(t, "", p.FromAccountName)
  1047  	require.Equal(t, stellar1.ParticipantType_STELLAR, p.ToType)
  1048  	require.Equal(t, accountIDRecip2, *p.ToAccountID)
  1049  	require.Equal(t, "vv", p.ToAccountName)
  1050  	require.Equal(t, "", p.ToUsername)
  1051  	require.Equal(t, "", p.ToAssertion)
  1052  	require.NotEmpty(t, p.NoteErr) // can't send encrypted note to stellar address
  1053  }
  1054  
  1055  func TestSendToSelf(t *testing.T) {
  1056  	tcs, cleanup := setupNTests(t, 1)
  1057  	defer cleanup()
  1058  
  1059  	acceptDisclaimer(tcs[0])
  1060  	rm := tcs[0].Backend
  1061  	accountID1 := rm.AddAccount(tcs[0].Fu.GetUID())
  1062  	accountID2 := rm.AddAccount(tcs[0].Fu.GetUID())
  1063  
  1064  	err := tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
  1065  		SecretKey:   rm.SecretKey(accountID1),
  1066  		MakePrimary: true,
  1067  		Name:        "qq",
  1068  	})
  1069  	require.NoError(t, err)
  1070  
  1071  	_, err = tcs[0].Srv.ChangeWalletAccountNameLocal(context.Background(), stellar1.ChangeWalletAccountNameLocalArg{
  1072  		AccountID: accountID1,
  1073  		NewName:   "office lunch money",
  1074  	})
  1075  	require.NoError(t, err)
  1076  
  1077  	err = tcs[0].Srv.ImportSecretKeyLocal(context.Background(), stellar1.ImportSecretKeyLocalArg{
  1078  		SecretKey: rm.SecretKey(accountID2),
  1079  		Name:      "uu",
  1080  	})
  1081  	require.NoError(t, err)
  1082  
  1083  	_, err = tcs[0].Srv.ChangeWalletAccountNameLocal(context.Background(), stellar1.ChangeWalletAccountNameLocalArg{
  1084  		AccountID: accountID2,
  1085  		NewName:   "savings",
  1086  	})
  1087  	require.NoError(t, err)
  1088  
  1089  	t.Logf("Send to the same account")
  1090  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  1091  		BypassBid:     true,
  1092  		From:          accountID1,
  1093  		To:            accountID1.String(),
  1094  		ToIsAccountID: true,
  1095  		Amount:        "100",
  1096  		Asset:         stellar1.AssetNative(),
  1097  	})
  1098  	require.NoError(t, err)
  1099  
  1100  	t.Logf("Send to another of the same user's account")
  1101  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  1102  		BypassBid:     true,
  1103  		From:          accountID1,
  1104  		To:            accountID2.String(),
  1105  		ToIsAccountID: true,
  1106  		Amount:        "200",
  1107  		Asset:         stellar1.AssetNative(),
  1108  	})
  1109  	require.NoError(t, err)
  1110  
  1111  	t.Logf("Send from another of the same user's account")
  1112  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  1113  		BypassBid:     true,
  1114  		From:          accountID2,
  1115  		To:            accountID1.String(),
  1116  		ToIsAccountID: true,
  1117  		Amount:        "300",
  1118  		Asset:         stellar1.AssetNative(),
  1119  	})
  1120  	require.NoError(t, err)
  1121  
  1122  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), accountID1, "test")
  1123  	require.NoError(t, err)
  1124  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), accountID2, "test")
  1125  	require.NoError(t, err)
  1126  
  1127  	page, err := tcs[0].Srv.GetPaymentsLocal(context.Background(), stellar1.GetPaymentsLocalArg{AccountID: accountID1})
  1128  	require.NoError(t, err)
  1129  	t.Logf("%v", spew.Sdump(page))
  1130  	require.Len(t, page.Payments, 3)
  1131  
  1132  	p := page.Payments[2].Payment
  1133  	require.Equal(t, "100 XLM", p.AmountDescription)
  1134  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, p.FromType)
  1135  	require.Equal(t, accountID1, p.FromAccountID)
  1136  	require.Equal(t, "office lunch money", p.FromAccountName)
  1137  	require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
  1138  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, p.ToType)
  1139  	require.Equal(t, accountID1, *p.ToAccountID)
  1140  	require.Equal(t, "office lunch money", p.ToAccountName)
  1141  	require.Equal(t, tcs[0].Fu.Username, p.ToUsername)
  1142  	require.Equal(t, "", p.ToAssertion)
  1143  	require.Equal(t, "$123.23 USD", p.WorthAtSendTime)
  1144  
  1145  	p = page.Payments[1].Payment
  1146  	require.Equal(t, "200 XLM", p.AmountDescription)
  1147  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, p.FromType)
  1148  	require.Equal(t, accountID1, p.FromAccountID)
  1149  	require.Equal(t, "office lunch money", p.FromAccountName)
  1150  	require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
  1151  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, p.ToType)
  1152  	require.Equal(t, accountID2, *p.ToAccountID)
  1153  	require.Equal(t, "savings", p.ToAccountName)
  1154  	require.Equal(t, tcs[0].Fu.Username, p.ToUsername)
  1155  	require.Equal(t, "", p.ToAssertion)
  1156  	require.Equal(t, "$123.23 USD", p.WorthAtSendTime)
  1157  
  1158  	p = page.Payments[0].Payment
  1159  	require.Equal(t, "300 XLM", p.AmountDescription)
  1160  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, p.FromType)
  1161  	require.Equal(t, accountID2, p.FromAccountID)
  1162  	require.Equal(t, "savings", p.FromAccountName)
  1163  	require.Equal(t, tcs[0].Fu.Username, p.FromUsername)
  1164  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, p.ToType)
  1165  	require.Equal(t, accountID1, *p.ToAccountID)
  1166  	require.Equal(t, "office lunch money", p.ToAccountName)
  1167  	require.Equal(t, tcs[0].Fu.Username, p.ToUsername)
  1168  	require.Equal(t, "", p.ToAssertion)
  1169  	require.Equal(t, "$123.23 USD", p.WorthAtSendTime)
  1170  
  1171  	pd1, err := tcs[0].Srv.GetPaymentDetailsLocal(context.Background(), stellar1.GetPaymentDetailsLocalArg{
  1172  		Id:        page.Payments[2].Payment.Id,
  1173  		AccountID: accountID1,
  1174  	})
  1175  	pd := pd1.Summary
  1176  	require.NoError(t, err)
  1177  	require.Equal(t, "100 XLM", pd.AmountDescription)
  1178  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, pd.FromType)
  1179  	require.Equal(t, accountID1, pd.FromAccountID)
  1180  	require.Equal(t, "office lunch money", pd.FromAccountName)
  1181  	require.Equal(t, tcs[0].Fu.Username, pd.FromUsername)
  1182  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, pd.ToType)
  1183  	require.Equal(t, accountID1, *pd.ToAccountID)
  1184  	require.Equal(t, "office lunch money", pd.ToAccountName)
  1185  	require.Equal(t, tcs[0].Fu.Username, pd.ToUsername)
  1186  	require.Equal(t, "", pd.ToAssertion)
  1187  	require.Equal(t, "$123.23 USD", p.WorthAtSendTime)
  1188  
  1189  	pd1, err = tcs[0].Srv.GetPaymentDetailsLocal(context.Background(), stellar1.GetPaymentDetailsLocalArg{
  1190  		Id:        page.Payments[1].Payment.Id,
  1191  		AccountID: accountID1,
  1192  	})
  1193  	pd = pd1.Summary
  1194  	require.NoError(t, err)
  1195  	require.Equal(t, "200 XLM", pd.AmountDescription)
  1196  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, pd.FromType)
  1197  	require.Equal(t, accountID1, pd.FromAccountID)
  1198  	require.Equal(t, "office lunch money", pd.FromAccountName)
  1199  	require.Equal(t, tcs[0].Fu.Username, pd.FromUsername)
  1200  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, pd.ToType)
  1201  	require.Equal(t, accountID2, *pd.ToAccountID)
  1202  	require.Equal(t, "savings", pd.ToAccountName)
  1203  	require.Equal(t, tcs[0].Fu.Username, pd.ToUsername)
  1204  	require.Equal(t, "", pd.ToAssertion)
  1205  	require.Equal(t, "$123.23 USD", p.WorthAtSendTime)
  1206  
  1207  	pd1, err = tcs[0].Srv.GetPaymentDetailsLocal(context.Background(), stellar1.GetPaymentDetailsLocalArg{
  1208  		Id:        page.Payments[0].Payment.Id,
  1209  		AccountID: accountID2,
  1210  	})
  1211  	pd = pd1.Summary
  1212  	require.NoError(t, err)
  1213  	require.Equal(t, "300 XLM", pd.AmountDescription)
  1214  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, pd.FromType)
  1215  	require.Equal(t, accountID2, pd.FromAccountID)
  1216  	require.Equal(t, "savings", pd.FromAccountName)
  1217  	require.Equal(t, tcs[0].Fu.Username, pd.FromUsername)
  1218  	require.Equal(t, stellar1.ParticipantType_OWNACCOUNT, pd.ToType)
  1219  	require.Equal(t, accountID1, *pd.ToAccountID)
  1220  	require.Equal(t, "office lunch money", pd.ToAccountName)
  1221  	require.Equal(t, tcs[0].Fu.Username, pd.ToUsername)
  1222  	require.Equal(t, "", pd.ToAssertion)
  1223  	require.Equal(t, "$123.23 USD", p.WorthAtSendTime)
  1224  }
  1225  
  1226  func TestPaymentDetailsEmptyAccId(t *testing.T) {
  1227  	tcs, cleanup := setupNTests(t, 2)
  1228  	defer cleanup()
  1229  
  1230  	acceptDisclaimer(tcs[0])
  1231  	acceptDisclaimer(tcs[1])
  1232  	backend := tcs[0].Backend
  1233  	backend.ImportAccountsForUser(tcs[0])
  1234  	backend.ImportAccountsForUser(tcs[1])
  1235  
  1236  	accID := getPrimaryAccountID(tcs[0])
  1237  	backend.accounts[accID].AddBalance("1000")
  1238  
  1239  	const secretNote string = "pleasure doing business 🤔"
  1240  
  1241  	_, err := tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  1242  		BypassBid:     true,
  1243  		From:          accID,
  1244  		To:            tcs[1].Fu.Username,
  1245  		ToIsAccountID: false,
  1246  		Amount:        "505.612",
  1247  		Asset:         stellar1.AssetNative(),
  1248  		WorthAmount:   "160.93",
  1249  		WorthCurrency: &usd,
  1250  		SecretNote:    secretNote,
  1251  		PublicMemo:    "",
  1252  	})
  1253  	require.NoError(t, err)
  1254  
  1255  	senderMsgs := kbtest.MockSentMessages(tcs[0].G, tcs[0].T)
  1256  	require.Len(t, senderMsgs, 1)
  1257  	require.Equal(t, senderMsgs[0].MsgType, chat1.MessageType_SENDPAYMENT)
  1258  
  1259  	// Imagine this is the receiver reading chat.
  1260  	paymentID := senderMsgs[0].Body.Sendpayment().PaymentID
  1261  
  1262  	detailsRes, err := tcs[0].Srv.GetGenericPaymentDetailsLocal(context.Background(), stellar1.GetGenericPaymentDetailsLocalArg{
  1263  		// Chat/Loader does not know account IDs, just payment IDs.
  1264  		// It derives delta and formatting (whether it's a debit or
  1265  		// credit) by checking chat message sender and receiver.
  1266  		Id: paymentID,
  1267  	})
  1268  	require.NoError(t, err)
  1269  	require.Equal(t, stellar1.BalanceDelta_NONE, detailsRes.Summary.Delta)
  1270  	require.Equal(t, "505.6120000 XLM", detailsRes.Summary.AmountDescription)
  1271  	require.Equal(t, "$160.93 USD", detailsRes.Summary.Worth)
  1272  	require.Equal(t, "", detailsRes.Summary.WorthAtSendTime)
  1273  	require.Equal(t, secretNote, detailsRes.Summary.Note)
  1274  	require.Equal(t, "", detailsRes.Summary.NoteErr)
  1275  }
  1276  
  1277  func TestBuildRequestLocal(t *testing.T) {
  1278  	tcs, cleanup := setupNTests(t, 2)
  1279  	defer cleanup()
  1280  
  1281  	acceptDisclaimer(tcs[0])
  1282  	worthInfo := "$1.00 = 3.1414139 XLM\nSource: coinmarketcap.com"
  1283  
  1284  	bres, err := tcs[0].Srv.BuildRequestLocal(context.Background(), stellar1.BuildRequestLocalArg{
  1285  		To: tcs[1].Fu.Username,
  1286  	})
  1287  	require.NoError(t, err)
  1288  	t.Logf(spew.Sdump(bres))
  1289  	require.Equal(t, false, bres.ReadyToRequest)
  1290  	require.Equal(t, "", bres.ToErrMsg)
  1291  	require.Equal(t, "", bres.AmountErrMsg)
  1292  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1293  	require.Equal(t, "$0.00 USD", bres.WorthDescription)
  1294  	require.Equal(t, worthInfo, bres.WorthInfo)
  1295  	require.True(t, bres.SendingIntentionXLM)
  1296  	require.Equal(t, "", bres.DisplayAmountXLM)
  1297  	require.Equal(t, "", bres.DisplayAmountFiat)
  1298  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1299  
  1300  	bres, err = tcs[0].Srv.BuildRequestLocal(context.Background(), stellar1.BuildRequestLocalArg{
  1301  		To:     tcs[1].Fu.Username,
  1302  		Amount: "-1",
  1303  	})
  1304  	require.NoError(t, err)
  1305  	t.Logf(spew.Sdump(bres))
  1306  	require.Equal(t, false, bres.ReadyToRequest)
  1307  	require.Equal(t, "", bres.ToErrMsg)
  1308  	require.Equal(t, "Invalid amount.", bres.AmountErrMsg)
  1309  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1310  	require.Equal(t, "", bres.WorthDescription)
  1311  	require.Equal(t, "", bres.WorthInfo)
  1312  	require.True(t, bres.SendingIntentionXLM)
  1313  	require.Equal(t, "", bres.DisplayAmountXLM)
  1314  	require.Equal(t, "", bres.DisplayAmountFiat)
  1315  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1316  
  1317  	bres, err = tcs[0].Srv.BuildRequestLocal(context.Background(), stellar1.BuildRequestLocalArg{
  1318  		To:     tcs[1].Fu.Username,
  1319  		Amount: "15",
  1320  	})
  1321  	require.NoError(t, err)
  1322  	t.Logf(spew.Sdump(bres))
  1323  	require.Equal(t, true, bres.ReadyToRequest)
  1324  	require.Equal(t, "", bres.ToErrMsg)
  1325  	require.Equal(t, "", bres.AmountErrMsg)
  1326  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1327  	require.Equal(t, "$4.77 USD", bres.WorthDescription)
  1328  	require.Equal(t, worthInfo, bres.WorthInfo)
  1329  	require.True(t, bres.SendingIntentionXLM)
  1330  	require.Equal(t, "15 XLM", bres.DisplayAmountXLM)
  1331  	require.Equal(t, "$4.77 USD", bres.DisplayAmountFiat)
  1332  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1333  
  1334  	t.Logf("requesting in amount composed in USD")
  1335  	bres, err = tcs[0].Srv.BuildRequestLocal(context.Background(), stellar1.BuildRequestLocalArg{
  1336  		To:       tcs[1].Fu.Username,
  1337  		Amount:   "8.50",
  1338  		Currency: &usd,
  1339  	})
  1340  	require.NoError(t, err)
  1341  	t.Logf(spew.Sdump(bres))
  1342  	require.Equal(t, true, bres.ReadyToRequest)
  1343  	require.Equal(t, "", bres.ToErrMsg)
  1344  	require.Equal(t, "", bres.AmountErrMsg)
  1345  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1346  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1347  	require.Equal(t, worthInfo, bres.WorthInfo)
  1348  	require.False(t, bres.SendingIntentionXLM)
  1349  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1350  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1351  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1352  }
  1353  
  1354  func TestBuildPaymentLocal(t *testing.T) {
  1355  	tcs, cleanup := setupNTests(t, 2)
  1356  	defer cleanup()
  1357  
  1358  	acceptDisclaimer(tcs[0])
  1359  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  1360  	require.NoError(t, err)
  1361  
  1362  	senderSecondaryAccountID, err := tcs[0].Srv.CreateWalletAccountLocal(context.Background(), stellar1.CreateWalletAccountLocalArg{
  1363  		Name: "second",
  1364  	})
  1365  	require.NoError(t, err)
  1366  
  1367  	worthInfo := "$1.00 = 3.1414139 XLM\nSource: coinmarketcap.com"
  1368  
  1369  	for _, toIsAccountID := range []bool{false, true} {
  1370  		t.Logf("toIsAccountID: %v", toIsAccountID)
  1371  		bres, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1372  			From:          senderAccountID,
  1373  			ToIsAccountID: toIsAccountID,
  1374  		})
  1375  		require.NoError(t, err)
  1376  		t.Logf(spew.Sdump(bres))
  1377  		require.Equal(t, false, bres.ReadyToReview)
  1378  		require.Equal(t, "", bres.ToErrMsg)
  1379  		require.Equal(t, "", bres.AmountErrMsg)
  1380  		require.Equal(t, "", bres.SecretNoteErrMsg)
  1381  		require.Equal(t, "", bres.PublicMemoErrMsg)
  1382  		require.Equal(t, "$0.00 USD", bres.WorthDescription)
  1383  		require.Equal(t, "USD", bres.WorthCurrency)
  1384  		require.Equal(t, worthInfo, bres.WorthInfo)
  1385  		require.True(t, bres.SendingIntentionXLM)
  1386  		require.Equal(t, "", bres.DisplayAmountXLM)
  1387  		require.Equal(t, "", bres.DisplayAmountFiat)
  1388  		requireBannerSet(t, bres.DeepCopy().Banners, nil)
  1389  	}
  1390  
  1391  	acceptDisclaimer(tcs[1])
  1392  
  1393  	bres, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1394  		From: senderAccountID,
  1395  		To:   tcs[1].Fu.Username,
  1396  	})
  1397  	require.NoError(t, err)
  1398  	t.Logf(spew.Sdump(bres))
  1399  	require.Equal(t, false, bres.ReadyToReview)
  1400  	require.Equal(t, "", bres.ToErrMsg)
  1401  	require.Equal(t, "", bres.AmountErrMsg)
  1402  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1403  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1404  	require.Equal(t, "$0.00 USD", bres.WorthDescription)
  1405  	require.Equal(t, worthInfo, bres.WorthInfo)
  1406  	require.True(t, bres.SendingIntentionXLM)
  1407  	require.Equal(t, "", bres.DisplayAmountXLM)
  1408  	require.Equal(t, "", bres.DisplayAmountFiat)
  1409  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1410  		Level:   "info",
  1411  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1412  	}})
  1413  
  1414  	recipientAccountID := getPrimaryAccountID(tcs[1])
  1415  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1416  		From:          senderAccountID,
  1417  		To:            recipientAccountID.String(),
  1418  		ToIsAccountID: true,
  1419  	})
  1420  	require.NoError(t, err)
  1421  	t.Logf(spew.Sdump(bres))
  1422  	require.Equal(t, false, bres.ReadyToReview)
  1423  	require.Equal(t, "", bres.ToErrMsg)
  1424  	require.Equal(t, "", bres.AmountErrMsg)
  1425  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1426  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1427  	require.Equal(t, "$0.00 USD", bres.WorthDescription)
  1428  	require.True(t, bres.SendingIntentionXLM)
  1429  	require.Equal(t, "", bres.DisplayAmountXLM)
  1430  	require.Equal(t, "", bres.DisplayAmountFiat)
  1431  	require.Equal(t, worthInfo, bres.WorthInfo)
  1432  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1433  		Level:   "info",
  1434  		Message: "Because it's their first transaction, you must send at least 1 XLM.",
  1435  	}})
  1436  
  1437  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1438  		From:   senderAccountID,
  1439  		To:     tcs[1].Fu.Username,
  1440  		Amount: "-1",
  1441  	})
  1442  	require.NoError(t, err)
  1443  	t.Logf(spew.Sdump(bres))
  1444  	require.Equal(t, false, bres.ReadyToReview)
  1445  	require.Equal(t, "", bres.ToErrMsg)
  1446  	require.Equal(t, "Invalid amount.", bres.AmountErrMsg)
  1447  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1448  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1449  	require.Equal(t, "", bres.WorthDescription)
  1450  	require.Equal(t, "", bres.WorthInfo)
  1451  	require.True(t, bres.SendingIntentionXLM)
  1452  	require.Equal(t, "", bres.DisplayAmountXLM)
  1453  	require.Equal(t, "", bres.DisplayAmountFiat)
  1454  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1455  		Level:   "info",
  1456  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1457  	}})
  1458  
  1459  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1460  		From:   senderAccountID,
  1461  		To:     tcs[1].Fu.Username,
  1462  		Amount: "30",
  1463  	})
  1464  	require.NoError(t, err)
  1465  	t.Logf(spew.Sdump(bres))
  1466  	require.Equal(t, false, bres.ReadyToReview)
  1467  	require.Equal(t, "", bres.ToErrMsg)
  1468  	require.Equal(t, "You have *0 XLM* available to send.", bres.AmountErrMsg)
  1469  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1470  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1471  	require.Equal(t, "$9.55 USD", bres.WorthDescription)
  1472  	require.Equal(t, worthInfo, bres.WorthInfo)
  1473  	require.True(t, bres.SendingIntentionXLM)
  1474  	require.Equal(t, "30 XLM", bres.DisplayAmountXLM)
  1475  	require.Equal(t, "$9.55 USD", bres.DisplayAmountFiat)
  1476  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1477  		Level:   "info",
  1478  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1479  	}})
  1480  
  1481  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1482  		From:     senderAccountID,
  1483  		To:       tcs[1].Fu.Username,
  1484  		Amount:   "30",
  1485  		Currency: &usd,
  1486  	})
  1487  	require.NoError(t, err)
  1488  	t.Logf(spew.Sdump(bres))
  1489  	require.Equal(t, false, bres.ReadyToReview)
  1490  	require.Equal(t, "", bres.ToErrMsg)
  1491  	require.Equal(t, "You have *$0.00 USD* worth of Lumens available to send.", bres.AmountErrMsg)
  1492  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1493  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1494  	require.Equal(t, "94.2424166 XLM", bres.WorthDescription)
  1495  	require.Equal(t, worthInfo, bres.WorthInfo)
  1496  	require.False(t, bres.SendingIntentionXLM)
  1497  	require.Equal(t, "94.2424166 XLM", bres.DisplayAmountXLM)
  1498  	require.Equal(t, "$30.00 USD", bres.DisplayAmountFiat)
  1499  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1500  		Level:   "info",
  1501  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1502  	}})
  1503  
  1504  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  1505  	tcs[0].Backend.Gift(senderAccountID, "20")
  1506  	tcs[0].Backend.Gift(senderSecondaryAccountID, "30")
  1507  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  1508  	require.NoError(t, err)
  1509  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderSecondaryAccountID, "test")
  1510  	require.NoError(t, err)
  1511  
  1512  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1513  		From:   senderAccountID,
  1514  		To:     tcs[1].Fu.Username,
  1515  		Amount: "30",
  1516  	})
  1517  	require.NoError(t, err)
  1518  	t.Logf(spew.Sdump(bres))
  1519  	require.Equal(t, false, bres.ReadyToReview)
  1520  	require.Equal(t, "", bres.ToErrMsg)
  1521  	require.Equal(t, "You only have *18.9999900 XLM* available to send.", bres.AmountErrMsg)
  1522  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1523  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1524  	require.Equal(t, "$9.55 USD", bres.WorthDescription)
  1525  	require.Equal(t, worthInfo, bres.WorthInfo)
  1526  	require.True(t, bres.SendingIntentionXLM)
  1527  	require.Equal(t, "30 XLM", bres.DisplayAmountXLM)
  1528  	require.Equal(t, "$9.55 USD", bres.DisplayAmountFiat)
  1529  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1530  		Level:   "info",
  1531  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1532  	}})
  1533  
  1534  	// The user's available-to-send is $6.0482288 which rounds up to $6.05.
  1535  	// Avoid the situation where you type $6.05 and it responds "your ATS is $6.05" (CORE-9338, related PICNIC-464)
  1536  	// Which while true in some way true, is not helpful for the user.
  1537  	// A user should always be able to send the string that is presented as their ATS.
  1538  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1539  		From:     senderAccountID,
  1540  		To:       tcs[1].Fu.Username,
  1541  		Amount:   "6.05",
  1542  		Currency: &usd,
  1543  	})
  1544  	require.NoError(t, err)
  1545  	t.Logf(spew.Sdump(bres))
  1546  	require.Equal(t, false, bres.ReadyToReview)
  1547  	require.Equal(t, "", bres.ToErrMsg)
  1548  	require.Equal(t, "You only have *$6.04 USD* worth of Lumens available to send.", bres.AmountErrMsg)
  1549  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1550  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1551  	require.Equal(t, "19.0055540 XLM", bres.WorthDescription)
  1552  	require.Equal(t, worthInfo, bres.WorthInfo)
  1553  	require.False(t, bres.SendingIntentionXLM)
  1554  	require.Equal(t, "19.0055540 XLM", bres.DisplayAmountXLM)
  1555  	require.Equal(t, "$6.05 USD", bres.DisplayAmountFiat)
  1556  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1557  		Level:   "info",
  1558  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1559  	}})
  1560  
  1561  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1562  		From:          senderAccountID,
  1563  		To:            recipientAccountID.String(),
  1564  		ToIsAccountID: true,
  1565  		Amount:        "0.01",
  1566  	})
  1567  	require.NoError(t, err)
  1568  	t.Logf(spew.Sdump(bres))
  1569  	require.Equal(t, false, bres.ReadyToReview)
  1570  	require.Equal(t, "", bres.ToErrMsg)
  1571  	require.Equal(t, "You must send at least *1 XLM*", bres.AmountErrMsg)
  1572  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1573  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1574  	require.Equal(t, "$0.00 USD", bres.WorthDescription)
  1575  	require.Equal(t, worthInfo, bres.WorthInfo)
  1576  	require.True(t, bres.SendingIntentionXLM)
  1577  	require.Equal(t, "0.0100000 XLM", bres.DisplayAmountXLM)
  1578  	require.Equal(t, "$0.00 USD", bres.DisplayAmountFiat)
  1579  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1580  		Level:   "info",
  1581  		Message: "Because it's their first transaction, you must send at least 1 XLM.",
  1582  	}})
  1583  
  1584  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1585  		From:   senderSecondaryAccountID,
  1586  		To:     "t_alice",
  1587  		Amount: "15",
  1588  	})
  1589  	require.NoError(t, err)
  1590  	t.Logf(spew.Sdump(bres))
  1591  	require.Equal(t, false, bres.ReadyToReview)
  1592  	require.Equal(t, "", bres.ToErrMsg)
  1593  	require.Equal(t, "", bres.AmountErrMsg)
  1594  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1595  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1596  	require.Equal(t, "$4.77 USD", bres.WorthDescription)
  1597  	require.Equal(t, worthInfo, bres.WorthInfo)
  1598  	require.True(t, bres.SendingIntentionXLM)
  1599  	require.Equal(t, "15 XLM", bres.DisplayAmountXLM)
  1600  	require.Equal(t, "$4.77 USD", bres.DisplayAmountFiat)
  1601  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1602  		Level:   "error",
  1603  		Message: "Because t_alice hasn’t set up their wallet yet, you can only send to them from your default account.",
  1604  	}})
  1605  
  1606  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1607  		From:   senderAccountID,
  1608  		To:     tcs[1].Fu.Username,
  1609  		Amount: "15",
  1610  	})
  1611  	require.NoError(t, err)
  1612  	t.Logf(spew.Sdump(bres))
  1613  	require.Equal(t, true, bres.ReadyToReview)
  1614  	require.Equal(t, "", bres.ToErrMsg)
  1615  	require.Equal(t, "", bres.AmountErrMsg)
  1616  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1617  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1618  	require.Equal(t, "$4.77 USD", bres.WorthDescription)
  1619  	require.Equal(t, worthInfo, bres.WorthInfo)
  1620  	require.True(t, bres.SendingIntentionXLM)
  1621  	require.Equal(t, "15 XLM", bres.DisplayAmountXLM)
  1622  	require.Equal(t, "$4.77 USD", bres.DisplayAmountFiat)
  1623  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1624  		Level:   "info",
  1625  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1626  	}})
  1627  	// and from non-primary account has an additional privacy banner
  1628  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1629  		From:   senderSecondaryAccountID,
  1630  		To:     tcs[1].Fu.Username,
  1631  		Amount: "15",
  1632  	})
  1633  	require.NoError(t, err)
  1634  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1635  		Level:   "info",
  1636  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 1 XLM.", tcs[1].Fu.Username),
  1637  	}, {
  1638  		Level:   "info",
  1639  		Message: "Your Keybase username will not be linked to this transaction.",
  1640  	}})
  1641  
  1642  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  1643  		BypassBid: true,
  1644  		From:      senderAccountID,
  1645  		To:        tcs[1].Fu.Username,
  1646  		Amount:    "15",
  1647  		Asset:     stellar1.AssetNative(),
  1648  	})
  1649  	require.NoError(t, err)
  1650  
  1651  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1652  		From:       senderAccountID,
  1653  		To:         tcs[1].Fu.Username,
  1654  		Amount:     "15",
  1655  		PublicMemo: "🥔🥔🥔🥔🥔🥔🥔🥔",
  1656  	})
  1657  	require.NoError(t, err)
  1658  	t.Logf(spew.Sdump(bres))
  1659  	require.Equal(t, false, bres.ReadyToReview)
  1660  	require.Equal(t, "", bres.ToErrMsg)
  1661  	require.Equal(t, "You only have *3.9999800 XLM* available to send.", bres.AmountErrMsg)
  1662  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1663  	require.Equal(t, "Memo is too long.", bres.PublicMemoErrMsg) // too many potatoes
  1664  	require.Equal(t, "$4.77 USD", bres.WorthDescription)
  1665  	require.Equal(t, worthInfo, bres.WorthInfo)
  1666  	require.True(t, bres.SendingIntentionXLM)
  1667  	require.Equal(t, "15 XLM", bres.DisplayAmountXLM)
  1668  	require.Equal(t, "$4.77 USD", bres.DisplayAmountFiat)
  1669  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{}) // recipient is funded so banner's gone
  1670  	// and from non-primary account has a privacy banner
  1671  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1672  		From:       senderSecondaryAccountID,
  1673  		To:         tcs[1].Fu.Username,
  1674  		Amount:     "15",
  1675  		PublicMemo: "🥔🥔🥔🥔🥔🥔🥔🥔",
  1676  	})
  1677  	require.NoError(t, err)
  1678  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1679  		Level:   "info",
  1680  		Message: "Your Keybase username will not be linked to this transaction.",
  1681  	}})
  1682  
  1683  	// Send an amount so close to available to send that the fee would push it it over the edge.
  1684  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1685  		From:   senderAccountID,
  1686  		To:     tcs[1].Fu.Username,
  1687  		Amount: "3.99999900",
  1688  	})
  1689  	require.NoError(t, err)
  1690  	t.Logf(spew.Sdump(bres))
  1691  	require.Equal(t, false, bres.ReadyToReview)
  1692  	require.Equal(t, "", bres.ToErrMsg)
  1693  	require.Equal(t, "You only have *3.9999800 XLM* available to send.", bres.AmountErrMsg)
  1694  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1695  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1696  	require.Equal(t, "$1.27 USD", bres.WorthDescription)
  1697  	require.Equal(t, worthInfo, bres.WorthInfo)
  1698  	require.True(t, bres.SendingIntentionXLM)
  1699  	require.Equal(t, "3.9999990 XLM", bres.DisplayAmountXLM)
  1700  	require.Equal(t, "$1.27 USD", bres.DisplayAmountFiat)
  1701  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1702  
  1703  	tcs[0].Backend.Gift(senderAccountID, "30")
  1704  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  1705  	require.NoError(t, err)
  1706  
  1707  	t.Logf("sending in amount composed in USD")
  1708  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1709  		From:     senderAccountID,
  1710  		To:       tcs[1].Fu.Username,
  1711  		Amount:   "8.50",
  1712  		Currency: &usd,
  1713  	})
  1714  	require.NoError(t, err)
  1715  	t.Logf(spew.Sdump(bres))
  1716  	require.Equal(t, true, bres.ReadyToReview)
  1717  	require.Equal(t, senderAccountID, bres.From)
  1718  	require.Equal(t, "", bres.ToErrMsg)
  1719  	require.Equal(t, "", bres.AmountErrMsg)
  1720  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1721  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1722  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1723  	require.Equal(t, worthInfo, bres.WorthInfo)
  1724  	require.Equal(t, "26.7020180", bres.WorthAmount)
  1725  	require.False(t, bres.SendingIntentionXLM)
  1726  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1727  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1728  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1729  
  1730  	t.Logf("using `fromDefaultAccount`")
  1731  	for _, x := range []string{"blank", "match", "wrong"} {
  1732  		var from stellar1.AccountID
  1733  		var fromRes stellar1.AccountID
  1734  		shouldFail := false
  1735  		switch x {
  1736  		case "blank":
  1737  			fromRes = senderAccountID
  1738  		case "match":
  1739  			from = senderAccountID
  1740  			fromRes = senderAccountID
  1741  			shouldFail = true
  1742  		case "wrong":
  1743  			otherAccountID, _ := randomStellarKeypair()
  1744  			from = otherAccountID
  1745  			shouldFail = true
  1746  		default:
  1747  			panic("bad case")
  1748  		}
  1749  		bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1750  			From:               from,
  1751  			FromPrimaryAccount: true,
  1752  			To:                 tcs[1].Fu.Username,
  1753  			Amount:             "8.50",
  1754  			Currency:           &usd,
  1755  		})
  1756  		if shouldFail {
  1757  			require.Error(t, err)
  1758  			require.Equal(t, "invalid build payment parameters", err.Error())
  1759  		} else {
  1760  			require.NoError(t, err)
  1761  			t.Logf(spew.Sdump(bres))
  1762  			require.Equal(t, true, bres.ReadyToReview)
  1763  			require.Equal(t, fromRes, bres.From, x)
  1764  			require.Equal(t, "", bres.ToErrMsg)
  1765  			require.Equal(t, "", bres.AmountErrMsg)
  1766  			require.Equal(t, "", bres.SecretNoteErrMsg)
  1767  			require.Equal(t, "", bres.PublicMemoErrMsg)
  1768  			require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1769  			require.Equal(t, worthInfo, bres.WorthInfo)
  1770  			require.False(t, bres.SendingIntentionXLM)
  1771  			require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1772  			require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1773  			requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1774  		}
  1775  	}
  1776  
  1777  	t.Logf("sending to account ID")
  1778  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1779  		From:          senderAccountID,
  1780  		To:            "GBJCIIIWEP2ZIKSNY3AP5GJ5OHNSN6Y4W5K4IVIY4VSQF5QLVE27GADK",
  1781  		ToIsAccountID: true,
  1782  		Amount:        "8.50",
  1783  		Currency:      &usd,
  1784  	})
  1785  	require.NoError(t, err)
  1786  	t.Logf(spew.Sdump(bres))
  1787  	require.Equal(t, true, bres.ReadyToReview)
  1788  	require.Equal(t, "", bres.ToErrMsg)
  1789  	require.Equal(t, "", bres.AmountErrMsg)
  1790  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1791  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1792  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1793  	require.Equal(t, worthInfo, bres.WorthInfo)
  1794  	require.False(t, bres.SendingIntentionXLM)
  1795  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1796  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1797  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1798  		Level:   "info",
  1799  		Message: "Because it's their first transaction, you must send at least 1 XLM.",
  1800  	}})
  1801  	// and from non-primary account has an additional privacy banner
  1802  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1803  		From:          senderSecondaryAccountID,
  1804  		To:            "GBJCIIIWEP2ZIKSNY3AP5GJ5OHNSN6Y4W5K4IVIY4VSQF5QLVE27GADK",
  1805  		ToIsAccountID: true,
  1806  		Amount:        "8.50",
  1807  		Currency:      &usd,
  1808  	})
  1809  	require.NoError(t, err)
  1810  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1811  		Level:   "info",
  1812  		Message: "Because it's their first transaction, you must send at least 1 XLM.",
  1813  	}, {
  1814  		Level:   "info",
  1815  		Message: "Your Keybase username will not be linked to this transaction.",
  1816  	}})
  1817  
  1818  	t.Logf("sending to account ID that is someone's primary")
  1819  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1820  		From:          senderAccountID,
  1821  		To:            senderAccountID.String(),
  1822  		ToIsAccountID: true,
  1823  		Amount:        "8.50",
  1824  		Currency:      &usd,
  1825  	})
  1826  	require.NoError(t, err)
  1827  	t.Logf(spew.Sdump(bres))
  1828  	require.Equal(t, true, bres.ReadyToReview)
  1829  	require.Equal(t, "", bres.ToErrMsg)
  1830  	require.Equal(t, "", bres.AmountErrMsg)
  1831  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1832  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1833  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1834  	require.Equal(t, worthInfo, bres.WorthInfo)
  1835  	require.False(t, bres.SendingIntentionXLM)
  1836  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1837  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1838  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1839  
  1840  	upak, _, err := tcs[0].G.GetUPAKLoader().LoadV2(
  1841  		libkb.NewLoadUserArgWithMetaContext(tcs[0].MetaContext()).WithPublicKeyOptional().
  1842  			WithUID(tcs[1].Fu.User.GetUID()).WithForcePoll(true))
  1843  	require.NoError(t, err)
  1844  	require.NotNil(t, upak.Current.StellarAccountID)
  1845  
  1846  	t.Logf("sending to account ID that is someone's primary")
  1847  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1848  		From:          senderAccountID,
  1849  		To:            *upak.Current.StellarAccountID,
  1850  		ToIsAccountID: true,
  1851  		Amount:        "8.50",
  1852  		Currency:      &usd,
  1853  	})
  1854  	require.NoError(t, err)
  1855  	t.Logf(spew.Sdump(bres))
  1856  	require.Equal(t, true, bres.ReadyToReview)
  1857  	require.Equal(t, "", bres.ToErrMsg)
  1858  	require.Equal(t, "", bres.AmountErrMsg)
  1859  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1860  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1861  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1862  	require.Equal(t, worthInfo, bres.WorthInfo)
  1863  	require.False(t, bres.SendingIntentionXLM)
  1864  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1865  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1866  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1867  }
  1868  
  1869  // Regression test for a case where, under the given balance and exchange parameters,
  1870  // an attempt to send $2.32 would respond that you only had $2.32 available to send.
  1871  // Which didn't make sense. (was PICNIC-464, related CORE-9338)
  1872  func TestBuildPaymentLocalATSRounding(t *testing.T) {
  1873  	tcs, cleanup := setupNTests(t, 1)
  1874  	defer cleanup()
  1875  
  1876  	acceptDisclaimer(tcs[0])
  1877  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  1878  	require.NoError(t, err)
  1879  
  1880  	tcs[0].Backend.SetExchangeRate("0.0622381942426")
  1881  	worthInfo := "$1.00 = 16.0673042 XLM\nSource: coinmarketcap.com"
  1882  
  1883  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  1884  	tcs[0].Backend.Gift(senderAccountID, "38.2713786")
  1885  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  1886  	require.NoError(t, err)
  1887  
  1888  	bres, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1889  		From:     senderAccountID,
  1890  		To:       "t_alice",
  1891  		Amount:   "2.32",
  1892  		Currency: &usd,
  1893  	})
  1894  	require.NoError(t, err)
  1895  	t.Logf(spew.Sdump(bres))
  1896  	require.Equal(t, false, bres.ReadyToReview)
  1897  	require.Equal(t, "", bres.ToErrMsg)
  1898  	// Before the fix, AmountErrMsg had $2.32
  1899  	require.Equal(t, "You only have *$2.31 USD* worth of Lumens available to send.", bres.AmountErrMsg)
  1900  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1901  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1902  	require.Equal(t, "37.2761458 XLM", bres.WorthDescription)
  1903  	require.Equal(t, worthInfo, bres.WorthInfo)
  1904  	require.False(t, bres.SendingIntentionXLM)
  1905  	require.Equal(t, "37.2761458 XLM", bres.DisplayAmountXLM)
  1906  	require.Equal(t, "$2.32 USD", bres.DisplayAmountFiat)
  1907  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1908  		Level:   "info",
  1909  		Message: fmt.Sprintf("Because it's %v's first transaction, you must send at least 2.01 XLM.", "t_alice"),
  1910  	}})
  1911  }
  1912  
  1913  func TestBuildPaymentLocalAdvancedBanner(t *testing.T) {
  1914  	tcs, cleanup := setupNTests(t, 4)
  1915  	defer cleanup()
  1916  
  1917  	acceptDisclaimer(tcs[0])
  1918  	acceptDisclaimer(tcs[1])
  1919  	acceptDisclaimer(tcs[2])
  1920  	acceptDisclaimer(tcs[3])
  1921  	fakeAcct := tcs[0].Backend.ImportAccountsForUser(tcs[0])[0]
  1922  	fakeAcct2 := tcs[1].Backend.ImportAccountsForUser(tcs[1])[0]
  1923  	fakeAcct3 := tcs[2].Backend.ImportAccountsForUser(tcs[2])[0]
  1924  	fakeAcct4 := tcs[3].Backend.ImportAccountsForUser(tcs[3])[0]
  1925  	tcs[0].Backend.Gift(fakeAcct.accountID, "100")
  1926  	tcs[0].Backend.Gift(fakeAcct2.accountID, "100")
  1927  	tcs[0].Backend.Gift(fakeAcct3.accountID, "100")
  1928  	tcs[0].Backend.Gift(fakeAcct4.accountID, "100")
  1929  
  1930  	err := tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), fakeAcct.accountID, "test")
  1931  	require.NoError(t, err)
  1932  	err = tcs[1].Srv.walletState.Refresh(tcs[1].MetaContext(), fakeAcct2.accountID, "test")
  1933  	require.NoError(t, err)
  1934  	err = tcs[2].Srv.walletState.Refresh(tcs[2].MetaContext(), fakeAcct3.accountID, "test")
  1935  	require.NoError(t, err)
  1936  	err = tcs[3].Srv.walletState.Refresh(tcs[3].MetaContext(), fakeAcct4.accountID, "test")
  1937  	require.NoError(t, err)
  1938  
  1939  	t.Logf("sending from one account to another that only have native assets")
  1940  	bres, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1941  		From:          fakeAcct.accountID,
  1942  		To:            fakeAcct2.accountID.String(),
  1943  		ToIsAccountID: true,
  1944  		Amount:        "8.50",
  1945  		Currency:      &usd,
  1946  	})
  1947  	require.NoError(t, err)
  1948  	t.Logf(spew.Sdump(bres))
  1949  	require.Equal(t, true, bres.ReadyToReview)
  1950  	require.Equal(t, "", bres.ToErrMsg)
  1951  	require.Equal(t, "", bres.AmountErrMsg)
  1952  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1953  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1954  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1955  	require.False(t, bres.SendingIntentionXLM)
  1956  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1957  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1958  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{})
  1959  
  1960  	t.Logf("sending from an account with non-native assets to an account with only native assets")
  1961  	astro := tcs[0].Backend.CreateFakeAsset("AstroDollars")
  1962  	fakeAcct.AdjustAssetBalance(0, astro)
  1963  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), fakeAcct.accountID, "test")
  1964  	require.NoError(t, err)
  1965  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1966  		From:          fakeAcct.accountID,
  1967  		To:            fakeAcct3.accountID.String(),
  1968  		ToIsAccountID: true,
  1969  		Amount:        "8.50",
  1970  		Currency:      &usd,
  1971  	})
  1972  	require.NoError(t, err)
  1973  	t.Logf(spew.Sdump(bres))
  1974  	require.Equal(t, true, bres.ReadyToReview)
  1975  	require.Equal(t, "", bres.ToErrMsg)
  1976  	require.Equal(t, "", bres.AmountErrMsg)
  1977  	require.Equal(t, "", bres.SecretNoteErrMsg)
  1978  	require.Equal(t, "", bres.PublicMemoErrMsg)
  1979  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  1980  	require.False(t, bres.SendingIntentionXLM)
  1981  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  1982  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  1983  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  1984  		Level:                 "info",
  1985  		OfferAdvancedSendForm: stellar1.AdvancedBanner_SENDER_BANNER,
  1986  	}})
  1987  
  1988  	t.Logf("sending from an account with only native assets to an account with non-native assets")
  1989  	bres, err = tcs[2].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  1990  		From:          fakeAcct3.accountID,
  1991  		To:            fakeAcct.accountID.String(),
  1992  		ToIsAccountID: true,
  1993  		Amount:        "8.50",
  1994  		Currency:      &usd,
  1995  	})
  1996  	require.NoError(t, err)
  1997  	t.Logf(spew.Sdump(bres))
  1998  	require.Equal(t, true, bres.ReadyToReview)
  1999  	require.Equal(t, "", bres.ToErrMsg)
  2000  	require.Equal(t, "", bres.AmountErrMsg)
  2001  	require.Equal(t, "", bres.SecretNoteErrMsg)
  2002  	require.Equal(t, "", bres.PublicMemoErrMsg)
  2003  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  2004  	require.False(t, bres.SendingIntentionXLM)
  2005  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  2006  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  2007  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  2008  		Level:                 "info",
  2009  		OfferAdvancedSendForm: stellar1.AdvancedBanner_RECEIVER_BANNER,
  2010  	}})
  2011  
  2012  	t.Logf("sending from an account with non-native assets to an account with the same non-native asset")
  2013  	fakeAcct4.AdjustAssetBalance(0, astro)
  2014  	err = tcs[3].Srv.walletState.Refresh(tcs[3].MetaContext(), fakeAcct4.accountID, "test")
  2015  	require.NoError(t, err)
  2016  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2017  		From:          fakeAcct.accountID,
  2018  		To:            fakeAcct4.accountID.String(),
  2019  		ToIsAccountID: true,
  2020  		Amount:        "8.50",
  2021  		Currency:      &usd,
  2022  	})
  2023  	require.NoError(t, err)
  2024  	t.Logf(spew.Sdump(bres))
  2025  	require.Equal(t, true, bres.ReadyToReview)
  2026  	require.Equal(t, "", bres.ToErrMsg)
  2027  	require.Equal(t, "", bres.AmountErrMsg)
  2028  	require.Equal(t, "", bres.SecretNoteErrMsg)
  2029  	require.Equal(t, "", bres.PublicMemoErrMsg)
  2030  	require.Equal(t, "26.7020180 XLM", bres.WorthDescription)
  2031  	require.False(t, bres.SendingIntentionXLM)
  2032  	require.Equal(t, "26.7020180 XLM", bres.DisplayAmountXLM)
  2033  	require.Equal(t, "$8.50 USD", bres.DisplayAmountFiat)
  2034  	requireBannerSet(t, bres.DeepCopy().Banners, []stellar1.SendBannerLocal{{
  2035  		Level:                 "info",
  2036  		OfferAdvancedSendForm: stellar1.AdvancedBanner_RECEIVER_BANNER,
  2037  	}})
  2038  }
  2039  
  2040  // Simple happy path case.
  2041  func TestBuildPaymentLocalBidHappy(t *testing.T) {
  2042  	testBuildPaymentLocalBidHappy(t, false)
  2043  }
  2044  
  2045  func TestBuildPaymentLocalBidHappyBypassReview(t *testing.T) {
  2046  	testBuildPaymentLocalBidHappy(t, true)
  2047  }
  2048  
  2049  func testBuildPaymentLocalBidHappy(t *testing.T, bypassReview bool) {
  2050  	t.Logf("BypassReview:%v", bypassReview)
  2051  
  2052  	tcs, cleanup := setupNTests(t, 2)
  2053  	defer cleanup()
  2054  
  2055  	acceptDisclaimer(tcs[0])
  2056  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  2057  	require.NoError(t, err)
  2058  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2059  	tcs[0].Backend.Gift(senderAccountID, "100")
  2060  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  2061  	require.NoError(t, err)
  2062  
  2063  	bid1, err := tcs[0].Srv.StartBuildPaymentLocal(context.Background(), 0)
  2064  	require.NoError(t, err)
  2065  
  2066  	bres, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2067  		Bid:    bid1,
  2068  		From:   senderAccountID,
  2069  		To:     tcs[1].Fu.Username,
  2070  		Amount: "11",
  2071  	})
  2072  	require.NoError(t, err)
  2073  	t.Logf(spew.Sdump(bres))
  2074  	require.Equal(t, true, bres.ReadyToReview)
  2075  
  2076  	t.Logf("Change the amount")
  2077  	bres, err = tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2078  		Bid:    bid1,
  2079  		From:   senderAccountID,
  2080  		To:     tcs[1].Fu.Username,
  2081  		Amount: "15",
  2082  	})
  2083  	require.NoError(t, err)
  2084  	t.Logf(spew.Sdump(bres))
  2085  	require.Equal(t, true, bres.ReadyToReview)
  2086  
  2087  	if !bypassReview {
  2088  		reviewPaymentExpectQuickSuccess(t, tcs[0], stellar1.ReviewPaymentLocalArg{
  2089  			Bid: bid1,
  2090  		})
  2091  	}
  2092  
  2093  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  2094  		Bid:          bid1,
  2095  		BypassReview: bypassReview,
  2096  		From:         senderAccountID,
  2097  		To:           tcs[1].Fu.Username,
  2098  		Amount:       "15",
  2099  		Asset:        stellar1.AssetNative(),
  2100  	})
  2101  	require.NoError(t, err)
  2102  }
  2103  
  2104  func reviewPaymentExpectQuickSuccess(t testing.TB, tc *TestContext, arg stellar1.ReviewPaymentLocalArg) {
  2105  	start := time.Now()
  2106  	timeout := time.Second
  2107  	if libkb.UseCITime(tc.G) {
  2108  		timeout = 15 * time.Second
  2109  	}
  2110  	timeoutCh := time.After(timeout)
  2111  	mockUI := tc.Srv.uiSource.(*testUISource).stellarUI.(*mockStellarUI)
  2112  	original := mockUI.PaymentReviewedHandler
  2113  	defer func() {
  2114  		mockUI.PaymentReviewedHandler = original
  2115  	}()
  2116  	reviewSuccessCh := make(chan struct{}, 1)
  2117  	reviewExitedCh := make(chan struct{}, 1)
  2118  	mockUI.PaymentReviewedHandler = func(ctx context.Context, notification stellar1.PaymentReviewedArg) error {
  2119  		assert.Equal(t, arg.Bid, notification.Msg.Bid)
  2120  		// Allow the not-following warning banner.
  2121  		if len(notification.Msg.Banners) == 1 {
  2122  			assert.True(t, regexp.MustCompile(
  2123  				`^You are not following .*\. Are you sure this is the right person\?$`,
  2124  			).MatchString(notification.Msg.Banners[0].Message))
  2125  			expect := []stellar1.SendBannerLocal{{
  2126  				Level:   "warning",
  2127  				Message: notification.Msg.Banners[0].Message,
  2128  			}}
  2129  			assert.Equal(t, expect, notification.Msg.Banners)
  2130  		} else {
  2131  			assert.Len(t, notification.Msg.Banners, 0)
  2132  		}
  2133  		switch notification.Msg.NextButton {
  2134  		case "spinning":
  2135  		case "enabled":
  2136  			select {
  2137  			case reviewSuccessCh <- struct{}{}:
  2138  			default:
  2139  			}
  2140  		default:
  2141  			assert.Failf(t, "unexpected button status", "%v", notification.Msg.NextButton)
  2142  		}
  2143  		return nil
  2144  	}
  2145  	go func() {
  2146  		err := tc.Srv.ReviewPaymentLocal(context.Background(), arg)
  2147  		assert.NoError(t, err) // Use 'assert' since 'require' can only be used from the main goroutine.
  2148  		reviewExitedCh <- struct{}{}
  2149  	}()
  2150  	select {
  2151  	case <-timeoutCh:
  2152  		assert.Fail(t, "timed out")
  2153  	case <-reviewSuccessCh:
  2154  	}
  2155  	select {
  2156  	case <-timeoutCh:
  2157  		assert.Fail(t, "timed out")
  2158  	case <-reviewExitedCh:
  2159  	}
  2160  	t.Logf("review ran for %v", time.Since(start))
  2161  	check(t)
  2162  }
  2163  
  2164  func reviewPaymentExpectContractFailure(t testing.TB, tc *TestContext, arg stellar1.ReviewPaymentLocalArg, msg string) {
  2165  	start := time.Now()
  2166  	timeout := time.Second
  2167  	if libkb.UseCITime(tc.G) {
  2168  		timeout = 15 * time.Second
  2169  	}
  2170  	timeoutCh := time.After(timeout)
  2171  	mockUI := tc.Srv.uiSource.(*testUISource).stellarUI.(*mockStellarUI)
  2172  	original := mockUI.PaymentReviewedHandler
  2173  	defer func() {
  2174  		mockUI.PaymentReviewedHandler = original
  2175  	}()
  2176  	mockUI.PaymentReviewedHandler = func(ctx context.Context, notification stellar1.PaymentReviewedArg) error {
  2177  		assert.Equal(t, arg.Bid, notification.Msg.Bid)
  2178  		switch notification.Msg.NextButton {
  2179  		case "spinning":
  2180  		case "disabled":
  2181  		default:
  2182  			assert.Failf(t, "unexpected button status", "%v", notification.Msg.NextButton)
  2183  		}
  2184  		return nil
  2185  	}
  2186  	reviewFinishCh := make(chan error, 1)
  2187  	go func() {
  2188  		err := tc.Srv.ReviewPaymentLocal(context.Background(), arg)
  2189  		reviewFinishCh <- err
  2190  	}()
  2191  	select {
  2192  	case <-timeoutCh:
  2193  		assert.Fail(t, "timed out")
  2194  	case err := <-reviewFinishCh:
  2195  		require.Error(t, err)
  2196  		require.Equal(t, msg, err.Error())
  2197  	}
  2198  	t.Logf("review ran for %v", time.Since(start))
  2199  	check(t)
  2200  }
  2201  
  2202  // Start a review. Expect that the review will send a broken tracking banner and then hang.
  2203  // `expectSuccess` can be called later after fixing the tracking and will assert that the review soon enables the button.
  2204  func reviewPaymentExpectBrokenTracking(t testing.TB, tc *TestContext, arg stellar1.ReviewPaymentLocalArg) (expectSuccess func()) {
  2205  	start := time.Now()
  2206  	timeout := time.Second
  2207  	if libkb.UseCITime(tc.G) {
  2208  		timeout = 15 * time.Second
  2209  	}
  2210  	timeoutCh := time.After(timeout)
  2211  	mockUI := tc.Srv.uiSource.(*testUISource).stellarUI.(*mockStellarUI)
  2212  	original := mockUI.PaymentReviewedHandler
  2213  	reviewDisabledCh := make(chan struct{}, 1)
  2214  	// Install a handler that's geared for track failures.
  2215  	mockUI.PaymentReviewedHandler = func(ctx context.Context, notification stellar1.PaymentReviewedArg) error {
  2216  		assert.Equal(t, arg.Bid, notification.Msg.Bid)
  2217  		switch notification.Msg.NextButton {
  2218  		case "spinning":
  2219  		case "disabled":
  2220  			select {
  2221  			case reviewDisabledCh <- struct{}{}:
  2222  			default:
  2223  				assert.Fail(t, "review disabled channel full")
  2224  			}
  2225  		default:
  2226  			assert.Failf(t, "unexpected button status", "%v", notification.Msg.NextButton)
  2227  		}
  2228  		return nil
  2229  	}
  2230  	reviewFinishCh := make(chan error, 1)
  2231  	go func() {
  2232  		err := tc.Srv.ReviewPaymentLocal(context.Background(), arg)
  2233  		reviewFinishCh <- err
  2234  	}()
  2235  	select {
  2236  	case <-timeoutCh:
  2237  		assert.Fail(t, "timed out")
  2238  	case err := <-reviewFinishCh:
  2239  		require.FailNowf(t, "review unexpectedly finished", "%v", err)
  2240  	case <-reviewDisabledCh:
  2241  		// great
  2242  	}
  2243  	t.Logf("review ran for %v", time.Since(start))
  2244  	check(t)
  2245  
  2246  	// Install a new handler that's geared for success.
  2247  	reviewEnabledCh := make(chan struct{}, 1)
  2248  	mockUI.PaymentReviewedHandler = func(ctx context.Context, notification stellar1.PaymentReviewedArg) error {
  2249  		assert.Equal(t, arg.Bid, notification.Msg.Bid)
  2250  		switch notification.Msg.NextButton {
  2251  		case "enabled":
  2252  			select {
  2253  			case reviewEnabledCh <- struct{}{}:
  2254  			default:
  2255  				assert.Fail(t, "review enabled channel full")
  2256  			}
  2257  		default:
  2258  			assert.Failf(t, "unexpected button status", "%v", notification.Msg.NextButton)
  2259  		}
  2260  		return nil
  2261  	}
  2262  	return func() {
  2263  		defer func() {
  2264  			mockUI.PaymentReviewedHandler = original
  2265  		}()
  2266  		timeoutCh := time.After(timeout)
  2267  		select {
  2268  		case <-timeoutCh:
  2269  			require.FailNow(t, "timed out")
  2270  		case <-reviewEnabledCh:
  2271  			// great
  2272  		}
  2273  		check(t)
  2274  	}
  2275  }
  2276  
  2277  // Review a payment.
  2278  // - At first the review fails on a tracking failure.
  2279  // - The user reaffirms their tracking of the recipient.
  2280  // - As a result the review succeeds.
  2281  func TestReviewPaymentLocal(t *testing.T) {
  2282  	tcs, cleanup := setupNTests(t, 2)
  2283  	defer cleanup()
  2284  
  2285  	acceptDisclaimer(tcs[0])
  2286  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  2287  	require.NoError(t, err)
  2288  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2289  	tcs[0].Backend.Gift(senderAccountID, "100")
  2290  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  2291  	require.NoError(t, err)
  2292  
  2293  	t.Logf("u1 proves rooter")
  2294  	_, sigID := proveRooter(tcs[1])
  2295  
  2296  	t.Logf("u0 tracks u1")
  2297  	_, err = kbtest.RunTrack(tcs[0].TestContext, tcs[0].Fu, tcs[1].Fu.Username)
  2298  	require.NoError(t, err)
  2299  
  2300  	t.Logf("u1 removes their proof")
  2301  	eng := engine.NewRevokeSigsEngine(tcs[1].G, []string{sigID.String()})
  2302  	err = engine.RunEngine2(tcs[1].MetaContext().WithUIs(libkb.UIs{
  2303  		LogUI:    tcs[1].G.UI.GetLogUI(),
  2304  		SecretUI: &libkb.TestSecretUI{Passphrase: "dummy-passphrase"},
  2305  	}), eng)
  2306  	require.NoError(t, err)
  2307  
  2308  	t.Logf("u0 starts a payment")
  2309  	bid1, err := tcs[0].Srv.StartBuildPaymentLocal(context.Background(), 0)
  2310  	require.NoError(t, err)
  2311  	amount := "11.0"
  2312  	buildRes, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2313  		Bid:    bid1,
  2314  		From:   senderAccountID,
  2315  		To:     tcs[1].Fu.Username,
  2316  		Amount: amount,
  2317  	})
  2318  	require.NoError(t, err)
  2319  	require.Equal(t, true, buildRes.ReadyToReview)
  2320  
  2321  	t.Logf("u0 starts review of a payment, which gets stuck on u1's broken proof")
  2322  	expectSuccess := reviewPaymentExpectBrokenTracking(t, tcs[0], stellar1.ReviewPaymentLocalArg{Bid: bid1})
  2323  
  2324  	t.Logf("u0 affirms tracking of u1, causing the review to complete")
  2325  	_, err = kbtest.RunTrack(tcs[0].TestContext, tcs[0].Fu, tcs[1].Fu.Username)
  2326  	require.NoError(t, err)
  2327  	expectSuccess()
  2328  
  2329  	t.Logf("u0 completes the send")
  2330  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  2331  		Bid:    bid1,
  2332  		From:   senderAccountID,
  2333  		To:     tcs[1].Fu.Username,
  2334  		Amount: amount,
  2335  		Asset:  stellar1.AssetNative(),
  2336  	})
  2337  	require.NoError(t, err)
  2338  }
  2339  
  2340  // Review a payment where recipient is a keybase.io federation address.
  2341  // - At first the review fails on a tracking failure.
  2342  // - The user reaffirms their tracking of the recipient.
  2343  // - As a result the review succeeds.
  2344  func TestKeybaseFederationReviewPaymentLocal(t *testing.T) {
  2345  	tcs, cleanup := setupNTests(t, 2)
  2346  	defer cleanup()
  2347  
  2348  	acceptDisclaimer(tcs[0])
  2349  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  2350  	require.NoError(t, err)
  2351  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2352  	tcs[0].Backend.Gift(senderAccountID, "100")
  2353  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  2354  	require.NoError(t, err)
  2355  
  2356  	t.Logf("u1 proves rooter")
  2357  	_, sigID := proveRooter(tcs[1])
  2358  
  2359  	t.Logf("u0 tracks u1")
  2360  	_, err = kbtest.RunTrack(tcs[0].TestContext, tcs[0].Fu, tcs[1].Fu.Username)
  2361  	require.NoError(t, err)
  2362  
  2363  	t.Logf("u1 removes their proof")
  2364  	eng := engine.NewRevokeSigsEngine(tcs[1].G, []string{sigID.String()})
  2365  	err = engine.RunEngine2(tcs[1].MetaContext().WithUIs(libkb.UIs{
  2366  		LogUI:    tcs[1].G.UI.GetLogUI(),
  2367  		SecretUI: &libkb.TestSecretUI{Passphrase: "dummy-passphrase"},
  2368  	}), eng)
  2369  	require.NoError(t, err)
  2370  
  2371  	t.Logf("u0 starts a payment")
  2372  	bid1, err := tcs[0].Srv.StartBuildPaymentLocal(context.Background(), 0)
  2373  	require.NoError(t, err)
  2374  	amount := "11.0"
  2375  	buildRes, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2376  		Bid:    bid1,
  2377  		From:   senderAccountID,
  2378  		To:     tcs[1].Fu.Username + "*keybase.io",
  2379  		Amount: amount,
  2380  	})
  2381  	require.NoError(t, err)
  2382  	require.Equal(t, true, buildRes.ReadyToReview)
  2383  
  2384  	t.Logf("u0 starts review of a payment, which gets stuck on u1's broken proof")
  2385  	expectSuccess := reviewPaymentExpectBrokenTracking(t, tcs[0], stellar1.ReviewPaymentLocalArg{Bid: bid1})
  2386  
  2387  	t.Logf("u0 affirms tracking of u1, causing the review to complete")
  2388  	_, err = kbtest.RunTrack(tcs[0].TestContext, tcs[0].Fu, tcs[1].Fu.Username)
  2389  	require.NoError(t, err)
  2390  	expectSuccess()
  2391  
  2392  	t.Logf("u0 completes the send")
  2393  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  2394  		Bid:    bid1,
  2395  		From:   senderAccountID,
  2396  		To:     tcs[1].Fu.Username + "*keybase.io",
  2397  		Amount: amount,
  2398  		Asset:  stellar1.AssetNative(),
  2399  	})
  2400  	require.NoError(t, err)
  2401  }
  2402  
  2403  // Review a payment where recipient is an SBS twitter user.
  2404  func TestReviewPaymentLocalSBS(t *testing.T) {
  2405  	tcs, cleanup := setupNTests(t, 2)
  2406  	defer cleanup()
  2407  
  2408  	acceptDisclaimer(tcs[0])
  2409  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  2410  	require.NoError(t, err)
  2411  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2412  	tcs[0].Backend.Gift(senderAccountID, "100")
  2413  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  2414  	require.NoError(t, err)
  2415  
  2416  	t.Logf("u0 starts a payment")
  2417  	bid1, err := tcs[0].Srv.StartBuildPaymentLocal(context.Background(), 0)
  2418  	require.NoError(t, err)
  2419  	amount := "11.0"
  2420  	buildRes, err := tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2421  		Bid:    bid1,
  2422  		From:   senderAccountID,
  2423  		To:     "torproject@twitter",
  2424  		Amount: amount,
  2425  	})
  2426  	require.NoError(t, err)
  2427  	require.Equal(t, true, buildRes.ReadyToReview)
  2428  
  2429  	t.Logf("u0 starts a review of the payment")
  2430  	reviewPaymentExpectQuickSuccess(t, tcs[0], stellar1.ReviewPaymentLocalArg{Bid: bid1})
  2431  
  2432  	t.Logf("u0 completes the send")
  2433  	_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  2434  		Bid:    bid1,
  2435  		From:   senderAccountID,
  2436  		To:     "torproject@twitter",
  2437  		Amount: amount,
  2438  		Asset:  stellar1.AssetNative(),
  2439  	})
  2440  	require.NoError(t, err)
  2441  }
  2442  
  2443  // Cases where Send is blocked because the build gamut wasn't run.
  2444  func TestBuildPaymentLocalBidBlocked(t *testing.T) {
  2445  	tcs, cleanup := setupNTests(t, 2)
  2446  	defer cleanup()
  2447  
  2448  	acceptDisclaimer(tcs[0])
  2449  	acceptDisclaimer(tcs[1])
  2450  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  2451  	require.NoError(t, err)
  2452  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2453  	tcs[0].Backend.Gift(senderAccountID, "100")
  2454  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), senderAccountID, "test")
  2455  	require.NoError(t, err)
  2456  	fakeAcct := tcs[1].Backend.ImportAccountsForUser(tcs[1])[0]
  2457  	err = tcs[1].Srv.walletState.Refresh(tcs[1].MetaContext(), fakeAcct.accountID, "test")
  2458  	require.NoError(t, err)
  2459  
  2460  	send := func(bid stellar1.BuildPaymentID, amount string) (errorString string) {
  2461  		_, err = tcs[0].Srv.SendPaymentLocal(context.Background(), stellar1.SendPaymentLocalArg{
  2462  			Bid:    bid,
  2463  			From:   senderAccountID,
  2464  			To:     tcs[1].Fu.Username,
  2465  			Amount: amount,
  2466  			Asset:  stellar1.AssetNative(),
  2467  		})
  2468  		if err != nil {
  2469  			errorString = err.Error()
  2470  			require.NotEqual(t, "", errorString, "empty error string")
  2471  			return errorString
  2472  		}
  2473  		return ""
  2474  	}
  2475  
  2476  	build := func(bid stellar1.BuildPaymentID, amount string) (res stellar1.BuildPaymentResLocal, err error) {
  2477  		return tcs[0].Srv.BuildPaymentLocal(context.Background(), stellar1.BuildPaymentLocalArg{
  2478  			Bid:    bid,
  2479  			From:   senderAccountID,
  2480  			To:     tcs[1].Fu.Username,
  2481  			Amount: amount,
  2482  		})
  2483  	}
  2484  
  2485  	units := []struct {
  2486  		Key         string
  2487  		Description string
  2488  	}{
  2489  		{
  2490  			Key:         "forgotBuild",
  2491  			Description: "Can't send without a successful build. Also can't review without a build.",
  2492  		}, {
  2493  			Key:         "forgotReview",
  2494  			Description: "Can't send before review",
  2495  		}, {
  2496  			Key:         "forgotBoth",
  2497  			Description: "Can't send before precheck and review",
  2498  		}, {
  2499  			Key:         "wrongAmount",
  2500  			Description: "Can't send with wrong amount",
  2501  		}, {
  2502  			Key:         "afterStoppedByFailedSend",
  2503  			Description: "Can't send after stopped (by failed send)",
  2504  		}, {
  2505  			Key:         "afterStoppedBySend",
  2506  			Description: "Can't send after stopped (by successful send)",
  2507  		}, {
  2508  			Key:         "afterStoppedByStop",
  2509  			Description: "Can't send after stopped (by stop call)",
  2510  		}, {
  2511  			Key:         "afterStopppedByStopThenBuild",
  2512  			Description: "Can't send after stopped (by stop call) even after build",
  2513  		}, {
  2514  			Key:         "build-review-build-send",
  2515  			Description: "Can't send without a review after the _latest_ send",
  2516  		},
  2517  	}
  2518  
  2519  	for _, unit := range units {
  2520  		t.Logf("unit %v: %v", unit.Key, unit.Description)
  2521  		bid1, err := tcs[0].Srv.StartBuildPaymentLocal(context.Background(), 0)
  2522  		require.NoError(t, err)
  2523  
  2524  		reviewExpectContractFailure := func(msg string) {
  2525  			reviewPaymentExpectContractFailure(t, tcs[0], stellar1.ReviewPaymentLocalArg{Bid: bid1}, msg)
  2526  		}
  2527  		reviewExpectQuickSuccess := func() {
  2528  			reviewPaymentExpectQuickSuccess(t, tcs[0], stellar1.ReviewPaymentLocalArg{Bid: bid1})
  2529  		}
  2530  
  2531  		switch unit.Key {
  2532  		case "forgotBuild":
  2533  			reviewExpectContractFailure("this payment is not ready to review")
  2534  
  2535  			errString := send(bid1, "11")
  2536  			require.Equal(t, "this payment is not ready to send", errString)
  2537  
  2538  		case "forgotReview":
  2539  			bres, err := build(bid1, "12")
  2540  			require.NoError(t, err)
  2541  			require.Equal(t, true, bres.ReadyToReview)
  2542  
  2543  			errString := send(bid1, "11")
  2544  			require.Equal(t, "this payment has not been reviewed", errString)
  2545  
  2546  		case "forgotBoth":
  2547  			errString := send(bid1, "12")
  2548  			require.Equal(t, "this payment is not ready to send", errString)
  2549  
  2550  		case "wrongAmount":
  2551  			bres, err := build(bid1, "12")
  2552  			require.NoError(t, err)
  2553  			require.Equal(t, true, bres.ReadyToReview)
  2554  
  2555  			reviewExpectQuickSuccess()
  2556  
  2557  			errString := send(bid1, "15")
  2558  			require.Equal(t, "mismatched amount: 15 != 12", errString)
  2559  
  2560  		case "afterStoppedByFailedSend":
  2561  			bres, err := build(bid1, "12")
  2562  			require.NoError(t, err)
  2563  			require.Equal(t, true, bres.ReadyToReview)
  2564  
  2565  			reviewExpectQuickSuccess()
  2566  
  2567  			errString := send(bid1, "15")
  2568  			require.Equal(t, "mismatched amount: 15 != 12", errString)
  2569  
  2570  			errString = send(bid1, "15")
  2571  			require.Equal(t, "This payment might have already been sent. Check your recent payments before trying again.", errString)
  2572  
  2573  		case "afterStoppedBySend":
  2574  			bres, err := build(bid1, "11")
  2575  			require.NoError(t, err)
  2576  			require.Equal(t, true, bres.ReadyToReview)
  2577  
  2578  			reviewExpectQuickSuccess()
  2579  
  2580  			errString := send(bid1, "11")
  2581  			require.Equal(t, "", errString)
  2582  
  2583  			errString = send(bid1, "11")
  2584  			require.Equal(t, "This payment might have already been sent. Check your recent payments before trying again.", errString)
  2585  
  2586  		case "afterStoppedByStop":
  2587  			errString := send(bid1, "11")
  2588  			require.Equal(t, "this payment is not ready to send", errString)
  2589  
  2590  			err = tcs[0].Srv.StopBuildPaymentLocal(context.Background(), stellar1.StopBuildPaymentLocalArg{Bid: bid1})
  2591  			require.NoError(t, err)
  2592  
  2593  			errString = send(bid1, "11")
  2594  			require.Equal(t, "This payment might have already been sent. Check your recent payments before trying again.", errString)
  2595  
  2596  		case "afterStopppedByStopThenBuild":
  2597  			errString := send(bid1, "11")
  2598  			require.Equal(t, "this payment is not ready to send", errString)
  2599  
  2600  			err = tcs[0].Srv.StopBuildPaymentLocal(context.Background(), stellar1.StopBuildPaymentLocalArg{Bid: bid1})
  2601  			require.NoError(t, err)
  2602  
  2603  			_, err := build(bid1, "11")
  2604  			_ = err // Calling build on a stopped payment is do-no-harm undefined behavior.
  2605  
  2606  			reviewExpectContractFailure("This payment might have already been sent. Check your recent payments before trying again.") // Calling review on a stopped payment is do-no-harm not gonna happen.
  2607  
  2608  			errString = send(bid1, "11")
  2609  			require.Equal(t, "This payment might have already been sent. Check your recent payments before trying again.", errString)
  2610  
  2611  		case "build-review-build-send":
  2612  			bres, err := build(bid1, "12")
  2613  			require.NoError(t, err)
  2614  			require.Equal(t, true, bres.ReadyToReview)
  2615  
  2616  			reviewExpectQuickSuccess()
  2617  
  2618  			bres, err = build(bid1, "15")
  2619  			require.NoError(t, err)
  2620  			require.Equal(t, true, bres.ReadyToReview)
  2621  
  2622  			errString := send(bid1, "15")
  2623  			require.Equal(t, "this payment has not been reviewed", errString)
  2624  
  2625  		default:
  2626  			t.Fatalf("unknown case %v", unit.Key)
  2627  		}
  2628  	}
  2629  }
  2630  
  2631  // modifies `expected`
  2632  func requireBannerSet(t testing.TB, got []stellar1.SendBannerLocal, expected []stellar1.SendBannerLocal) {
  2633  	if len(got) != len(expected) {
  2634  		t.Logf("%s", spew.Sdump(got))
  2635  		require.Len(t, got, len(expected))
  2636  	}
  2637  	sort.Slice(got, func(i, j int) bool {
  2638  		return got[i].Message < got[j].Message
  2639  	})
  2640  	sort.Slice(expected, func(i, j int) bool {
  2641  		return expected[i].Message < expected[j].Message
  2642  	})
  2643  	for i := range expected {
  2644  		require.Equal(t, expected[i], got[i])
  2645  	}
  2646  }
  2647  
  2648  var usd = stellar1.OutsideCurrencyCode("USD")
  2649  
  2650  func TestGetSendAssetChoices(t *testing.T) {
  2651  	tcs, cleanup := setupNTests(t, 2)
  2652  	defer cleanup()
  2653  
  2654  	acceptDisclaimer(tcs[0])
  2655  	acceptDisclaimer(tcs[1])
  2656  	fakeAccts := tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2657  	fakeAccts2 := tcs[1].Backend.ImportAccountsForUser(tcs[1])
  2658  
  2659  	// Empty account (not even on the network), expecting to see 0
  2660  	// other assets here.
  2661  	choices, err := tcs[0].Srv.GetSendAssetChoicesLocal(context.Background(), stellar1.GetSendAssetChoicesLocalArg{
  2662  		From: fakeAccts[0].accountID,
  2663  	})
  2664  	require.NoError(t, err)
  2665  	require.Len(t, choices, 0)
  2666  
  2667  	// Same with `To` argument.
  2668  	choices, err = tcs[0].Srv.GetSendAssetChoicesLocal(context.Background(), stellar1.GetSendAssetChoicesLocalArg{
  2669  		From: fakeAccts[0].accountID,
  2670  		To:   tcs[1].Fu.Username,
  2671  	})
  2672  	require.NoError(t, err)
  2673  	require.Len(t, choices, 0)
  2674  
  2675  	// Test assets
  2676  	keys := tcs[0].Backend.CreateFakeAsset("KEYS")
  2677  	astro := tcs[0].Backend.CreateFakeAsset("AstroDollars")
  2678  
  2679  	// Adjust balance with 0 adds empty balance of given asset (mock
  2680  	// "open a trustline").
  2681  	fakeAccts[0].AdjustAssetBalance(0, keys)
  2682  	fakeAccts[0].AdjustAssetBalance(0, astro)
  2683  
  2684  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), fakeAccts[0].accountID, "test")
  2685  	require.NoError(t, err)
  2686  
  2687  	// New asset choices should be visible
  2688  	choices, err = tcs[0].Srv.GetSendAssetChoicesLocal(context.Background(), stellar1.GetSendAssetChoicesLocalArg{
  2689  		From: fakeAccts[0].accountID,
  2690  	})
  2691  	require.NoError(t, err)
  2692  	require.Len(t, choices, 2)
  2693  	require.Equal(t, keys, choices[0].Asset)
  2694  	require.Equal(t, astro, choices[1].Asset)
  2695  	for _, v := range choices {
  2696  		require.Equal(t, v.Asset.Code, v.Left)
  2697  		require.Equal(t, v.Asset.Issuer, v.Right)
  2698  		require.True(t, v.Enabled)
  2699  	}
  2700  
  2701  	// We should see the same choices, but all disabled because the
  2702  	// recipient does not accept them.
  2703  	choices2, err := tcs[0].Srv.GetSendAssetChoicesLocal(context.Background(), stellar1.GetSendAssetChoicesLocalArg{
  2704  		From: fakeAccts[0].accountID,
  2705  		To:   tcs[1].Fu.Username,
  2706  	})
  2707  	require.NoError(t, err)
  2708  	require.Len(t, choices2, len(choices))
  2709  	for i, v := range choices2 {
  2710  		require.Equal(t, choices[i].Asset, v.Asset)
  2711  		require.Equal(t, v.Asset.Code, v.Left)
  2712  		require.Equal(t, v.Asset.Issuer, v.Right)
  2713  		require.False(t, v.Enabled)
  2714  		require.Contains(t, v.Subtext, tcs[1].Fu.Username)
  2715  		require.Contains(t, v.Subtext, "does not accept")
  2716  		require.Contains(t, v.Subtext, v.Asset.Code)
  2717  	}
  2718  
  2719  	// Open AstroDollars for tcs[1]
  2720  	fakeAccts2[0].AdjustAssetBalance(0, astro)
  2721  
  2722  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), fakeAccts[0].accountID, "test")
  2723  	require.NoError(t, err)
  2724  
  2725  	choices2, err = tcs[0].Srv.GetSendAssetChoicesLocal(context.Background(), stellar1.GetSendAssetChoicesLocalArg{
  2726  		From: fakeAccts[0].accountID,
  2727  		To:   fakeAccts2[0].accountID.String(), // this time use account ID as `To` argument
  2728  	})
  2729  	require.NoError(t, err)
  2730  	require.Len(t, choices2, len(choices))
  2731  
  2732  	require.Equal(t, keys, choices2[0].Asset)
  2733  	require.False(t, choices2[0].Enabled)
  2734  	require.Equal(t, choices2[0].Subtext, fmt.Sprintf("Recipient does not accept %v", choices2[0].Asset.Code))
  2735  
  2736  	require.Equal(t, astro, choices2[1].Asset)
  2737  	require.True(t, choices2[1].Enabled)
  2738  
  2739  	// Try with arg.To AccountID not in the system.
  2740  	externalAcc := tcs[0].Backend.AddAccount(tcs[0].Fu.GetUID())
  2741  	choices3, err := tcs[0].Srv.GetSendAssetChoicesLocal(context.Background(), stellar1.GetSendAssetChoicesLocalArg{
  2742  		From: fakeAccts[0].accountID,
  2743  		To:   externalAcc.String(),
  2744  	})
  2745  	require.NoError(t, err)
  2746  	require.Len(t, choices3, len(choices))
  2747  	for _, v := range choices3 {
  2748  		require.False(t, v.Enabled)
  2749  		require.Contains(t, v.Subtext, "Recipient does not accept")
  2750  	}
  2751  }
  2752  
  2753  func TestMakeRequestLocalBasics(t *testing.T) {
  2754  	tcs, cleanup := setupNTests(t, 2)
  2755  	defer cleanup()
  2756  	acceptDisclaimer(tcs[0])
  2757  
  2758  	xlm := stellar1.AssetNative()
  2759  	_, err := tcs[0].Srv.MakeRequestLocal(context.Background(), stellar1.MakeRequestLocalArg{
  2760  		Recipient: tcs[1].Fu.Username,
  2761  		Asset:     &xlm,
  2762  	})
  2763  	require.Error(t, err)
  2764  
  2765  	_, err = tcs[0].Srv.MakeRequestLocal(context.Background(), stellar1.MakeRequestLocalArg{
  2766  		Recipient: tcs[1].Fu.Username,
  2767  		Asset:     &xlm,
  2768  		Amount:    "0",
  2769  	})
  2770  	require.Error(t, err)
  2771  
  2772  	_, err = tcs[0].Srv.MakeRequestLocal(context.Background(), stellar1.MakeRequestLocalArg{
  2773  		Recipient: tcs[1].Fu.Username,
  2774  		Asset:     &xlm,
  2775  		Amount:    "-1.2345",
  2776  	})
  2777  	require.Error(t, err)
  2778  
  2779  	reqID, err := tcs[0].Srv.MakeRequestLocal(context.Background(), stellar1.MakeRequestLocalArg{
  2780  		Recipient: tcs[1].Fu.Username,
  2781  		Asset:     &xlm,
  2782  		Amount:    "1.2345",
  2783  	})
  2784  	require.NoError(t, err)
  2785  	require.NotEmpty(t, reqID)
  2786  }
  2787  
  2788  func TestMakeRequestLocalNotifications(t *testing.T) {
  2789  	tcs, cleanup := setupNTests(t, 2)
  2790  	defer cleanup()
  2791  	acceptDisclaimer(tcs[0])
  2792  	acceptDisclaimer(tcs[1])
  2793  
  2794  	// set up notification listeners
  2795  	listenerSender := newChatListener()
  2796  	listenerRecip := newChatListener()
  2797  	tcs[0].G.NotifyRouter.AddListener(listenerSender)
  2798  	tcs[1].G.NotifyRouter.AddListener(listenerRecip)
  2799  
  2800  	xlm := stellar1.AssetNative()
  2801  	reqID, err := tcs[0].Srv.MakeRequestLocal(context.Background(), stellar1.MakeRequestLocalArg{
  2802  		Recipient: tcs[1].Fu.Username,
  2803  		Asset:     &xlm,
  2804  		Amount:    "1.2345",
  2805  	})
  2806  	require.NoError(t, err)
  2807  	require.NotEmpty(t, reqID)
  2808  
  2809  	// pretend that the chat message was unboxed and call the request loader to load the info:
  2810  	loaderSender := stellar.DefaultLoader(tcs[0].G)
  2811  	convID := chat1.ConversationID("efef")
  2812  	msgID := chat1.MessageID(654)
  2813  	loaderSender.LoadRequest(context.Background(), convID, msgID, tcs[0].Fu.Username, reqID)
  2814  
  2815  	loaderRecip := stellar.NewLoader(tcs[1].G)
  2816  	loaderRecip.LoadRequest(context.Background(), convID, msgID, tcs[0].Fu.Username, reqID)
  2817  
  2818  	// check the sender chat notification
  2819  	select {
  2820  	case info := <-listenerSender.requestInfos:
  2821  		require.NotNil(t, info)
  2822  		require.Equal(t, tcs[0].Fu.User.GetUID(), info.Uid)
  2823  		require.Equal(t, convID, info.ConvID)
  2824  		require.Equal(t, msgID, info.MsgID)
  2825  		require.Equal(t, "1.2345", info.Info.Amount)
  2826  		require.Equal(t, "1.2345 XLM", info.Info.AmountDescription)
  2827  		require.NotNil(t, info.Info.Asset)
  2828  		require.Equal(t, "native", info.Info.Asset.Type)
  2829  		require.Nil(t, info.Info.Currency)
  2830  		require.Equal(t, stellar1.RequestStatus_OK, info.Info.Status)
  2831  	case <-time.After(20 * time.Second):
  2832  		t.Fatal("timed out waiting for chat request info notification to sender")
  2833  	}
  2834  
  2835  	// check the recipient chat notification
  2836  	select {
  2837  	case info := <-listenerRecip.requestInfos:
  2838  		require.NotNil(t, info)
  2839  		require.Equal(t, tcs[1].Fu.User.GetUID(), info.Uid)
  2840  		require.Equal(t, convID, info.ConvID)
  2841  		require.Equal(t, msgID, info.MsgID)
  2842  		require.Equal(t, "1.2345", info.Info.Amount)
  2843  		require.Equal(t, "1.2345 XLM", info.Info.AmountDescription)
  2844  		require.NotNil(t, info.Info.Asset)
  2845  		require.Equal(t, "native", info.Info.Asset.Type)
  2846  		require.Nil(t, info.Info.Currency)
  2847  		require.Equal(t, stellar1.RequestStatus_OK, info.Info.Status)
  2848  	case <-time.After(20 * time.Second):
  2849  		t.Fatal("timed out waiting for chat request info notification to sender")
  2850  	}
  2851  
  2852  	// load it again, should not get another notification
  2853  	loaderRecip.LoadRequest(context.Background(), convID, msgID, tcs[0].Fu.Username, reqID)
  2854  	select {
  2855  	case info := <-listenerRecip.requestInfos:
  2856  		t.Fatalf("received request notification on second load: %+v", info)
  2857  	case <-time.After(100 * time.Millisecond):
  2858  	}
  2859  
  2860  }
  2861  
  2862  func TestSetMobileOnly(t *testing.T) {
  2863  	tcs, cleanup := setupTestsWithSettings(t, []usetting{usettingMobile})
  2864  	defer cleanup()
  2865  
  2866  	makeActiveDeviceOlder(t, tcs[0].G)
  2867  	setupWithNewBundle(t, tcs[0])
  2868  
  2869  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2870  	accountID := getPrimaryAccountID(tcs[0])
  2871  	walletAcctLocalArg := stellar1.GetWalletAccountLocalArg{AccountID: accountID}
  2872  
  2873  	// assert not mobile only yet
  2874  	mobileOnly, err := tcs[0].Srv.IsAccountMobileOnlyLocal(context.Background(), stellar1.IsAccountMobileOnlyLocalArg{AccountID: accountID})
  2875  	require.NoError(t, err)
  2876  	require.False(t, mobileOnly)
  2877  	accs, err := tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
  2878  	require.NoError(t, err)
  2879  	require.Len(t, accs, 1)
  2880  	require.Equal(t, accs[0].AccountMode, stellar1.AccountMode_USER)
  2881  	details, err := tcs[0].Srv.GetWalletAccountLocal(context.Background(), walletAcctLocalArg)
  2882  	require.NoError(t, err)
  2883  	require.Equal(t, stellar1.AccountMode_USER, details.AccountMode)
  2884  	require.Equal(t, true, details.AccountModeEditable)
  2885  	require.Equal(t, false, details.DeviceReadOnly)
  2886  
  2887  	err = tcs[0].Srv.SetAccountMobileOnlyLocal(context.Background(), stellar1.SetAccountMobileOnlyLocalArg{AccountID: accountID})
  2888  	require.NoError(t, err)
  2889  
  2890  	// yes mobile only
  2891  	mobileOnly, err = tcs[0].Srv.IsAccountMobileOnlyLocal(context.Background(), stellar1.IsAccountMobileOnlyLocalArg{AccountID: accountID})
  2892  	require.NoError(t, err)
  2893  	require.True(t, mobileOnly)
  2894  	accs, err = tcs[0].Srv.WalletGetAccountsCLILocal(context.Background())
  2895  	require.NoError(t, err)
  2896  	require.Len(t, accs, 1)
  2897  	require.Equal(t, accs[0].AccountMode, stellar1.AccountMode_MOBILE)
  2898  	details, err = tcs[0].Srv.GetWalletAccountLocal(context.Background(), walletAcctLocalArg)
  2899  	require.NoError(t, err)
  2900  	require.Equal(t, stellar1.AccountMode_MOBILE, details.AccountMode)
  2901  	require.Equal(t, true, details.AccountModeEditable)
  2902  	require.Equal(t, false, details.DeviceReadOnly)
  2903  
  2904  	mode, err := tcs[0].Srv.walletState.AccountMode(accountID)
  2905  	require.NoError(t, err)
  2906  	require.Equal(t, stellar1.AccountMode_MOBILE, mode)
  2907  
  2908  	// service_test verifies that `SetAccountMobileOnlyLocal` behaves correctly under the covers
  2909  
  2910  	// Provision new mobile to check AccountModeEditable and DeviceReadOnly
  2911  	tc2, cleanup2 := provisionNewDeviceForTest(t, tcs[0], keybase1.DeviceTypeV2_MOBILE)
  2912  	defer cleanup2()
  2913  	details, err = tc2.Srv.GetWalletAccountLocal(context.Background(), walletAcctLocalArg)
  2914  	require.NoError(t, err)
  2915  	require.Equal(t, stellar1.AccountMode_MOBILE, details.AccountMode)
  2916  	require.Equal(t, false, details.AccountModeEditable)
  2917  	require.Equal(t, true, details.DeviceReadOnly)
  2918  
  2919  	// Provision new desktop device.
  2920  	tc3, cleanup3 := provisionNewDeviceForTest(t, tcs[0], keybase1.DeviceTypeV2_DESKTOP)
  2921  	defer cleanup3()
  2922  	details, err = tc3.Srv.GetWalletAccountLocal(context.Background(), walletAcctLocalArg)
  2923  	require.NoError(t, err)
  2924  	require.Equal(t, stellar1.AccountMode_MOBILE, details.AccountMode)
  2925  	require.Equal(t, false, details.AccountModeEditable)
  2926  	require.Equal(t, true, details.DeviceReadOnly)
  2927  }
  2928  
  2929  const lumenautAccID = stellar1.AccountID("GCCD6AJOYZCUAQLX32ZJF2MKFFAUJ53PVCFQI3RHWKL3V47QYE2BNAUT")
  2930  
  2931  func TestSetInflation(t *testing.T) {
  2932  	tcs, cleanup := setupNTests(t, 1)
  2933  	defer cleanup()
  2934  
  2935  	// Test to see if RPCs are reaching remote (mocks).
  2936  	acceptDisclaimer(tcs[0])
  2937  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  2938  	require.NoError(t, err)
  2939  
  2940  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  2941  	tcs[0].Backend.Gift(senderAccountID, "20")
  2942  
  2943  	getInflation := func() stellar1.InflationDestinationResultLocal {
  2944  		res, err := tcs[0].Srv.GetInflationDestinationLocal(context.Background(), stellar1.GetInflationDestinationLocalArg{
  2945  			AccountID: senderAccountID,
  2946  		})
  2947  		require.NoError(t, err)
  2948  		return res
  2949  	}
  2950  	res := getInflation()
  2951  	require.Nil(t, res.Destination)
  2952  	require.Nil(t, res.KnownDestination)
  2953  	require.False(t, res.Self)
  2954  
  2955  	pub, _ := randomStellarKeypair()
  2956  	err = tcs[0].Srv.SetInflationDestinationLocal(context.Background(), stellar1.SetInflationDestinationLocalArg{
  2957  		AccountID:   senderAccountID,
  2958  		Destination: pub,
  2959  	})
  2960  	require.NoError(t, err)
  2961  
  2962  	res = getInflation()
  2963  	require.NotNil(t, res.Destination)
  2964  	require.Equal(t, pub, *res.Destination)
  2965  	require.Nil(t, res.KnownDestination)
  2966  	require.False(t, res.Self)
  2967  
  2968  	err = tcs[0].Srv.SetInflationDestinationLocal(context.Background(), stellar1.SetInflationDestinationLocalArg{
  2969  		AccountID:   senderAccountID,
  2970  		Destination: senderAccountID,
  2971  	})
  2972  	require.NoError(t, err)
  2973  
  2974  	res = getInflation()
  2975  	require.NotNil(t, res.Destination)
  2976  	require.Equal(t, senderAccountID, *res.Destination)
  2977  	require.Nil(t, res.KnownDestination)
  2978  	require.True(t, res.Self)
  2979  
  2980  	err = tcs[0].Srv.SetInflationDestinationLocal(context.Background(), stellar1.SetInflationDestinationLocalArg{
  2981  		AccountID:   senderAccountID,
  2982  		Destination: lumenautAccID,
  2983  	})
  2984  	require.NoError(t, err)
  2985  
  2986  	res = getInflation()
  2987  	require.NotNil(t, res.Destination)
  2988  	require.Equal(t, lumenautAccID, *res.Destination)
  2989  	require.NotNil(t, res.KnownDestination)
  2990  	require.Equal(t, lumenautAccID, res.KnownDestination.AccountID)
  2991  	require.Equal(t, "https://pool.lumenaut.net/", res.KnownDestination.Url)
  2992  	require.False(t, res.Self)
  2993  }
  2994  
  2995  func TestGetInflationDestinations(t *testing.T) {
  2996  	tcs, cleanup := setupNTests(t, 1)
  2997  	defer cleanup()
  2998  
  2999  	acceptDisclaimer(tcs[0])
  3000  	tcs[0].Backend.ImportAccountsForUser(tcs[0])
  3001  
  3002  	// This hits server, check if result is not empty and if lumenaut pool is
  3003  	// there (this should not change).
  3004  	res, err := tcs[0].Srv.GetPredefinedInflationDestinationsLocal(context.Background(), 0)
  3005  	require.NoError(t, err)
  3006  	require.NotEmpty(t, res)
  3007  	var found bool
  3008  	for _, dest := range res {
  3009  		if dest.Tag == "lumenaut" {
  3010  			require.False(t, found, "expecting to find only one lumenaut")
  3011  			found = true
  3012  			require.Equal(t, lumenautAccID, dest.AccountID)
  3013  			require.Equal(t, "Lumenaut", dest.Name)
  3014  			require.True(t, dest.Recommended)
  3015  			require.Equal(t, "https://pool.lumenaut.net/", dest.Url)
  3016  		}
  3017  	}
  3018  	require.True(t, found, "expecting to find lumenaut in the list")
  3019  }
  3020  
  3021  func TestManageTrustlines(t *testing.T) {
  3022  	tcs, cleanup := setupNTests(t, 1)
  3023  	defer cleanup()
  3024  
  3025  	otherAccountID, _ := randomStellarKeypair()
  3026  	trustlines, err := tcs[0].Srv.GetTrustlinesLocal(context.Background(), stellar1.GetTrustlinesLocalArg{
  3027  		AccountID: otherAccountID,
  3028  	})
  3029  	require.NoError(t, err)
  3030  	require.Len(t, trustlines, 0)
  3031  
  3032  	acceptDisclaimer(tcs[0])
  3033  	accounts := tcs[0].Backend.ImportAccountsForUser(tcs[0])
  3034  
  3035  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  3036  	require.NoError(t, err)
  3037  	tcs[0].Backend.Gift(senderAccountID, "20")
  3038  
  3039  	keys := tcs[0].Backend.CreateFakeAsset("KEYS")
  3040  	trustlineArg := stellar1.Trustline{
  3041  		AssetCode: stellar1.AssetCode(keys.Code),
  3042  		Issuer:    stellar1.AccountID(keys.Issuer),
  3043  	}
  3044  
  3045  	// Add trustline to the account.
  3046  	err = tcs[0].Srv.AddTrustlineLocal(context.Background(), stellar1.AddTrustlineLocalArg{
  3047  		AccountID: senderAccountID,
  3048  		Trustline: trustlineArg,
  3049  		Limit:     "",
  3050  	})
  3051  	require.NoError(t, err)
  3052  
  3053  	// Check if it shows up in GetAccountAssetsLocal
  3054  	balances, err := tcs[0].Srv.GetAccountAssetsLocal(context.Background(), stellar1.GetAccountAssetsLocalArg{
  3055  		AccountID: senderAccountID,
  3056  	})
  3057  	require.NoError(t, err)
  3058  	require.Len(t, balances, 2)
  3059  
  3060  	require.Equal(t, keys.Code, balances[1].AssetCode)
  3061  	require.Equal(t, keys.Code, balances[1].Name)
  3062  	require.Equal(t, keys.Issuer, balances[1].IssuerAccountID)
  3063  	require.Equal(t, "0", balances[1].BalanceTotal)
  3064  	require.Equal(t, "0", balances[1].BalanceAvailableToSend)
  3065  
  3066  	// Check if shows up it GetTrustlinesLocal
  3067  	balances2, err := tcs[0].Srv.GetTrustlinesLocal(context.Background(), stellar1.GetTrustlinesLocalArg{
  3068  		AccountID: senderAccountID,
  3069  	})
  3070  	require.NoError(t, err)
  3071  	require.Len(t, balances2, 1)
  3072  	require.Equal(t, keys, balances2[0].Asset)
  3073  	require.Equal(t, "0.0000000", balances2[0].Amount)
  3074  	require.Equal(t, "922337203685.4775807", balances2[0].Limit) // max limit
  3075  
  3076  	// Check if shows up it GetTrustlinesForRecipientLocal
  3077  	rtlines, err := tcs[0].Srv.GetTrustlinesForRecipientLocal(context.Background(), stellar1.GetTrustlinesForRecipientLocalArg{
  3078  		Recipient: tcs[0].Fu.Username,
  3079  	})
  3080  	require.NoError(t, err)
  3081  	require.Len(t, rtlines.Trustlines, 1)
  3082  	require.Equal(t, keys, rtlines.Trustlines[0].Asset)
  3083  	require.Equal(t, "0.0000000", rtlines.Trustlines[0].Amount)
  3084  	require.Equal(t, "922337203685.4775807", rtlines.Trustlines[0].Limit) // max limit
  3085  	require.Equal(t, rtlines.RecipientType, stellar1.ParticipantType_KEYBASE)
  3086  
  3087  	// Change limit.
  3088  	err = tcs[0].Srv.ChangeTrustlineLimitLocal(context.Background(), stellar1.ChangeTrustlineLimitLocalArg{
  3089  		AccountID: senderAccountID,
  3090  		Trustline: trustlineArg,
  3091  		Limit:     "100",
  3092  	})
  3093  	require.NoError(t, err)
  3094  	// Check if our mock account has changed:
  3095  	require.Equal(t, "100.0000000", accounts[0].otherBalances[0].Limit)
  3096  
  3097  	// Check if new limit shows up in BalancesLocal. Limit is not currently
  3098  	// returned via GetAccountAssetsLocal.
  3099  	balances2, err = tcs[0].Srv.BalancesLocal(context.Background(), senderAccountID)
  3100  	require.NoError(t, err)
  3101  	require.Len(t, balances2, 2)
  3102  	require.True(t, balances2[0].Asset.IsNativeXLM())
  3103  	require.Equal(t, "20.0000000", balances2[0].Amount)
  3104  	require.Equal(t, "", balances2[0].Limit)
  3105  	require.Equal(t, keys, balances2[1].Asset)
  3106  	require.Equal(t, "0.0000000", balances2[1].Amount)
  3107  	require.Equal(t, "100.0000000", balances2[1].Limit)
  3108  
  3109  	// Delete trustline.
  3110  	err = tcs[0].Srv.DeleteTrustlineLocal(context.Background(), stellar1.DeleteTrustlineLocalArg{
  3111  		AccountID: senderAccountID,
  3112  		Trustline: trustlineArg,
  3113  	})
  3114  	require.NoError(t, err)
  3115  
  3116  	// See if it's gone from GetAccountAssetsLocal.
  3117  	balances, err = tcs[0].Srv.GetAccountAssetsLocal(context.Background(), stellar1.GetAccountAssetsLocalArg{
  3118  		AccountID: senderAccountID,
  3119  	})
  3120  	require.NoError(t, err)
  3121  	require.Len(t, balances, 1)
  3122  	require.Equal(t, "Lumens", balances[0].Name)
  3123  	require.Equal(t, "Stellar network", balances[0].IssuerName)
  3124  	require.Equal(t, "", balances[0].IssuerAccountID)
  3125  }
  3126  
  3127  func TestManageTrustlinesErrors(t *testing.T) {
  3128  	tcs, cleanup := setupNTests(t, 1)
  3129  	defer cleanup()
  3130  
  3131  	acceptDisclaimer(tcs[0])
  3132  	accounts := tcs[0].Backend.ImportAccountsForUser(tcs[0])
  3133  
  3134  	senderAccountID, err := stellar.GetOwnPrimaryAccountID(tcs[0].MetaContext())
  3135  	require.NoError(t, err)
  3136  	tcs[0].Backend.Gift(senderAccountID, "20")
  3137  
  3138  	keys := tcs[0].Backend.CreateFakeAsset("KEYS")
  3139  	trustlineArg := stellar1.Trustline{
  3140  		AssetCode: stellar1.AssetCode(keys.Code),
  3141  		Issuer:    stellar1.AccountID(keys.Issuer),
  3142  	}
  3143  
  3144  	// Removing a trustline that's not currently in the account should fail.
  3145  	err = tcs[0].Srv.DeleteTrustlineLocal(context.Background(), stellar1.DeleteTrustlineLocalArg{
  3146  		AccountID: senderAccountID,
  3147  		Trustline: trustlineArg,
  3148  	})
  3149  	require.Error(t, err)
  3150  
  3151  	// Cannot change limist of a trustline that wasn't added.
  3152  	err = tcs[0].Srv.ChangeTrustlineLimitLocal(context.Background(), stellar1.ChangeTrustlineLimitLocalArg{
  3153  		AccountID: senderAccountID,
  3154  		Trustline: trustlineArg,
  3155  		Limit:     "10",
  3156  	})
  3157  	require.Error(t, err)
  3158  
  3159  	// Finally, add the trustline.
  3160  	err = tcs[0].Srv.AddTrustlineLocal(context.Background(), stellar1.AddTrustlineLocalArg{
  3161  		Trustline: trustlineArg,
  3162  		Limit:     "",
  3163  	})
  3164  	require.NoError(t, err)
  3165  
  3166  	b, err := stellarnet.ParseStellarAmount("20")
  3167  	require.NoError(t, err)
  3168  	accounts[0].AdjustAssetBalance(b, keys)
  3169  	err = tcs[0].Srv.walletState.Refresh(tcs[0].MetaContext(), accounts[0].accountID, "test adjust balance")
  3170  	require.NoError(t, err)
  3171  
  3172  	// Cannot change limit to below current balance.
  3173  	err = tcs[0].Srv.ChangeTrustlineLimitLocal(context.Background(), stellar1.ChangeTrustlineLimitLocalArg{
  3174  		AccountID: senderAccountID,
  3175  		Trustline: trustlineArg,
  3176  		Limit:     "10",
  3177  	})
  3178  	require.Error(t, err)
  3179  
  3180  	// Cannot remove trustline with balance.
  3181  	err = tcs[0].Srv.DeleteTrustlineLocal(context.Background(), stellar1.DeleteTrustlineLocalArg{
  3182  		AccountID: senderAccountID,
  3183  		Trustline: trustlineArg,
  3184  	})
  3185  	require.Error(t, err)
  3186  }
  3187  
  3188  type chatListener struct {
  3189  	libkb.NoopNotifyListener
  3190  
  3191  	paymentInfos chan chat1.ChatPaymentInfoArg
  3192  	requestInfos chan chat1.ChatRequestInfoArg
  3193  }
  3194  
  3195  func newChatListener() *chatListener {
  3196  	x := &chatListener{
  3197  		paymentInfos: make(chan chat1.ChatPaymentInfoArg, 1),
  3198  		requestInfos: make(chan chat1.ChatRequestInfoArg, 1),
  3199  	}
  3200  	return x
  3201  }
  3202  
  3203  func (c *chatListener) ChatPaymentInfo(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, info chat1.UIPaymentInfo) {
  3204  	c.paymentInfos <- chat1.ChatPaymentInfoArg{Uid: uid, ConvID: convID, MsgID: msgID, Info: info}
  3205  }
  3206  
  3207  func (c *chatListener) ChatRequestInfo(uid keybase1.UID, convID chat1.ConversationID, msgID chat1.MessageID, info chat1.UIRequestInfo) {
  3208  	c.requestInfos <- chat1.ChatRequestInfoArg{Uid: uid, ConvID: convID, MsgID: msgID, Info: info}
  3209  }
  3210  
  3211  func firstAccountName(t testing.TB, tc *TestContext) string {
  3212  	loggedInUsername := tc.G.ActiveDevice.Username(libkb.NewMetaContextForTest(tc.TestContext))
  3213  	require.True(t, loggedInUsername.IsValid())
  3214  	return fmt.Sprintf("%v's account", loggedInUsername)
  3215  }
  3216  
  3217  func check(t testing.TB) {
  3218  	if t.Failed() {
  3219  		// The test failed. Possibly in anothe goroutine. Look earlier in the logs for the real failure.
  3220  		require.FailNow(t, "test already failed")
  3221  	}
  3222  }
  3223  
  3224  func TestGetStaticConfigLocal(t *testing.T) {
  3225  	tcs, cleanup := setupNTests(t, 1)
  3226  	defer cleanup()
  3227  
  3228  	staticConfig, err := tcs[0].Srv.GetStaticConfigLocal(context.Background())
  3229  
  3230  	require.NoError(t, err)
  3231  	require.Equal(t, staticConfig.PaymentNoteMaxLength, 500)
  3232  	require.Equal(t, staticConfig.RequestNoteMaxLength, 240)
  3233  	require.Equal(t, staticConfig.PublicMemoMaxLength, 28)
  3234  }