2019-08-20 15:38:40 +00:00
|
|
|
package accounts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-04-11 15:18:28 +00:00
|
|
|
"errors"
|
2022-01-24 09:29:18 +00:00
|
|
|
"strings"
|
2019-08-20 15:38:40 +00:00
|
|
|
|
2022-01-24 09:29:18 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2019-08-28 07:49:03 +00:00
|
|
|
"github.com/ethereum/go-ethereum/event"
|
2020-01-02 09:10:19 +00:00
|
|
|
|
status-im/status-react#9203 Faster tx fetching with less request
*** How it worked before this PR on multiaccount creation:
- On multiacc creation we scanned chain for eth and erc20 transfers. For
each address of a new empty multiaccount this scan required
1. two `eth_getBalance` requests to find out that there is no any
balance change between zero and the last block, for eth transfers
2. and `chain-size/100000` (currently ~100) `eth_getLogs` requests,
for erc20 transfers
- For some reason we scanned an address of the chat account as well, and
also accounts were not deduplicated. So even for an empty multiacc we
scanned chain twice for each chat and main wallet addresses, in result
app had to execute about 400 requests.
- As mentioned above, `eth_getBalance` requests were used to check if
there were any eth transfers, and that caused empty history in case
if user already used all available eth (so that both zero and latest
blocks show 0 eth for an address). There might have been transactions
but we wouldn't fetch/show them.
- There was no upper limit for the number of rpc requests during the
scan, so it could require indefinite number of requests; the scanning
algorithm was written so that we persisted the whole history of
transactions or tried to scan form the beginning again in case of
failure, giving up only after 10 minutes of failures. In result
addresses with sufficient number of transactions would never be fully
scanned and during these 10 minutes app could use gigabytes of
internet data.
- Failures were caused by `eth_getBlockByNumber`/`eth_getBlockByHash`
requests. These requests return significantly bigger responses than
`eth_getBalance`/`eth_transactionsCount` and it is likely that
execution of thousands of them in parallel caused failures for
accounts with hundreds of transactions. Even for an account with 12k
we could successfully determine blocks with transaction in a few
minutes using `eth_getBalance` requests, but `eth_getBlock...`
couldn't be processed for this acc.
- There was no caching for for `eth_getBalance` requests, and this
caused in average 3-4 times more such requests than is needed.
*** How it works now on multiaccount creation:
- On multiacc creation we scan chain for last ~30 eth transactions and
then check erc20 in the range where these eth transactions were found.
For an empty address in multiacc this means:
1. two `eth_getBalance` transactions to determine that there was no
balance change between zero and the last block; two
`eth_transactionsCount` requests to determine there are no outgoing
transactions for this address; total 4 requests for eth transfers
2. 20 `eth_getLogs` for erc20 transfers. This number can be lowered,
but that's not a big deal
- Deduplication of addresses is added and also we don't scan chat
account, so a new multiacc requires ~25 (we also request latest block
number and probably execute a few other calls) request to determine
that multiacc is empty (comparing to ~400 before)
- In case if address contains transactions we:
1. determine the range which contains 20-25 outgoing eth/erc20
transactions. This usually requires up to 10 `eth_transactionCount`
requests
2. then we scan chain for eth transfers using `eth_getBalance` and
`eth_transactionCount` (for double checking zero balances)
3. we make sure that we do not scan db for more than 30 blocks with
transfers. That's important for accounts with mostly incoming
transactions, because the range found on the first step might
contain any number of incoming transfers, but only 20-25 outgoing
transactions
4. when we found ~30 blocks in a given range, we update initial
range `from` block using the oldest found block
5. and now we scan db for erc20transfers using `eth_getLogs`
`oldest-found-eth-block`-`latest-block`, we make not more than 20 calls
6. when all blocks which contain incoming/outgoing transfers for a
given address are found, we save these blocks to db and mark that
transfers from these blocks are still to be fetched
7. Then we select latest ~30 (the number can be adjusted) blocks from
these which were found and fetch transfers, this requires 3-4
requests per transfer.
8. we persist scanned range so that we know were to start next time
9. we dispatch an event which tells client that transactions are found
10. client fetches latest 20 transfers
- when user presses "fetch more" button we check if app's db contains next
20 transfers, if not we scan chain again and return transfers after
small fixes
2019-12-18 11:01:46 +00:00
|
|
|
"github.com/ethereum/go-ethereum/log"
|
2022-01-24 09:29:18 +00:00
|
|
|
"github.com/status-im/status-go/account"
|
2019-12-11 13:59:37 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2019-08-20 15:38:40 +00:00
|
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
2023-12-04 10:18:05 +00:00
|
|
|
walletsettings "github.com/status-im/status-go/multiaccounts/settings_wallet"
|
2022-01-24 09:29:18 +00:00
|
|
|
"github.com/status-im/status-go/params"
|
2022-07-06 16:12:49 +00:00
|
|
|
"github.com/status-im/status-go/protocol"
|
2023-05-31 19:45:11 +00:00
|
|
|
"github.com/status-im/status-go/services/accounts/accountsevent"
|
2019-08-20 15:38:40 +00:00
|
|
|
)
|
|
|
|
|
2022-07-06 16:12:49 +00:00
|
|
|
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}
|
2019-08-20 15:38:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// API is class with methods available over RPC.
|
|
|
|
type API struct {
|
2022-07-06 16:12:49 +00:00
|
|
|
manager *account.GethManager
|
|
|
|
config *params.NodeConfig
|
|
|
|
db *accounts.Database
|
|
|
|
feed *event.Feed
|
|
|
|
messenger **protocol.Messenger
|
2019-08-20 15:38:40 +00:00
|
|
|
}
|
|
|
|
|
2022-05-18 10:42:51 +00:00
|
|
|
type DerivedAddress struct {
|
|
|
|
Address common.Address `json:"address"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
HasActivity bool `json:"hasActivity"`
|
|
|
|
AlreadyCreated bool `json:"alreadyCreated"`
|
|
|
|
}
|
|
|
|
|
2023-04-21 14:15:31 +00:00
|
|
|
func (api *API) SaveAccount(ctx context.Context, account *accounts.Account) error {
|
|
|
|
log.Info("[AccountsAPI::SaveAccount]")
|
2023-05-16 10:48:00 +00:00
|
|
|
err := (*api.messenger).SaveOrUpdateAccount(account)
|
2019-08-28 07:49:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-31 19:45:11 +00:00
|
|
|
|
|
|
|
api.feed.Send(accountsevent.Event{
|
|
|
|
Type: accountsevent.EventTypeAdded,
|
|
|
|
Accounts: []common.Address{common.Address(account.Address)},
|
|
|
|
})
|
2019-08-28 07:49:03 +00:00
|
|
|
return nil
|
2019-08-20 15:38:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
// Setting `Keypair` without `Accounts` will update keypair only, `Keycards` won't be saved/updated this way.
|
2023-05-16 10:48:00 +00:00
|
|
|
func (api *API) SaveKeypair(ctx context.Context, keypair *accounts.Keypair) error {
|
|
|
|
log.Info("[AccountsAPI::SaveKeypair]")
|
|
|
|
err := (*api.messenger).SaveOrUpdateKeypair(keypair)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-31 19:45:11 +00:00
|
|
|
|
|
|
|
commonAddresses := []common.Address{}
|
2023-05-16 10:48:00 +00:00
|
|
|
for _, acc := range keypair.Accounts {
|
2023-05-31 19:45:11 +00:00
|
|
|
commonAddresses = append(commonAddresses, common.Address(acc.Address))
|
2022-05-09 08:00:48 +00:00
|
|
|
}
|
2023-05-31 19:45:11 +00:00
|
|
|
|
|
|
|
api.feed.Send(accountsevent.Event{
|
|
|
|
Type: accountsevent.EventTypeAdded,
|
|
|
|
Accounts: commonAddresses,
|
|
|
|
})
|
2023-05-16 10:48:00 +00:00
|
|
|
return nil
|
2019-08-20 15:38:40 +00:00
|
|
|
}
|
2019-12-16 15:23:36 +00:00
|
|
|
|
2023-08-07 15:03:08 +00:00
|
|
|
func (api *API) HasPairedDevices(ctx context.Context) bool {
|
|
|
|
return (*api.messenger).HasPairedDevices()
|
|
|
|
}
|
|
|
|
|
2023-05-24 14:42:31 +00:00
|
|
|
// Setting `Keypair` without `Accounts` will update keypair only.
|
|
|
|
func (api *API) UpdateKeypairName(ctx context.Context, keyUID string, name string) error {
|
|
|
|
return (*api.messenger).UpdateKeypairName(keyUID, name)
|
|
|
|
}
|
|
|
|
|
2023-07-16 11:11:48 +00:00
|
|
|
func (api *API) MoveWalletAccount(ctx context.Context, fromPosition int64, toPosition int64) error {
|
|
|
|
return (*api.messenger).MoveWalletAccount(fromPosition, toPosition)
|
2023-06-20 11:35:22 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 10:18:05 +00:00
|
|
|
func (api *API) UpdateTokenPreferences(ctx context.Context, preferences []walletsettings.TokenPreferences) error {
|
|
|
|
return (*api.messenger).UpdateTokenPreferences(preferences)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) GetTokenPreferences(ctx context.Context) ([]walletsettings.TokenPreferences, error) {
|
|
|
|
return (*api.messenger).GetTokenPreferences()
|
|
|
|
}
|
|
|
|
|
2024-01-17 13:12:49 +00:00
|
|
|
func (api *API) UpdateCollectiblePreferences(ctx context.Context, preferences []walletsettings.CollectiblePreferences) error {
|
|
|
|
return (*api.messenger).UpdateCollectiblePreferences(preferences)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) GetCollectiblePreferences(ctx context.Context) ([]walletsettings.CollectiblePreferences, error) {
|
|
|
|
return (*api.messenger).GetCollectiblePreferences()
|
|
|
|
}
|
|
|
|
|
2023-04-25 10:29:09 +00:00
|
|
|
func (api *API) GetAccounts(ctx context.Context) ([]*accounts.Account, error) {
|
2023-07-25 15:17:17 +00:00
|
|
|
return api.db.GetActiveAccounts()
|
2023-05-16 10:48:00 +00:00
|
|
|
}
|
2023-04-25 10:29:09 +00:00
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
func (api *API) GetWatchOnlyAccounts(ctx context.Context) ([]*accounts.Account, error) {
|
2023-07-26 05:52:45 +00:00
|
|
|
return api.db.GetActiveWatchOnlyAccounts()
|
2023-04-25 10:29:09 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
func (api *API) GetKeypairs(ctx context.Context) ([]*accounts.Keypair, error) {
|
2023-07-25 15:17:17 +00:00
|
|
|
return api.db.GetActiveKeypairs()
|
2023-05-16 10:48:00 +00:00
|
|
|
}
|
2023-04-25 10:29:09 +00:00
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
func (api *API) GetAccountByAddress(ctx context.Context, address types.Address) (*accounts.Account, error) {
|
|
|
|
return api.db.GetAccountByAddress(address)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) GetKeypairByKeyUID(ctx context.Context, keyUID string) (*accounts.Keypair, error) {
|
|
|
|
return api.db.GetKeypairByKeyUID(keyUID)
|
2023-04-25 10:29:09 +00:00
|
|
|
}
|
|
|
|
|
2023-05-05 07:43:05 +00:00
|
|
|
func (api *API) DeleteAccount(ctx context.Context, address types.Address) error {
|
2023-06-28 19:45:36 +00:00
|
|
|
err := (*api.messenger).DeleteAccount(address)
|
2023-04-10 14:36:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-31 19:45:11 +00:00
|
|
|
api.feed.Send(accountsevent.Event{
|
|
|
|
Type: accountsevent.EventTypeRemoved,
|
|
|
|
Accounts: []common.Address{common.Address(address)},
|
|
|
|
})
|
|
|
|
|
2023-06-28 19:45:36 +00:00
|
|
|
return nil
|
2023-01-10 09:38:34 +00:00
|
|
|
}
|
|
|
|
|
2023-07-20 15:00:39 +00:00
|
|
|
func (api *API) DeleteKeypair(ctx context.Context, keyUID string) error {
|
|
|
|
keypair, err := api.db.GetKeypairByKeyUID(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = (*api.messenger).DeleteKeypair(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var addresses []common.Address
|
|
|
|
for _, acc := range keypair.Accounts {
|
|
|
|
if acc.Chat {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
addresses = append(addresses, common.Address(acc.Address))
|
|
|
|
}
|
|
|
|
|
|
|
|
api.feed.Send(accountsevent.Event{
|
|
|
|
Type: accountsevent.EventTypeRemoved,
|
|
|
|
Accounts: addresses,
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
func (api *API) AddKeypair(ctx context.Context, password string, keypair *accounts.Keypair) error {
|
|
|
|
if len(keypair.KeyUID) == 0 {
|
|
|
|
return errors.New("`KeyUID` field of a keypair must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(keypair.Name) == 0 {
|
|
|
|
return errors.New("`Name` field of a keypair must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(keypair.Type) == 0 {
|
|
|
|
return errors.New("`Type` field of a keypair must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
if keypair.Type != accounts.KeypairTypeKey {
|
|
|
|
if len(keypair.DerivedFrom) == 0 {
|
|
|
|
return errors.New("`DerivedFrom` field of a keypair must be set")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, acc := range keypair.Accounts {
|
|
|
|
if acc.KeyUID != keypair.KeyUID {
|
|
|
|
return errors.New("all accounts of a keypair must have the same `KeyUID` as keypair key uid")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := api.checkAccountValidity(acc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := api.SaveKeypair(ctx, keypair)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(password) > 0 {
|
|
|
|
for _, acc := range keypair.Accounts {
|
|
|
|
if acc.Type == accounts.AccountTypeGenerated || acc.Type == accounts.AccountTypeSeed {
|
|
|
|
err = api.createKeystoreFileForAccount(keypair.DerivedFrom, password, acc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) checkAccountValidity(account *accounts.Account) error {
|
2023-03-20 07:39:33 +00:00
|
|
|
if len(account.Address) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("`Address` field of an account must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(account.Type) == 0 {
|
|
|
|
return errors.New("`Type` field of an account must be set")
|
2022-01-24 09:29:18 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if account.Wallet || account.Chat {
|
|
|
|
return errors.New("default wallet and chat account cannot be added this way")
|
2022-04-13 09:15:26 +00:00
|
|
|
}
|
2022-01-24 09:29:18 +00:00
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if len(account.Name) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("`Name` field of an account must be set")
|
2022-04-11 15:18:28 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if len(account.Emoji) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("`Emoji` field of an account must be set")
|
2022-01-24 09:29:18 +00:00
|
|
|
}
|
2022-04-13 09:15:26 +00:00
|
|
|
|
2023-06-02 15:06:51 +00:00
|
|
|
if len(account.ColorID) == 0 {
|
|
|
|
return errors.New("`ColorID` field of an account must be set")
|
2022-10-26 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if account.Type != accounts.AccountTypeWatch {
|
2022-10-26 20:05:37 +00:00
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if len(account.KeyUID) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("`KeyUID` field of an account must be set")
|
2023-03-20 07:39:33 +00:00
|
|
|
}
|
2022-01-24 09:29:18 +00:00
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if len(account.PublicKey) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("`PublicKey` field of an account must be set")
|
2023-03-20 07:39:33 +00:00
|
|
|
}
|
2022-04-13 09:15:26 +00:00
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if account.Type != accounts.AccountTypeKey {
|
|
|
|
if len(account.Path) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("`Path` field of an account must be set")
|
2023-03-20 07:39:33 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-11 15:18:28 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
addressExists, err := api.db.AddressExists(account.Address)
|
2022-10-26 20:05:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
if addressExists {
|
|
|
|
return errors.New("account already exists")
|
2022-10-26 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) createKeystoreFileForAccount(masterAddress string, password string, account *accounts.Account) error {
|
|
|
|
if account.Type != accounts.AccountTypeGenerated && account.Type != accounts.AccountTypeSeed {
|
|
|
|
return errors.New("cannot create keystore file if account is not of `generated` or `seed` type")
|
|
|
|
}
|
|
|
|
if masterAddress == "" {
|
|
|
|
return errors.New("cannot create keystore file if master address is empty")
|
|
|
|
}
|
|
|
|
if password == "" {
|
|
|
|
return errors.New("cannot create keystore file if password is empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := api.manager.AccountsGenerator().LoadAccount(masterAddress, password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = api.manager.AccountsGenerator().StoreDerivedAccounts(info.ID, password, []string{account.Path})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) AddAccount(ctx context.Context, password string, account *accounts.Account) error {
|
|
|
|
err := api.checkAccountValidity(account)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if account.Type != accounts.AccountTypeWatch {
|
|
|
|
kp, err := api.db.GetKeypairByKeyUID(account.KeyUID)
|
2023-03-20 07:39:33 +00:00
|
|
|
if err != nil {
|
2023-05-16 10:48:00 +00:00
|
|
|
if err == accounts.ErrDbKeypairNotFound {
|
|
|
|
return errors.New("cannot add an account for an unknown keypair")
|
|
|
|
}
|
2023-03-20 07:39:33 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-10-26 20:05:37 +00:00
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
// 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 {
|
|
|
|
err = api.createKeystoreFileForAccount(kp.DerivedFrom, password, account)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-03-20 07:39:33 +00:00
|
|
|
}
|
2022-10-26 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
2023-12-01 11:30:42 +00:00
|
|
|
if account.Type == accounts.AccountTypeGenerated {
|
|
|
|
account.AddressWasNotShown = true
|
|
|
|
}
|
|
|
|
|
2023-04-21 14:15:31 +00:00
|
|
|
return api.SaveAccount(ctx, account)
|
2022-04-13 09:15:26 +00:00
|
|
|
}
|
2022-01-24 09:29:18 +00:00
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
// 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)
|
2022-01-24 09:29:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
kp, err := api.db.GetKeypairByKeyUID(info.KeyUID)
|
|
|
|
if err != nil && err != accounts.ErrDbKeypairNotFound {
|
2022-01-24 09:29:18 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-04-13 09:15:26 +00:00
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
if kp != nil {
|
2023-03-20 07:39:33 +00:00
|
|
|
return errors.New("provided private key was already imported")
|
2022-01-24 09:29:18 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
_, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password)
|
|
|
|
return err
|
2022-10-26 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
2023-08-07 15:03:08 +00:00
|
|
|
// Creates all keystore files for a keypair and mark it in db as fully operable.
|
|
|
|
func (api *API) MakePrivateKeyKeypairFullyOperable(ctx context.Context, privateKey string, password string) error {
|
|
|
|
info, err := api.manager.AccountsGenerator().ImportPrivateKey(privateKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
kp, err := api.db.GetKeypairByKeyUID(info.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if kp == nil {
|
|
|
|
return errors.New("keypair for the provided private key is not known")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-09 11:14:17 +00:00
|
|
|
return (*api.messenger).MarkKeypairFullyOperable(info.KeyUID)
|
2023-08-07 15:03:08 +00:00
|
|
|
}
|
|
|
|
|
2023-08-21 15:41:03 +00:00
|
|
|
func (api *API) MakePartiallyOperableAccoutsFullyOperable(ctx context.Context, password string) (addresses []types.Address, err error) {
|
|
|
|
profileKeypair, err := api.db.GetProfileKeypair()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-08-25 13:20:53 +00:00
|
|
|
if !profileKeypair.MigratedToKeycard() && !api.VerifyPassword(password) {
|
2023-08-21 15:41:03 +00:00
|
|
|
err = errors.New("wrong password provided")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
keypairs, err := api.db.GetActiveKeypairs()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, kp := range keypairs {
|
|
|
|
for _, acc := range kp.Accounts {
|
|
|
|
if acc.Operable != accounts.AccountPartiallyOperable {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = api.createKeystoreFileForAccount(kp.DerivedFrom, password, acc)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = api.db.MarkAccountFullyOperable(acc.Address)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
addresses = append(addresses, acc.Address)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
// 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), " ")
|
2022-01-24 09:29:18 +00:00
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
|
2022-01-24 09:29:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-04-13 09:15:26 +00:00
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
|
|
|
|
if err != nil && err != accounts.ErrDbKeypairNotFound {
|
2023-03-01 15:15:42 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
if kp != nil {
|
2023-03-20 07:39:33 +00:00
|
|
|
return errors.New("provided mnemonic was already imported, to add new account use `AddAccount` endpoint")
|
2022-04-13 09:15:26 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
|
|
|
|
return err
|
2022-01-24 09:29:18 +00:00
|
|
|
}
|
2022-08-24 16:01:23 +00:00
|
|
|
|
2023-08-07 15:03:08 +00:00
|
|
|
// Creates all keystore files for a keypair and mark it in db as fully operable.
|
|
|
|
func (api *API) MakeSeedPhraseKeypairFullyOperable(ctx context.Context, mnemonic string, password string) error {
|
|
|
|
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
|
|
|
|
|
|
|
|
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if kp == nil {
|
|
|
|
return errors.New("keypair for the provided seed phrase is not known")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var paths []string
|
|
|
|
for _, acc := range kp.Accounts {
|
|
|
|
paths = append(paths, acc.Path)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = api.manager.AccountsGenerator().StoreDerivedAccounts(generatedAccountInfo.ID, password, paths)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-09 11:14:17 +00:00
|
|
|
return (*api.messenger).MarkKeypairFullyOperable(generatedAccountInfo.KeyUID)
|
2023-08-07 15:03:08 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 12:14:51 +00:00
|
|
|
// Creates a random new mnemonic.
|
|
|
|
func (api *API) GetRandomMnemonic(ctx context.Context) (string, error) {
|
2024-02-19 12:53:01 +00:00
|
|
|
return account.GetRandomMnemonic()
|
2023-03-21 12:14:51 +00:00
|
|
|
}
|
|
|
|
|
2023-04-20 13:07:22 +00:00
|
|
|
func (api *API) VerifyKeystoreFileForAccount(address types.Address, password string) bool {
|
|
|
|
_, err := api.manager.VerifyAccountPassword(api.config.KeyStoreDir, address.Hex(), password)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2023-03-20 07:39:33 +00:00
|
|
|
func (api *API) VerifyPassword(password string) bool {
|
|
|
|
address, err := api.db.GetChatAddress()
|
2022-10-26 20:05:37 +00:00
|
|
|
if err != nil {
|
2023-03-20 07:39:33 +00:00
|
|
|
return false
|
2022-10-26 20:05:37 +00:00
|
|
|
}
|
2023-04-20 13:07:22 +00:00
|
|
|
return api.VerifyKeystoreFileForAccount(address, password)
|
2022-08-24 16:01:23 +00:00
|
|
|
}
|
2022-09-06 07:10:40 +00:00
|
|
|
|
2023-08-29 14:14:12 +00:00
|
|
|
func (api *API) MigrateNonProfileKeycardKeypairToApp(ctx context.Context, mnemonic string, password string) error {
|
|
|
|
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
|
|
|
|
|
|
|
|
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if kp.Type == accounts.KeypairTypeProfile {
|
|
|
|
return errors.New("cannot migrate profile keypair")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !kp.MigratedToKeycard() {
|
|
|
|
return errors.New("keypair being migrated is not a keycard keypair")
|
|
|
|
}
|
|
|
|
|
|
|
|
profileKeypair, err := api.db.GetProfileKeypair()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !profileKeypair.MigratedToKeycard() && !api.VerifyPassword(password) {
|
|
|
|
return errors.New("wrong password provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, acc := range kp.Accounts {
|
|
|
|
err = api.createKeystoreFileForAccount(kp.DerivedFrom, password, acc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this will emit SyncKeypair message
|
|
|
|
return (*api.messenger).DeleteAllKeycardsWithKeyUID(ctx, generatedAccountInfo.KeyUID)
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
// If keypair is migrated from keycard to app, then `accountsComingFromKeycard` should be set to true, otherwise false.
|
2023-07-05 12:41:58 +00:00
|
|
|
// If keycard is new `Position` will be determined and set by the backend and `KeycardLocked` will be set to false.
|
|
|
|
// If keycard is already added, `Position` and `KeycardLocked` will be unchanged.
|
|
|
|
func (api *API) SaveOrUpdateKeycard(ctx context.Context, keycard *accounts.Keycard, accountsComingFromKeycard bool) error {
|
|
|
|
if len(keycard.AccountsAddresses) == 0 {
|
2023-05-16 10:48:00 +00:00
|
|
|
return errors.New("cannot migrate a keypair without accounts")
|
2023-04-25 07:52:43 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
kpDb, err := api.db.GetKeypairByKeyUID(keycard.KeyUID)
|
2023-04-25 07:52:43 +00:00
|
|
|
if err != nil {
|
2023-05-16 10:48:00 +00:00
|
|
|
if err == accounts.ErrDbKeypairNotFound {
|
|
|
|
return errors.New("cannot migrate an unknown keypair")
|
|
|
|
}
|
2023-04-25 07:52:43 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
err = (*api.messenger).SaveOrUpdateKeycard(ctx, keycard)
|
2022-11-08 13:30:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
if !accountsComingFromKeycard {
|
|
|
|
// Once we migrate a keypair, corresponding keystore files need to be deleted
|
|
|
|
// if the keypair being migrated is not already migrated (in case user is creating a copy of an existing Keycard)
|
2023-08-25 13:20:53 +00:00
|
|
|
// and if keypair operability is different from non operable (otherwise there are not keystore files to be deleted).
|
|
|
|
if !kpDb.MigratedToKeycard() && kpDb.Operability() != accounts.AccountNonOperable {
|
|
|
|
for _, acc := range kpDb.Accounts {
|
|
|
|
if acc.Operable != accounts.AccountFullyOperable {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = api.manager.DeleteAccount(acc.Address)
|
2023-05-16 10:48:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = api.manager.DeleteAccount(types.Address(common.HexToAddress(kpDb.DerivedFrom)))
|
2023-02-01 08:46:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2023-08-25 13:20:53 +00:00
|
|
|
|
|
|
|
err = (*api.messenger).MarkKeypairFullyOperable(keycard.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-08 13:30:33 +00:00
|
|
|
}
|
2023-04-25 07:52:43 +00:00
|
|
|
|
2022-11-08 13:30:33 +00:00
|
|
|
return nil
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
func (api *API) GetAllKnownKeycards(ctx context.Context) ([]*accounts.Keycard, error) {
|
2022-12-07 17:14:19 +00:00
|
|
|
return api.db.GetAllKnownKeycards()
|
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) GetKeycardsWithSameKeyUID(ctx context.Context, keyUID string) ([]*accounts.Keycard, error) {
|
|
|
|
return api.db.GetKeycardsWithSameKeyUID(keyUID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) GetKeycardByKeycardUID(ctx context.Context, keycardUID string) (*accounts.Keycard, error) {
|
|
|
|
return api.db.GetKeycardByKeycardUID(keycardUID)
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) SetKeycardName(ctx context.Context, keycardUID string, kpName string) error {
|
|
|
|
return (*api.messenger).SetKeycardName(ctx, keycardUID, kpName)
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) KeycardLocked(ctx context.Context, keycardUID string) error {
|
|
|
|
return (*api.messenger).KeycardLocked(ctx, keycardUID)
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) KeycardUnlocked(ctx context.Context, keycardUID string) error {
|
|
|
|
return (*api.messenger).KeycardUnlocked(ctx, keycardUID)
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) DeleteKeycardAccounts(ctx context.Context, keycardUID string, accountAddresses []types.Address) error {
|
|
|
|
return (*api.messenger).DeleteKeycardAccounts(ctx, keycardUID, accountAddresses)
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) DeleteKeycard(ctx context.Context, keycardUID string) error {
|
|
|
|
return (*api.messenger).DeleteKeycard(ctx, keycardUID)
|
2022-09-06 07:10:40 +00:00
|
|
|
}
|
2022-09-20 10:45:50 +00:00
|
|
|
|
2023-05-09 18:48:56 +00:00
|
|
|
func (api *API) DeleteAllKeycardsWithKeyUID(ctx context.Context, keyUID string) error {
|
2023-07-05 12:41:58 +00:00
|
|
|
return (*api.messenger).DeleteAllKeycardsWithKeyUID(ctx, keyUID)
|
2023-01-25 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
func (api *API) UpdateKeycardUID(ctx context.Context, oldKeycardUID string, newKeycardUID string) error {
|
|
|
|
return (*api.messenger).UpdateKeycardUID(ctx, oldKeycardUID, newKeycardUID)
|
2022-09-20 10:45:50 +00:00
|
|
|
}
|
2023-12-01 11:30:42 +00:00
|
|
|
|
|
|
|
func (api *API) AddressWasShown(address types.Address) error {
|
|
|
|
return api.db.AddressWasShown(address)
|
|
|
|
}
|
2024-03-27 13:58:51 +00:00
|
|
|
|
|
|
|
func (api *API) GetNumOfAddressesToGenerateForKeypair(keyUID string) (uint64, error) {
|
|
|
|
return api.db.GetNumOfAddressesToGenerateForKeypair(keyUID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) ResolveSuggestedPathForKeypair(keyUID string) (string, error) {
|
|
|
|
return api.db.ResolveSuggestedPathForKeypair(keyUID)
|
|
|
|
}
|