neat: accounts and wallet api sorted out

Unused endpoints removed, new ones with more meaningful naming are added and
their purposes were revised.
This commit is contained in:
Sale Djenic 2023-03-20 08:39:33 +01:00 committed by saledjenic
parent a812365525
commit d30c88b80e
2 changed files with 122 additions and 436 deletions

View File

@ -3,7 +3,6 @@ package accounts
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"strings" "strings"
"time" "time"
@ -15,14 +14,10 @@ import (
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/keypairs" "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/params"
"github.com/status-im/status-go/protocol" "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 { 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} 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) return (*api.messenger).DeleteAccount(address)
} }
func (api *API) AddAccountWatch(ctx context.Context, address string, name string, color string, emoji string) error { func (api *API) AddAccount(ctx context.Context, password string, account *accounts.Account) error {
account := &accounts.Account{ if len(account.Address) == 0 {
Address: types.Address(common.HexToAddress(address)), return errors.New("`Address` field must be set")
Type: accounts.AccountTypeWatch,
Name: name,
Emoji: emoji,
Color: color,
} }
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}) return api.SaveAccounts(ctx, []*accounts.Account{account})
} }
func (api *API) AddAccountWithMnemonic( // Imports a new private key and creates local keystore file.
ctx context.Context, func (api *API) ImportPrivateKey(ctx context.Context, privateKey string, password string) error {
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 {
info, err := api.manager.AccountsGenerator().ImportPrivateKey(privateKey) info, err := api.manager.AccountsGenerator().ImportPrivateKey(privateKey)
if err != nil { if err != nil {
return err return err
} }
addressExists, err := api.db.AddressExists(types.Address(common.HexToAddress(info.Address))) accs, err := api.db.GetAccountsByKeyUID(info.KeyUID)
if err != nil { if err != nil {
return err 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) _, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password)
if err != nil {
return err return err
} }
account := &accounts.Account{ // Imports a new mnemonic and creates local keystore file.
Address: types.Address(common.HexToAddress(info.Address)), func (api *API) ImportMnemonic(ctx context.Context, mnemonic string, password string) error {
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 {
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ") mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "") generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
@ -338,120 +201,25 @@ func (api *API) addAccountWithMnemonicPasswordVerified(
return err 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) _, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
if err != nil {
return err 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)
}
func (api *API) VerifyPassword(password string) bool { 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 return err == nil
} }

View File

@ -2,7 +2,6 @@ package wallet
import ( import (
"context" "context"
"fmt"
"math/big" "math/big"
"strings" "strings"
"time" "time"
@ -13,7 +12,6 @@ import (
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc/chain" "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/bridge"
"github.com/status-im/status-go/services/wallet/currency" "github.com/status-im/status-go/services/wallet/currency"
"github.com/status-im/status-go/services/wallet/history" "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) 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) info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password)
if err != nil { if err != nil {
return nil, err 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) { // Generates addresses for the provided paths derived from the provided mnemonic, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function)
info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password) func (api *API) GetDerivedAddressesForMnemonic(ctx context.Context, mnemonic string, paths []string) ([]*DerivedAddress, error) {
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) {
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ") mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
info, err := api.s.gethManager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "") info, err := api.s.gethManager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
@ -419,27 +410,44 @@ func (api *API) GetDerivedAddressesForMnemonicWithPath(ctx context.Context, mnem
return nil, err 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) { // Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function)
var derivedAddresses = make([]*DerivedAddress, 0) func (api *API) getDerivedAddresses(id string, paths []string) ([]*DerivedAddress, error) {
info, err := api.s.gethManager.AccountsGenerator().ImportPrivateKey(privateKey) addedAccounts, err := api.s.accountsDB.GetAccounts()
if err != nil { 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 { if err != nil {
return derivedAddresses, err return nil, err
}
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) derivedAddresses = append(derivedAddresses, derivedAddress)
}
return derivedAddresses, nil 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 var derivedAddress *DerivedAddress
commonAddr := common.HexToAddress(address) commonAddr := common.HexToAddress(address)
@ -447,9 +455,6 @@ func (api *API) GetDerivedAddressDetails(ctx context.Context, address string) (*
if err != nil { if err != nil {
return derivedAddress, err 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) 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 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) { 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") log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction")
return api.s.transactionManager.CreateMultiTransaction(ctx, multiTransaction, data, api.router.bridges, password) return api.s.transactionManager.CreateMultiTransaction(ctx, multiTransaction, data, api.router.bridges, password)