status-go/services/accounts/accounts.go

638 lines
19 KiB
Go
Raw Normal View History

package accounts
import (
"context"
"errors"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
2020-01-02 09:10:19 +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"
test_: Code Migration from status-cli-tests author shashankshampi <shashank.sanket1995@gmail.com> 1729780155 +0530 committer shashankshampi <shashank.sanket1995@gmail.com> 1730274350 +0530 test: Code Migration from status-cli-tests fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments fix(ci)_: remove workspace and tmp dir This ensures we do not encounter weird errors like: ``` + ln -s /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907 /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go ln: failed to create symbolic link '/home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go': File exists script returned exit code 1 ``` Signed-off-by: Jakub Sokołowski <jakub@status.im> chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 cleanup added logger and cleanup review comments changes fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 test_: remove port bind chore(wallet)_: move route execution code to separate module chore_: replace geth logger with zap logger (#5962) closes: #6002 feat(telemetry)_: add metrics for message reliability (#5899) * feat(telemetry)_: track message reliability Add metrics for dial errors, missed messages, missed relevant messages, and confirmed delivery. * fix_: handle error from json marshal chore_: use zap logger as request logger iterates: status-im/status-desktop#16536 test_: unique project per run test_: use docker compose v2, more concrete project name fix(codecov)_: ignore folders without tests Otherwise Codecov reports incorrect numbers when making changes. https://docs.codecov.com/docs/ignoring-paths Signed-off-by: Jakub Sokołowski <jakub@status.im> test_: verify schema of signals during init; fix schema verification warnings (#5947) fix_: update defaultGorushURL (#6011) fix(tests)_: use non-standard port to avoid conflicts We have observed `nimbus-eth2` build failures reporting this port: ```json { "lvl": "NTC", "ts": "2024-10-28 13:51:32.308+00:00", "msg": "REST HTTP server could not be started", "topics": "beacnde", "address": "127.0.0.1:5432", "reason": "(98) Address already in use" } ``` https://ci.status.im/job/nimbus-eth2/job/platforms/job/linux/job/x86_64/job/main/job/PR-6683/3/ Signed-off-by: Jakub Sokołowski <jakub@status.im> fix_: create request logger ad-hoc in tests Fixes `TestCall` failing when run concurrently. chore_: configure codecov (#6005) * chore_: configure codecov * fix_: after_n_builds
2024-10-24 14:29:15 +00:00
"github.com/status-im/status-go/logutils"
"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"
"github.com/status-im/status-go/params"
2022-07-06 16:12:49 +00:00
"github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/services/accounts/accountsevent"
)
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}
}
// 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
}
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"`
}
func (api *API) SaveAccount(ctx context.Context, account *accounts.Account) error {
test_: Code Migration from status-cli-tests author shashankshampi <shashank.sanket1995@gmail.com> 1729780155 +0530 committer shashankshampi <shashank.sanket1995@gmail.com> 1730274350 +0530 test: Code Migration from status-cli-tests fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments fix(ci)_: remove workspace and tmp dir This ensures we do not encounter weird errors like: ``` + ln -s /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907 /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go ln: failed to create symbolic link '/home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go': File exists script returned exit code 1 ``` Signed-off-by: Jakub Sokołowski <jakub@status.im> chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 cleanup added logger and cleanup review comments changes fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 test_: remove port bind chore(wallet)_: move route execution code to separate module chore_: replace geth logger with zap logger (#5962) closes: #6002 feat(telemetry)_: add metrics for message reliability (#5899) * feat(telemetry)_: track message reliability Add metrics for dial errors, missed messages, missed relevant messages, and confirmed delivery. * fix_: handle error from json marshal chore_: use zap logger as request logger iterates: status-im/status-desktop#16536 test_: unique project per run test_: use docker compose v2, more concrete project name fix(codecov)_: ignore folders without tests Otherwise Codecov reports incorrect numbers when making changes. https://docs.codecov.com/docs/ignoring-paths Signed-off-by: Jakub Sokołowski <jakub@status.im> test_: verify schema of signals during init; fix schema verification warnings (#5947) fix_: update defaultGorushURL (#6011) fix(tests)_: use non-standard port to avoid conflicts We have observed `nimbus-eth2` build failures reporting this port: ```json { "lvl": "NTC", "ts": "2024-10-28 13:51:32.308+00:00", "msg": "REST HTTP server could not be started", "topics": "beacnde", "address": "127.0.0.1:5432", "reason": "(98) Address already in use" } ``` https://ci.status.im/job/nimbus-eth2/job/platforms/job/linux/job/x86_64/job/main/job/PR-6683/3/ Signed-off-by: Jakub Sokołowski <jakub@status.im> fix_: create request logger ad-hoc in tests Fixes `TestCall` failing when run concurrently. chore_: configure codecov (#6005) * chore_: configure codecov * fix_: after_n_builds
2024-10-24 14:29:15 +00:00
logutils.ZapLogger().Info("[AccountsAPI::SaveAccount]")
err := (*api.messenger).SaveOrUpdateAccount(account)
if err != nil {
return err
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeAdded,
Accounts: []common.Address{common.Address(account.Address)},
})
return nil
}
// Setting `Keypair` without `Accounts` will update keypair only, `Keycards` won't be saved/updated this way.
func (api *API) SaveKeypair(ctx context.Context, keypair *accounts.Keypair) error {
test_: Code Migration from status-cli-tests author shashankshampi <shashank.sanket1995@gmail.com> 1729780155 +0530 committer shashankshampi <shashank.sanket1995@gmail.com> 1730274350 +0530 test: Code Migration from status-cli-tests fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments fix(ci)_: remove workspace and tmp dir This ensures we do not encounter weird errors like: ``` + ln -s /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907 /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go ln: failed to create symbolic link '/home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go': File exists script returned exit code 1 ``` Signed-off-by: Jakub Sokołowski <jakub@status.im> chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 cleanup added logger and cleanup review comments changes fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 test_: remove port bind chore(wallet)_: move route execution code to separate module chore_: replace geth logger with zap logger (#5962) closes: #6002 feat(telemetry)_: add metrics for message reliability (#5899) * feat(telemetry)_: track message reliability Add metrics for dial errors, missed messages, missed relevant messages, and confirmed delivery. * fix_: handle error from json marshal chore_: use zap logger as request logger iterates: status-im/status-desktop#16536 test_: unique project per run test_: use docker compose v2, more concrete project name fix(codecov)_: ignore folders without tests Otherwise Codecov reports incorrect numbers when making changes. https://docs.codecov.com/docs/ignoring-paths Signed-off-by: Jakub Sokołowski <jakub@status.im> test_: verify schema of signals during init; fix schema verification warnings (#5947) fix_: update defaultGorushURL (#6011) fix(tests)_: use non-standard port to avoid conflicts We have observed `nimbus-eth2` build failures reporting this port: ```json { "lvl": "NTC", "ts": "2024-10-28 13:51:32.308+00:00", "msg": "REST HTTP server could not be started", "topics": "beacnde", "address": "127.0.0.1:5432", "reason": "(98) Address already in use" } ``` https://ci.status.im/job/nimbus-eth2/job/platforms/job/linux/job/x86_64/job/main/job/PR-6683/3/ Signed-off-by: Jakub Sokołowski <jakub@status.im> fix_: create request logger ad-hoc in tests Fixes `TestCall` failing when run concurrently. chore_: configure codecov (#6005) * chore_: configure codecov * fix_: after_n_builds
2024-10-24 14:29:15 +00:00
logutils.ZapLogger().Info("[AccountsAPI::SaveKeypair]")
err := (*api.messenger).SaveOrUpdateKeypair(keypair)
if err != nil {
return err
}
commonAddresses := []common.Address{}
for _, acc := range keypair.Accounts {
commonAddresses = append(commonAddresses, common.Address(acc.Address))
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeAdded,
Accounts: commonAddresses,
})
return nil
}
func (api *API) HasPairedDevices(ctx context.Context) bool {
return (*api.messenger).HasPairedDevices()
}
// 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)
}
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()
}
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()
}
func (api *API) GetAccounts(ctx context.Context) ([]*accounts.Account, error) {
return api.db.GetActiveAccounts()
}
func (api *API) GetWatchOnlyAccounts(ctx context.Context) ([]*accounts.Account, error) {
return api.db.GetActiveWatchOnlyAccounts()
}
func (api *API) GetKeypairs(ctx context.Context) ([]*accounts.Keypair, error) {
return api.db.GetActiveKeypairs()
}
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)
}
func (api *API) DeleteAccount(ctx context.Context, address types.Address) error {
err := (*api.messenger).DeleteAccount(address)
if err != nil {
return err
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeRemoved,
Accounts: []common.Address{common.Address(address)},
})
return nil
}
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
}
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
}
// RemainingAccountCapacity returns the number of accounts that can be added.
func (api *API) RemainingAccountCapacity(ctx context.Context) (int, error) {
return (*api.messenger).RemainingAccountCapacity()
}
// RemainingKeypairCapacity returns the number of keypairs that can be added.
func (api *API) RemainingKeypairCapacity(ctx context.Context) (int, error) {
return (*api.messenger).RemainingKeypairCapacity()
}
// RemainingWatchOnlyAccountCapacity returns the number of watch-only accounts that can be added.
func (api *API) RemainingWatchOnlyAccountCapacity(ctx context.Context) (int, error) {
return (*api.messenger).RemainingWatchOnlyAccountCapacity()
}
func (api *API) checkAccountValidity(account *accounts.Account) error {
if len(account.Address) == 0 {
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")
}
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 of an account must be set")
}
if len(account.Emoji) == 0 {
return errors.New("`Emoji` field of an account must be set")
}
if len(account.ColorID) == 0 {
return errors.New("`ColorID` field of an account must be set")
}
if account.Type != accounts.AccountTypeWatch {
if len(account.KeyUID) == 0 {
return errors.New("`KeyUID` field of an account must be set")
}
if len(account.PublicKey) == 0 {
return errors.New("`PublicKey` field of an account must be set")
}
if account.Type != accounts.AccountTypeKey {
if len(account.Path) == 0 {
return errors.New("`Path` field of an account must be set")
}
}
}
addressExists, err := api.db.AddressExists(account.Address)
if err != nil {
return err
}
if addressExists {
return errors.New("account already exists")
}
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)
if err != nil {
if err == accounts.ErrDbKeypairNotFound {
return errors.New("cannot add an account for an unknown keypair")
}
return err
}
// 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
}
}
}
if account.Type == accounts.AccountTypeGenerated {
account.AddressWasNotShown = true
}
return api.SaveAccount(ctx, account)
}
// 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
}
kp, err := api.db.GetKeypairByKeyUID(info.KeyUID)
if err != nil && err != accounts.ErrDbKeypairNotFound {
return err
}
if kp != nil {
return errors.New("provided private key was already imported")
}
_, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password)
return err
}
// 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
}
return (*api.messenger).MarkKeypairFullyOperable(info.KeyUID)
}
func (api *API) MakePartiallyOperableAccoutsFullyOperable(ctx context.Context, password string) (addresses []types.Address, err error) {
profileKeypair, err := api.db.GetProfileKeypair()
if err != nil {
return
}
if !profileKeypair.MigratedToKeycard() && !api.VerifyPassword(password) {
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
}
// 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, "")
if err != nil {
return err
}
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
if err != nil && err != accounts.ErrDbKeypairNotFound {
return err
}
if kp != nil {
return errors.New("provided mnemonic was already imported, to add new account use `AddAccount` endpoint")
}
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
return err
}
// 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
}
return (*api.messenger).MarkKeypairFullyOperable(generatedAccountInfo.KeyUID)
}
// Creates a random new mnemonic.
func (api *API) GetRandomMnemonic(ctx context.Context) (string, error) {
return account.GetRandomMnemonic()
}
func (api *API) VerifyKeystoreFileForAccount(address types.Address, password string) bool {
_, err := api.manager.VerifyAccountPassword(api.config.KeyStoreDir, address.Hex(), password)
return err == nil
}
func (api *API) VerifyPassword(password string) bool {
address, err := api.db.GetChatAddress()
if err != nil {
return false
}
return api.VerifyKeystoreFileForAccount(address, password)
}
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)
}
// If keypair is migrated from keycard to app, then `accountsComingFromKeycard` should be set to true, otherwise false.
// 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 {
return errors.New("cannot migrate a keypair without accounts")
}
kpDb, err := api.db.GetKeypairByKeyUID(keycard.KeyUID)
if err != nil {
if err == accounts.ErrDbKeypairNotFound {
return errors.New("cannot migrate an unknown keypair")
}
return err
}
err = (*api.messenger).SaveOrUpdateKeycard(ctx, keycard)
if err != nil {
return err
}
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)
// 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)
if err != nil {
return err
}
}
err = api.manager.DeleteAccount(types.Address(common.HexToAddress(kpDb.DerivedFrom)))
if err != nil {
return err
}
}
err = (*api.messenger).MarkKeypairFullyOperable(keycard.KeyUID)
if err != nil {
return err
}
}
return nil
}
func (api *API) GetAllKnownKeycards(ctx context.Context) ([]*accounts.Keycard, error) {
return api.db.GetAllKnownKeycards()
}
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)
}
func (api *API) SetKeycardName(ctx context.Context, keycardUID string, kpName string) error {
return (*api.messenger).SetKeycardName(ctx, keycardUID, kpName)
}
func (api *API) KeycardLocked(ctx context.Context, keycardUID string) error {
return (*api.messenger).KeycardLocked(ctx, keycardUID)
}
func (api *API) KeycardUnlocked(ctx context.Context, keycardUID string) error {
return (*api.messenger).KeycardUnlocked(ctx, keycardUID)
}
func (api *API) DeleteKeycardAccounts(ctx context.Context, keycardUID string, accountAddresses []types.Address) error {
return (*api.messenger).DeleteKeycardAccounts(ctx, keycardUID, accountAddresses)
}
func (api *API) DeleteKeycard(ctx context.Context, keycardUID string) error {
return (*api.messenger).DeleteKeycard(ctx, keycardUID)
}
func (api *API) DeleteAllKeycardsWithKeyUID(ctx context.Context, keyUID string) error {
return (*api.messenger).DeleteAllKeycardsWithKeyUID(ctx, keyUID)
}
func (api *API) UpdateKeycardUID(ctx context.Context, oldKeycardUID string, newKeycardUID string) error {
return (*api.messenger).UpdateKeycardUID(ctx, oldKeycardUID, newKeycardUID)
}
func (api *API) AddressWasShown(address types.Address) error {
return api.db.AddressWasShown(address)
}
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)
}