diff --git a/node/status_node_services.go b/node/status_node_services.go index fd19ce7d3..2499a4fce 100644 --- a/node/status_node_services.go +++ b/node/status_node_services.go @@ -443,7 +443,7 @@ func (b *StatusNode) appmetricsService() common.StatusService { func (b *StatusNode) walletService(accountsDB *accounts.Database, accountsFeed *event.Feed, openseaAPIKey string) common.StatusService { if b.walletSrvc == nil { - b.walletSrvc = wallet.NewService(b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey) + b.walletSrvc = wallet.NewService(b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey, b.gethAccountManager) } return b.walletSrvc } diff --git a/services/accounts/accounts.go b/services/accounts/accounts.go index ceb8b76fc..22fd30398 100644 --- a/services/accounts/accounts.go +++ b/services/accounts/accounts.go @@ -32,13 +32,6 @@ type API struct { feed *event.Feed } -type DerivedAddress struct { - Address common.Address `json:"address"` - Path string `json:"path"` - HasActivity bool `json:"hasActivity"` - AlreadyCreated bool `json:"alreadyCreated"` -} - func (api *API) SaveAccounts(ctx context.Context, accounts []accounts.Account) error { log.Info("[AccountsAPI::SaveAccounts]") err := api.db.SaveAccounts(accounts) @@ -201,26 +194,6 @@ func (api *API) GenerateAccountWithDerivedPath( return api.generateAccount(ctx, password, name, color, emoji, path, derivedFrom) } -func (api *API) GetDerivedAddressesForPath(password string, derivedFrom string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { - info, err := api.manager.AccountsGenerator().LoadAccount(derivedFrom, password) - if err != nil { - return nil, err - } - - return api.getDerivedAddresses(info.ID, path, pageSize, pageNumber) -} - -func (api *API) GetDerivedAddressesForMenominicWithPath(mnemonic string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { - mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ") - - info, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "") - if err != nil { - return nil, err - } - - return api.getDerivedAddresses(info.ID, path, pageSize, pageNumber) -} - func (api *API) verifyPassword(password string) error { address, err := api.db.GetChatAddress() if err != nil { @@ -230,49 +203,6 @@ func (api *API) verifyPassword(password string) error { return err } -func (api *API) getDerivedAddresses(id string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { - addedAccounts, err := api.db.GetAccounts() - if err != nil { - return nil, err - } - - derivedAddresses := make([]*DerivedAddress, 0) - - 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) - - info, err := api.manager.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 - } - } - - address := &DerivedAddress{ - Address: common.HexToAddress(info[derivedPath].Address), - Path: derivedPath, - HasActivity: false, - AlreadyCreated: alreadyExists, - } - - derivedAddresses = append(derivedAddresses, address) - } - return derivedAddresses, nil -} - func (api *API) addAccountWithMnemonic( ctx context.Context, mnemonic string, diff --git a/services/wallet/api.go b/services/wallet/api.go index 88d4530e1..8a5f91d0e 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -2,11 +2,16 @@ package wallet import ( "context" + "fmt" + "math/big" + "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" + "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" + "github.com/status-im/status-go/services/wallet/async" "github.com/status-im/status-go/services/wallet/chain" "github.com/status-im/status-go/services/wallet/transfer" ) @@ -30,6 +35,13 @@ func (api *API) GetWallet(ctx context.Context, chainIDs []uint64) (*Wallet, erro return api.r.GetWallet(ctx, chainIDs) } +type DerivedAddress struct { + Address common.Address `json:"address"` + Path string `json:"path"` + HasActivity bool `json:"hasActivity"` + AlreadyCreated bool `json:"alreadyCreated"` +} + // SetInitialBlocksRange sets initial blocks range func (api *API) SetInitialBlocksRange(ctx context.Context) error { return api.s.transferController.SetInitialBlocksRange([]uint64{api.s.rpcClient.UpstreamChainID}) @@ -303,3 +315,144 @@ func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*Suggeste log.Debug("call to GetSuggestedFees") return api.s.feesManager.suggestedFees(ctx, chainID) } + +func (api *API) GetDerivedAddressesForPath(ctx context.Context, password string, derivedFrom string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { + info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password) + if err != nil { + return nil, err + } + + return api.getDerivedAddresses(ctx, info.ID, path, pageSize, pageNumber) +} + +func (api *API) GetDerivedAddressesForMenominicWithPath(ctx context.Context, mnemonic string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) { + mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ") + + info, err := api.s.gethManager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "") + if err != nil { + return nil, err + } + + return api.getDerivedAddresses(ctx, info.ID, path, pageSize, pageNumber) +} + +func (api *API) GetDerivedAddressForPrivateKey(ctx context.Context, privateKey string) ([]*DerivedAddress, error) { + var derivedAddresses = make([]*DerivedAddress, 0) + info, err := api.s.gethManager.AccountsGenerator().ImportPrivateKey(privateKey) + if err != nil { + return derivedAddresses, err + } + + addressExists, err := api.s.accountsDB.AddressExists(types.Address(common.HexToAddress(info.Address))) + if err != nil { + return derivedAddresses, err + } + if addressExists { + return derivedAddresses, fmt.Errorf("account already exists") + } + + transactions, err := api.s.transferController.GetTransfersByAddress(ctx, api.s.rpcClient.UpstreamChainID, common.HexToAddress(info.Address), nil, (*hexutil.Big)(big.NewInt(1)), false) + + if err != nil { + return derivedAddresses, err + } + + hasActivity := int64(len(transactions)) > 0 + + derivedAddress := &DerivedAddress{ + Address: common.HexToAddress(info.Address), + Path: "", + HasActivity: hasActivity, + AlreadyCreated: addressExists, + } + derivedAddresses = append(derivedAddresses, derivedAddress) + + return derivedAddresses, 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, (*hexutil.Big)(big.NewInt(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 +} diff --git a/services/wallet/service.go b/services/wallet/service.go index 0f2b24d86..a635a61fd 100644 --- a/services/wallet/service.go +++ b/services/wallet/service.go @@ -8,13 +8,14 @@ import ( "github.com/ethereum/go-ethereum/p2p" gethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/status-im/status-go/account" "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/wallet/transfer" ) // NewService initializes service instance. -func NewService(db *sql.DB, accountsDB *accounts.Database, rpcClient *rpc.Client, accountFeed *event.Feed, openseaAPIKey string) *Service { +func NewService(db *sql.DB, accountsDB *accounts.Database, rpcClient *rpc.Client, accountFeed *event.Feed, openseaAPIKey string, gethManager *account.GethManager) *Service { cryptoOnRampManager := NewCryptoOnRampManager(&CryptoOnRampOptions{ dataSourceType: DataSourceStatic, }) @@ -36,6 +37,7 @@ func NewService(db *sql.DB, accountsDB *accounts.Database, rpcClient *rpc.Client cryptoOnRampManager: cryptoOnRampManager, openseaAPIKey: openseaAPIKey, feesManager: &FeeManager{rpcClient}, + gethManager: gethManager, } } @@ -53,6 +55,7 @@ type Service struct { feesManager *FeeManager started bool openseaAPIKey string + gethManager *account.GethManager } // Start signals transmitter.