diff --git a/services/accounts/accounts.go b/services/accounts/accounts.go index 1766d561b..fe66172e0 100644 --- a/services/accounts/accounts.go +++ b/services/accounts/accounts.go @@ -3,7 +3,6 @@ package accounts import ( "context" "errors" - "fmt" "strings" "time" @@ -15,14 +14,10 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/keypairs" - "github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/params" "github.com/status-im/status-go/protocol" ) -const pathWalletRoot = "m/44'/60'/0'/0" -const pathDefaultWallet = pathWalletRoot + "/0" - func NewAccountsAPI(manager *account.GethManager, config *params.NodeConfig, db *accounts.Database, feed *event.Feed, messenger **protocol.Messenger) *API { return &API{manager, config, db, feed, messenger} } @@ -105,232 +100,100 @@ func (api *API) DeleteAccount(ctx context.Context, address types.Address, passwo return (*api.messenger).DeleteAccount(address) } -func (api *API) AddAccountWatch(ctx context.Context, address string, name string, color string, emoji string) error { - account := &accounts.Account{ - Address: types.Address(common.HexToAddress(address)), - Type: accounts.AccountTypeWatch, - Name: name, - Emoji: emoji, - Color: color, +func (api *API) AddAccount(ctx context.Context, password string, account *accounts.Account) error { + if len(account.Address) == 0 { + return errors.New("`Address` field must be set") } + + if account.Wallet || account.Chat { + return errors.New("default wallet and chat account cannot be added this way") + } + + if len(account.Name) == 0 { + return errors.New("`Name` field must be set") + } + + if len(account.Emoji) == 0 { + return errors.New("`Emoji` field must be set") + } + + if len(account.Color) == 0 { + return errors.New("`Color` field must be set") + } + + if account.Type != accounts.AccountTypeWatch { + + if len(account.KeyUID) == 0 { + return errors.New("`KeyUID` field must be set") + } + + if len(account.PublicKey) == 0 { + return errors.New("`PublicKey` field must be set") + } + + if len(account.KeypairName) == 0 { + return errors.New("`KeypairName` field must be set") + } + + if account.Type != accounts.AccountTypeKey { + if len(account.DerivedFrom) == 0 { + return errors.New("`DerivedFrom` field must be set") + } + + if len(account.Path) == 0 { + return errors.New("`Path` field must be set") + } + } + } + + addressExists, err := api.db.AddressExists(account.Address) + if err != nil { + return err + } + + if addressExists { + return errors.New("account already exists") + } + + // we need to create local keystore file only if password is provided and the account is being added is of + // "generated" or "seed" type. + if (account.Type == accounts.AccountTypeGenerated || account.Type == accounts.AccountTypeSeed) && len(password) > 0 { + info, err := api.manager.AccountsGenerator().LoadAccount(account.DerivedFrom, password) + if err != nil { + return err + } + + _, err = api.manager.AccountsGenerator().StoreDerivedAccounts(info.ID, password, []string{account.Path}) + if err != nil { + return err + } + } + return api.SaveAccounts(ctx, []*accounts.Account{account}) } -func (api *API) AddAccountWithMnemonic( - ctx context.Context, - mnemonic string, - password string, - name string, - color string, - emoji string, -) error { - return api.addAccountWithMnemonic(ctx, mnemonic, password, name, color, emoji, pathWalletRoot) -} - -func (api *API) AddAccountWithMnemonicPasswordVerified( - ctx context.Context, - mnemonic string, - password string, - name string, - color string, - emoji string, -) error { - return api.addAccountWithMnemonicPasswordVerified(ctx, mnemonic, password, name, color, emoji, pathWalletRoot) -} - -func (api *API) AddAccountWithMnemonicAndPath( - ctx context.Context, - mnemonic string, - password string, - name string, - color string, - emoji string, - path string, -) error { - return api.addAccountWithMnemonic(ctx, mnemonic, password, name, color, emoji, path) -} - -func (api *API) AddAccountWithMnemonicAndPathPasswordVerified( - ctx context.Context, - mnemonic string, - password string, - name string, - color string, - emoji string, - path string, -) error { - return api.addAccountWithMnemonicPasswordVerified(ctx, mnemonic, password, name, color, emoji, path) -} - -// AddAccountWithPrivateKeyPasswordVerified adds an accounts.Account created from the given private key -// assuming that client has already authenticated logged in use, this function doesn't verify a password. -func (api *API) AddAccountWithPrivateKeyPasswordVerified( - ctx context.Context, - privateKey string, - password string, - name string, - color string, - emoji string, -) error { - +// Imports a new private key and creates local keystore file. +func (api *API) ImportPrivateKey(ctx context.Context, privateKey string, password string) error { info, err := api.manager.AccountsGenerator().ImportPrivateKey(privateKey) if err != nil { return err } - addressExists, err := api.db.AddressExists(types.Address(common.HexToAddress(info.Address))) + accs, err := api.db.GetAccountsByKeyUID(info.KeyUID) if err != nil { return err } - if addressExists { - return errors.New("account already exists") + + if len(accs) > 0 { + return errors.New("provided private key was already imported") } _, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password) - if err != nil { - return err - } - - account := &accounts.Account{ - Address: types.Address(common.HexToAddress(info.Address)), - KeyUID: info.KeyUID, - PublicKey: types.HexBytes(info.PublicKey), - Type: accounts.AccountTypeKey, - Name: name, - Emoji: emoji, - Color: color, - Path: pathDefaultWallet, - } - - return api.SaveAccounts(ctx, []*accounts.Account{account}) -} - -func (api *API) AddAccountWithPrivateKey( - ctx context.Context, - privateKey string, - password string, - name string, - color string, - emoji string, -) error { - err := api.verifyPassword(password) - if err != nil { - return err - } - - return api.AddAccountWithPrivateKeyPasswordVerified(ctx, privateKey, password, name, color, emoji) -} - -func (api *API) GenerateAccount( - ctx context.Context, - password string, - name string, - color string, - emoji string, -) error { - address, err := api.db.GetWalletRootAddress() - if err != nil { - return err - } - - latestDerivedPath, err := api.db.GetLatestDerivedPath() - if err != nil { - return err - } - - newDerivedPath := latestDerivedPath + 1 - path := fmt.Sprint(pathWalletRoot, "/", newDerivedPath) - - err = api.generateAccount(ctx, password, name, color, emoji, path, address.Hex()) - if err != nil { - return err - } - - err = api.db.SaveSettingField(settings.LatestDerivedPath, newDerivedPath) - if err != nil { - return err - } - return err } -func (api *API) GenerateAccountPasswordVerified( - ctx context.Context, - password string, - name string, - color string, - emoji string, -) error { - address, err := api.db.GetWalletRootAddress() - if err != nil { - return err - } - - latestDerivedPath, err := api.db.GetLatestDerivedPath() - if err != nil { - return err - } - - newDerivedPath := latestDerivedPath + 1 - path := fmt.Sprint(pathWalletRoot, "/", newDerivedPath) - - err = api.generateAccountPasswordVerified(ctx, password, name, color, emoji, path, address.Hex()) - if err != nil { - return err - } - - err = api.db.SaveSettingField(settings.LatestDerivedPath, newDerivedPath) - if err != nil { - return err - } - - return err -} - -func (api *API) GenerateAccountWithDerivedPath( - ctx context.Context, - password string, - name string, - color string, - emoji string, - path string, - derivedFrom string, -) error { - return api.generateAccount(ctx, password, name, color, emoji, path, derivedFrom) -} - -func (api *API) GenerateAccountWithDerivedPathPasswordVerified( - ctx context.Context, - password string, - name string, - color string, - emoji string, - path string, - derivedFrom string, -) error { - return api.generateAccountPasswordVerified(ctx, password, name, color, emoji, path, derivedFrom) -} - -func (api *API) verifyPassword(password string) error { - address, err := api.db.GetChatAddress() - if err != nil { - return err - } - _, err = api.manager.VerifyAccountPassword(api.config.KeyStoreDir, address.Hex(), password) - return err -} - -// addAccountWithMnemonicPasswordVerified adds an accounts.Account derived from the given Mnemonic -// assuming that client has already authenticated logged in use, this function doesn't verify a password. -func (api *API) addAccountWithMnemonicPasswordVerified( - ctx context.Context, - mnemonic string, - password string, - name string, - color string, - emoji string, - path string, -) error { +// Imports a new mnemonic and creates local keystore file. +func (api *API) ImportMnemonic(ctx context.Context, mnemonic string, password string) error { mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ") generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "") @@ -338,120 +201,25 @@ func (api *API) addAccountWithMnemonicPasswordVerified( return err } + accs, err := api.db.GetAccountsByKeyUID(generatedAccountInfo.KeyUID) + if err != nil { + return err + } + + if len(accs) > 0 { + return errors.New("provided mnemonic was already imported, to add new account use `AddAccount` endpoint") + } + _, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password) - if err != nil { - return err - } - - accountinfos, err := api.manager.AccountsGenerator().StoreDerivedAccounts(generatedAccountInfo.ID, password, []string{path}) - if err != nil { - return err - } - - account := &accounts.Account{ - Address: types.Address(common.HexToAddress(accountinfos[path].Address)), - KeyUID: generatedAccountInfo.KeyUID, - PublicKey: types.HexBytes(accountinfos[path].PublicKey), - Type: accounts.AccountTypeSeed, - Name: name, - Emoji: emoji, - Color: color, - Path: path, - DerivedFrom: generatedAccountInfo.Address, - } - return api.SaveAccounts(ctx, []*accounts.Account{account}) -} - -func (api *API) addAccountWithMnemonic( - ctx context.Context, - mnemonic string, - password string, - name string, - color string, - emoji string, - path string, -) error { - err := api.verifyPassword(password) - if err != nil { - return err - } - - return api.addAccountWithMnemonicPasswordVerified(ctx, mnemonic, password, name, color, emoji, path) -} - -// generateAccountPasswordVerified adds an accounts.Account generated from the given path -// assuming that client has already authenticated logged in use, this function doesn't verify a password. -func (api *API) generateAccountPasswordVerified( - ctx context.Context, - password string, - name string, - color string, - emoji string, - path string, - address string, -) error { - info, err := api.manager.AccountsGenerator().LoadAccount(address, password) - if err != nil { - return err - } - - infos, err := api.manager.AccountsGenerator().DeriveAddresses(info.ID, []string{path}) - if err != nil { - return err - } - - _, err = api.manager.AccountsGenerator().StoreDerivedAccounts(info.ID, password, []string{path}) - if err != nil { - return err - } - - allAccounts, err := api.GetAccounts(ctx) - if err != nil { - return err - } - - accountType := accounts.AccountTypeGenerated - for _, acc := range allAccounts { - if (acc.Type == accounts.AccountTypeSeed || acc.Type == accounts.AccountTypeKey) && acc.DerivedFrom == address { - accountType = acc.Type - break - } - } - - acc := &accounts.Account{ - Address: types.Address(common.HexToAddress(infos[path].Address)), - KeyUID: info.KeyUID, - PublicKey: types.HexBytes(infos[path].PublicKey), - Type: accountType, - Name: name, - Emoji: emoji, - Color: color, - Path: path, - DerivedFrom: address, - } - - return api.SaveAccounts(ctx, []*accounts.Account{acc}) -} - -func (api *API) generateAccount( - ctx context.Context, - password string, - name string, - color string, - emoji string, - path string, - address string, -) error { - err := api.verifyPassword(password) - if err != nil { - return err - } - - return api.generateAccountPasswordVerified(ctx, password, name, color, emoji, path, address) + return err } func (api *API) VerifyPassword(password string) bool { - err := api.verifyPassword(password) + address, err := api.db.GetChatAddress() + if err != nil { + return false + } + _, err = api.manager.VerifyAccountPassword(api.config.KeyStoreDir, address.Hex(), password) return err == nil } diff --git a/services/wallet/api.go b/services/wallet/api.go index c6a58ada6..5505da39b 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -2,7 +2,6 @@ package wallet import ( "context" - "fmt" "math/big" "strings" "time" @@ -13,7 +12,6 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" "github.com/status-im/status-go/rpc/chain" - "github.com/status-im/status-go/services/wallet/async" "github.com/status-im/status-go/services/wallet/bridge" "github.com/status-im/status-go/services/wallet/currency" "github.com/status-im/status-go/services/wallet/history" @@ -393,25 +391,18 @@ func (api *API) GetSuggestedRoutes( return api.router.suggestedRoutes(ctx, sendType, account, amountIn.ToInt(), tokenSymbol, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs, gasFeeMode, fromLockedAmount) } -func (api *API) GetDerivedAddressForPath(ctx context.Context, password string, derivedFrom string, path string) (*DerivedAddress, error) { +// Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) +func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) { info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password) if err != nil { return nil, err } - return api.getDerivedAddress(info.ID, path) + return api.getDerivedAddresses(info.ID, paths) } -func (api *API) GetDerivedAddressesForPath(ctx context.Context, password string, derivedFrom string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { - info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password) - if err != nil { - return nil, err - } - - return api.getDerivedAddresses(ctx, info.ID, path, pageSize, pageNumber) -} - -func (api *API) GetDerivedAddressesForMnemonicWithPath(ctx context.Context, mnemonic string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { +// Generates addresses for the provided paths derived from the provided mnemonic, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) +func (api *API) GetDerivedAddressesForMnemonic(ctx context.Context, mnemonic string, paths []string) ([]*DerivedAddress, error) { mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ") info, err := api.s.gethManager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "") @@ -419,27 +410,44 @@ func (api *API) GetDerivedAddressesForMnemonicWithPath(ctx context.Context, mnem return nil, err } - return api.getDerivedAddresses(ctx, info.ID, path, pageSize, pageNumber) + return api.getDerivedAddresses(info.ID, paths) } -func (api *API) GetDerivedAddressForPrivateKey(ctx context.Context, privateKey string) ([]*DerivedAddress, error) { - var derivedAddresses = make([]*DerivedAddress, 0) - info, err := api.s.gethManager.AccountsGenerator().ImportPrivateKey(privateKey) +// Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) +func (api *API) getDerivedAddresses(id string, paths []string) ([]*DerivedAddress, error) { + addedAccounts, err := api.s.accountsDB.GetAccounts() if err != nil { - return derivedAddresses, err + return nil, err } - derivedAddress, err := api.GetDerivedAddressDetails(ctx, info.Address) + info, err := api.s.gethManager.AccountsGenerator().DeriveAddresses(id, paths) if err != nil { - return derivedAddresses, err + return nil, err } - derivedAddresses = append(derivedAddresses, derivedAddress) + derivedAddresses := make([]*DerivedAddress, 0) + for accPath, acc := range info { + + derivedAddress := &DerivedAddress{ + Address: common.HexToAddress(acc.Address), + Path: accPath, + } + + for _, account := range addedAccounts { + if types.Address(derivedAddress.Address) == account.Address { + derivedAddress.AlreadyCreated = true + break + } + } + + derivedAddresses = append(derivedAddresses, derivedAddress) + } return derivedAddresses, nil } -func (api *API) GetDerivedAddressDetails(ctx context.Context, address string) (*DerivedAddress, error) { +// Returns details for the passed address (response doesn't include derivation path) +func (api *API) GetAddressDetails(ctx context.Context, address string) (*DerivedAddress, error) { var derivedAddress *DerivedAddress commonAddr := common.HexToAddress(address) @@ -447,9 +455,6 @@ func (api *API) GetDerivedAddressDetails(ctx context.Context, address string) (* if err != nil { return derivedAddress, err } - if addressExists { - return derivedAddress, fmt.Errorf("account already exists") - } transactions, err := api.s.transferController.GetTransfersByAddress(ctx, api.s.rpcClient.UpstreamChainID, commonAddr, nil, 1, false) @@ -469,93 +474,6 @@ func (api *API) GetDerivedAddressDetails(ctx context.Context, address string) (* return derivedAddress, nil } -func (api *API) getDerivedAddresses(ctx context.Context, id string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { - var ( - group = async.NewAtomicGroup(ctx) - derivedAddresses = make([]*DerivedAddress, 0) - unorderedDerivedAddresses = map[int]*DerivedAddress{} - err error - ) - - splitPathValues := strings.Split(path, "/") - if len(splitPathValues) == 6 { - derivedAddress, err := api.getDerivedAddress(id, path) - if err != nil { - return nil, err - } - derivedAddresses = append(derivedAddresses, derivedAddress) - } else { - - if pageNumber <= 0 || pageSize <= 0 { - return nil, fmt.Errorf("pageSize and pageNumber should be greater than 0") - } - - var startIndex = ((pageNumber - 1) * pageSize) - var endIndex = (pageNumber * pageSize) - - for i := startIndex; i < endIndex; i++ { - derivedPath := fmt.Sprint(path, "/", i) - index := i - group.Add(func(parent context.Context) error { - derivedAddress, err := api.getDerivedAddress(id, derivedPath) - if err != nil { - return err - } - unorderedDerivedAddresses[index] = derivedAddress - return nil - }) - } - select { - case <-group.WaitAsync(): - case <-ctx.Done(): - return nil, ctx.Err() - } - for i := startIndex; i < endIndex; i++ { - derivedAddresses = append(derivedAddresses, unorderedDerivedAddresses[i]) - } - err = group.Error() - } - return derivedAddresses, err -} - -func (api *API) getDerivedAddress(id string, derivedPath string) (*DerivedAddress, error) { - addedAccounts, err := api.s.accountsDB.GetAccounts() - if err != nil { - return nil, err - } - - info, err := api.s.gethManager.AccountsGenerator().DeriveAddresses(id, []string{derivedPath}) - if err != nil { - return nil, err - } - - alreadyExists := false - for _, account := range addedAccounts { - if types.Address(common.HexToAddress(info[derivedPath].Address)) == account.Address { - alreadyExists = true - break - } - } - - var ctx context.Context - transactions, err := api.s.transferController.GetTransfersByAddress(ctx, api.s.rpcClient.UpstreamChainID, common.HexToAddress(info[derivedPath].Address), nil, 1, false) - - if err != nil { - return nil, err - } - - hasActivity := int64(len(transactions)) > 0 - - address := &DerivedAddress{ - Address: common.HexToAddress(info[derivedPath].Address), - Path: derivedPath, - HasActivity: hasActivity, - AlreadyCreated: alreadyExists, - } - - return address, nil -} - func (api *API) CreateMultiTransaction(ctx context.Context, multiTransaction *transfer.MultiTransaction, data []*bridge.TransactionBridge, password string) (*transfer.MultiTransactionResult, error) { log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction") return api.s.transactionManager.CreateMultiTransaction(ctx, multiTransaction, data, api.router.bridges, password)