status-go/services/wallet/service.go

406 lines
13 KiB
Go
Raw Permalink Normal View History

package wallet
import (
"database/sql"
2023-03-23 12:13:16 +00:00
"encoding/json"
"fmt"
"sync"
2023-02-20 09:32:45 +00:00
"time"
2023-03-20 13:02:09 +00:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"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/logutils"
2022-05-10 07:48:05 +00:00
"github.com/status-im/status-go/multiaccounts/accounts"
2022-07-15 08:53:56 +00:00
"github.com/status-im/status-go/params"
protocolCommon "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/services/wallet/activity"
"github.com/status-im/status-go/services/wallet/balance"
"github.com/status-im/status-go/services/wallet/blockchainstate"
"github.com/status-im/status-go/services/wallet/collectibles"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/currency"
feat: retrieve balance history for tokens and cache it to DB Extends wallet module with the history package with the following components: BalanceDB (balance_db.go) - Keeps track of balance information (token count, block, block timestamp) for a token identity (chain, address, currency) - The cached data is stored in `balance_history` table. - Uniqueness constrained is enforced by the `balance_history_identify_entry` UNIQUE index. - Optimal DB fetching is ensured by the `balance_history_filter_entries` index Balance (balance.go) - Provides two stages: - Fetch of balance history using RPC calls (Balance.update function) - Retrieving of cached balance data from the DB it exists (Balance.get function) - Fetching and retrieving of data is done for specific time intervals defined by TimeInterval "enumeration" - Update process is done for a token identity by the Balance.Update function - The granularity of data points returned is defined by the constant increment step define in `timeIntervalToStride` for each time interval. - The `blocksStride` values have a common divisor to have cache hit between time intervals. Service (service.go) - Main APIs - StartBalanceHistory: Regularly updates balance history for all enabled networks, available accounts and provided tokens. - GetBalanceHistory: retrieves cached token count for a token identity (chain, address, currency) for multiple chains - UpdateVisibleTokens: will set the list of tokens to have historical balance fetched. This is a simplification to limit tokens to a small list that make sense Fetch balance history for ECR20 tokens - Add token.Manager.GetTokenBalanceAt to fetch balance of a specific block number of ECR20. - Add tokenChainClientSource concrete implementation of DataSource to fetch balance of ECR20 tokens. - Chose the correct DataSource implementation based on the token "is native" property. Tests Tests are implemented using a mock of `DataSource` interface used to intercept the RPC calls. Notes: - the timestamp used for retrieving block balance is constant Closes status-desktop: #8175, #8226, #8862
2022-11-15 12:14:41 +00:00
"github.com/status-im/status-go/services/wallet/history"
2023-02-21 09:05:16 +00:00
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/onramp"
"github.com/status-im/status-go/services/wallet/routeexecution"
"github.com/status-im/status-go/services/wallet/router"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/alchemy"
"github.com/status-im/status-go/services/wallet/thirdparty/coingecko"
2023-02-21 09:05:16 +00:00
"github.com/status-im/status-go/services/wallet/thirdparty/cryptocompare"
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
"github.com/status-im/status-go/services/wallet/thirdparty/rarible"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
2022-12-01 09:19:32 +00:00
"github.com/status-im/status-go/services/wallet/walletevent"
2022-07-15 08:53:56 +00:00
"github.com/status-im/status-go/transactions"
)
2023-03-20 13:02:09 +00:00
const (
EventBlockchainStatusChanged walletevent.EventType = "wallet-blockchain-status-changed"
)
2023-01-17 09:56:16 +00:00
// NewService initializes service instance.
2022-07-15 08:53:56 +00:00
func NewService(
db *sql.DB,
accountsDB *accounts.Database,
appDB *sql.DB,
2022-07-15 08:53:56 +00:00
rpcClient *rpc.Client,
accountFeed *event.Feed,
settingsFeed *event.Feed,
2022-07-15 08:53:56 +00:00
gethManager *account.GethManager,
transactor *transactions.Transactor,
config *params.NodeConfig,
2022-09-13 07:10:59 +00:00
ens *ens.Service,
pendingTxManager *transactions.PendingTxTracker,
feed *event.Feed,
mediaServer *server.MediaServer,
statusProxyStageName string,
2022-07-15 08:53:56 +00:00
) *Service {
2022-12-01 09:19:32 +00:00
signals := &walletevent.SignalsTransmitter{
Publisher: feed,
2022-12-01 09:19:32 +00:00
}
2023-03-23 12:13:16 +00:00
blockchainStatus := make(map[uint64]string)
mutex := sync.Mutex{}
2023-03-20 13:02:09 +00:00
rpcClient.SetWalletNotifier(func(chainID uint64, message string) {
mutex.Lock()
defer mutex.Unlock()
2023-03-23 12:13:16 +00:00
if len(blockchainStatus) == 0 {
networks, err := rpcClient.NetworkManager.Get(false)
if err != nil {
return
}
for _, network := range networks {
blockchainStatus[network.ChainID] = "up"
}
}
blockchainStatus[chainID] = message
encodedmessage, err := json.Marshal(blockchainStatus)
if err != nil {
return
}
feed.Send(walletevent.Event{
2023-03-20 13:02:09 +00:00
Type: EventBlockchainStatusChanged,
Accounts: []common.Address{},
2023-03-23 12:13:16 +00:00
Message: string(encodedmessage),
2023-03-20 13:02:09 +00:00
At: time.Now().Unix(),
ChainID: chainID,
})
})
communityManager := community.NewManager(db, mediaServer, feed)
balanceCacher := balance.NewCacherWithTTL(5 * time.Minute)
tokenManager := token.NewTokenManager(db, rpcClient, communityManager, rpcClient.NetworkManager, appDB, mediaServer, feed, accountFeed, accountsDB, token.NewPersistence(db))
tokenManager.Start()
cryptoOnRampProviders := []onramp.Provider{
onramp.NewMercuryoProvider(tokenManager),
onramp.NewRampProvider(),
onramp.NewMoonPayProvider(),
}
cryptoOnRampManager := onramp.NewManager(cryptoOnRampProviders)
2021-09-10 18:08:22 +00:00
savedAddressesManager := &SavedAddressesManager{db: db}
transactionManager := transfer.NewTransactionManager(transfer.NewMultiTransactionDB(db), gethManager, transactor, config, accountsDB, pendingTxManager, feed)
blockChainState := blockchainstate.NewBlockChainState()
transferController := transfer.NewTransferController(db, accountsDB, rpcClient, accountFeed, feed, transactionManager, pendingTxManager,
tokenManager, balanceCacher, blockChainState)
transferController.Start()
2023-02-21 09:05:16 +00:00
cryptoCompare := cryptocompare.NewClient()
coingecko := coingecko.NewClient()
cryptoCompareProxy := cryptocompare.NewClientWithParams(cryptocompare.Params{
ID: fmt.Sprintf("%s-proxy", cryptoCompare.ID()),
URL: fmt.Sprintf("https://%s.api.status.im/cryptocompare/", statusProxyStageName),
User: config.WalletConfig.StatusProxyMarketUser,
Password: config.WalletConfig.StatusProxyMarketPassword,
})
marketManager := market.NewManager([]thirdparty.MarketDataProvider{cryptoCompare, coingecko, cryptoCompareProxy}, feed)
reader := NewReader(tokenManager, marketManager, token.NewPersistence(db), feed)
history := history.NewService(db, accountsDB, accountFeed, feed, rpcClient, tokenManager, marketManager, balanceCacher.Cache())
currency := currency.NewService(db, feed, tokenManager, marketManager)
2023-08-17 18:10:13 +00:00
openseaHTTPClient := opensea.NewHTTPClient()
openseaV2Client := opensea.NewClientV2(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient)
raribleClient := rarible.NewClient(config.WalletConfig.RaribleMainnetAPIKey, config.WalletConfig.RaribleTestnetAPIKey)
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
// Collectible providers in priority order (i.e. provider N+1 will be tried only if provider N fails)
contractOwnershipProviders := []thirdparty.CollectibleContractOwnershipProvider{
raribleClient,
alchemyClient,
}
accountOwnershipProviders := []thirdparty.CollectibleAccountOwnershipProvider{
raribleClient,
alchemyClient,
openseaV2Client,
}
collectibleDataProviders := []thirdparty.CollectibleDataProvider{
raribleClient,
alchemyClient,
openseaV2Client,
}
2023-08-16 13:01:57 +00:00
collectionDataProviders := []thirdparty.CollectionDataProvider{
raribleClient,
2023-08-16 13:01:57 +00:00
alchemyClient,
openseaV2Client,
2023-08-16 13:01:57 +00:00
}
collectibleSearchProviders := []thirdparty.CollectibleSearchProvider{
raribleClient,
}
collectibleProviders := thirdparty.CollectibleProviders{
ContractOwnershipProviders: contractOwnershipProviders,
AccountOwnershipProviders: accountOwnershipProviders,
CollectibleDataProviders: collectibleDataProviders,
CollectionDataProviders: collectionDataProviders,
SearchProviders: collectibleSearchProviders,
}
collectiblesManager := collectibles.NewManager(
db,
rpcClient,
communityManager,
collectibleProviders,
mediaServer,
feed,
)
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, settingsFeed, communityManager, rpcClient.NetworkManager, collectiblesManager)
activity := activity.NewService(db, accountsDB, tokenManager, collectiblesManager, feed, pendingTxManager)
featureFlags := &protocolCommon.FeatureFlags{}
if config.WalletConfig.EnableCelerBridge {
featureFlags.EnableCelerBridge = true
}
router := router.NewRouter(rpcClient, transactor, tokenManager, marketManager, collectibles,
collectiblesManager, ens)
pathProcessors := buildPathProcessors(rpcClient, transactor, tokenManager, ens, featureFlags)
for _, processor := range pathProcessors {
router.AddPathProcessor(processor)
}
routeExecutionManager := routeexecution.NewManager(router, transactionManager, transferController)
return &Service{
2022-05-10 07:48:05 +00:00
db: db,
accountsDB: accountsDB,
rpcClient: rpcClient,
2021-09-10 18:08:22 +00:00
tokenManager: tokenManager,
communityManager: communityManager,
2021-09-10 18:08:22 +00:00
savedAddressesManager: savedAddressesManager,
transactionManager: transactionManager,
pendingTxManager: pendingTxManager,
2021-09-10 18:08:22 +00:00
transferController: transferController,
cryptoOnRampManager: cryptoOnRampManager,
collectiblesManager: collectiblesManager,
2023-07-18 15:02:56 +00:00
collectibles: collectibles,
gethManager: gethManager,
2023-02-21 09:05:16 +00:00
marketManager: marketManager,
2022-09-13 07:10:59 +00:00
transactor: transactor,
ens: ens,
feed: feed,
2022-12-01 09:19:32 +00:00
signals: signals,
reader: reader,
feat: retrieve balance history for tokens and cache it to DB Extends wallet module with the history package with the following components: BalanceDB (balance_db.go) - Keeps track of balance information (token count, block, block timestamp) for a token identity (chain, address, currency) - The cached data is stored in `balance_history` table. - Uniqueness constrained is enforced by the `balance_history_identify_entry` UNIQUE index. - Optimal DB fetching is ensured by the `balance_history_filter_entries` index Balance (balance.go) - Provides two stages: - Fetch of balance history using RPC calls (Balance.update function) - Retrieving of cached balance data from the DB it exists (Balance.get function) - Fetching and retrieving of data is done for specific time intervals defined by TimeInterval "enumeration" - Update process is done for a token identity by the Balance.Update function - The granularity of data points returned is defined by the constant increment step define in `timeIntervalToStride` for each time interval. - The `blocksStride` values have a common divisor to have cache hit between time intervals. Service (service.go) - Main APIs - StartBalanceHistory: Regularly updates balance history for all enabled networks, available accounts and provided tokens. - GetBalanceHistory: retrieves cached token count for a token identity (chain, address, currency) for multiple chains - UpdateVisibleTokens: will set the list of tokens to have historical balance fetched. This is a simplification to limit tokens to a small list that make sense Fetch balance history for ECR20 tokens - Add token.Manager.GetTokenBalanceAt to fetch balance of a specific block number of ECR20. - Add tokenChainClientSource concrete implementation of DataSource to fetch balance of ECR20 tokens. - Chose the correct DataSource implementation based on the token "is native" property. Tests Tests are implemented using a mock of `DataSource` interface used to intercept the RPC calls. Notes: - the timestamp used for retrieving block balance is constant Closes status-desktop: #8175, #8226, #8862
2022-11-15 12:14:41 +00:00
history: history,
currency: currency,
activity: activity,
2023-07-06 07:37:04 +00:00
decoder: NewDecoder(),
2023-09-04 10:18:46 +00:00
blockChainState: blockChainState,
keycardPairings: NewKeycardPairings(),
config: config,
featureFlags: featureFlags,
router: router,
routeExecutionManager: routeExecutionManager,
}
}
func buildPathProcessors(
rpcClient *rpc.Client,
transactor *transactions.Transactor,
tokenManager *token.Manager,
ens *ens.Service,
featureFlags *protocolCommon.FeatureFlags,
) []pathprocessor.PathProcessor {
ret := make([]pathprocessor.PathProcessor, 0)
transfer := pathprocessor.NewTransferProcessor(rpcClient, transactor)
ret = append(ret, transfer)
erc721Transfer := pathprocessor.NewERC721Processor(rpcClient, transactor)
ret = append(ret, erc721Transfer)
erc1155Transfer := pathprocessor.NewERC1155Processor(rpcClient, transactor)
ret = append(ret, erc1155Transfer)
hop := pathprocessor.NewHopBridgeProcessor(rpcClient, transactor, tokenManager, rpcClient.NetworkManager)
ret = append(ret, hop)
if featureFlags.EnableCelerBridge {
// TODO: Celar Bridge is out of scope for 2.30, check it thoroughly once we decide to include it again
cbridge := pathprocessor.NewCelerBridgeProcessor(rpcClient, transactor, tokenManager)
ret = append(ret, cbridge)
}
paraswap := pathprocessor.NewSwapParaswapProcessor(rpcClient, transactor, tokenManager)
ret = append(ret, paraswap)
ensRegister := pathprocessor.NewENSRegisterProcessor(rpcClient, transactor, ens)
ret = append(ret, ensRegister)
ensRelease := pathprocessor.NewENSReleaseProcessor(rpcClient, transactor, ens)
ret = append(ret, ensRelease)
ensPublicKey := pathprocessor.NewENSPublicKeyProcessor(rpcClient, transactor, ens)
ret = append(ret, ensPublicKey)
buyStickers := pathprocessor.NewStickersBuyProcessor(rpcClient, transactor)
ret = append(ret, buyStickers)
return ret
}
// Service is a wallet service.
type Service struct {
2022-05-10 07:48:05 +00:00
db *sql.DB
accountsDB *accounts.Database
rpcClient *rpc.Client
2021-09-10 18:08:22 +00:00
savedAddressesManager *SavedAddressesManager
2022-10-25 14:50:32 +00:00
tokenManager *token.Manager
communityManager *community.Manager
transactionManager *transfer.TransactionManager
pendingTxManager *transactions.PendingTxTracker
cryptoOnRampManager *onramp.Manager
2021-09-10 18:08:22 +00:00
transferController *transfer.Controller
2023-02-21 09:05:16 +00:00
marketManager *market.Manager
2021-09-10 18:08:22 +00:00
started bool
collectiblesManager *collectibles.Manager
2023-07-18 15:02:56 +00:00
collectibles *collectibles.Service
gethManager *account.GethManager
2022-09-13 07:10:59 +00:00
transactor *transactions.Transactor
ens *ens.Service
feed *event.Feed
2022-12-01 09:19:32 +00:00
signals *walletevent.SignalsTransmitter
reader *Reader
feat: retrieve balance history for tokens and cache it to DB Extends wallet module with the history package with the following components: BalanceDB (balance_db.go) - Keeps track of balance information (token count, block, block timestamp) for a token identity (chain, address, currency) - The cached data is stored in `balance_history` table. - Uniqueness constrained is enforced by the `balance_history_identify_entry` UNIQUE index. - Optimal DB fetching is ensured by the `balance_history_filter_entries` index Balance (balance.go) - Provides two stages: - Fetch of balance history using RPC calls (Balance.update function) - Retrieving of cached balance data from the DB it exists (Balance.get function) - Fetching and retrieving of data is done for specific time intervals defined by TimeInterval "enumeration" - Update process is done for a token identity by the Balance.Update function - The granularity of data points returned is defined by the constant increment step define in `timeIntervalToStride` for each time interval. - The `blocksStride` values have a common divisor to have cache hit between time intervals. Service (service.go) - Main APIs - StartBalanceHistory: Regularly updates balance history for all enabled networks, available accounts and provided tokens. - GetBalanceHistory: retrieves cached token count for a token identity (chain, address, currency) for multiple chains - UpdateVisibleTokens: will set the list of tokens to have historical balance fetched. This is a simplification to limit tokens to a small list that make sense Fetch balance history for ECR20 tokens - Add token.Manager.GetTokenBalanceAt to fetch balance of a specific block number of ECR20. - Add tokenChainClientSource concrete implementation of DataSource to fetch balance of ECR20 tokens. - Chose the correct DataSource implementation based on the token "is native" property. Tests Tests are implemented using a mock of `DataSource` interface used to intercept the RPC calls. Notes: - the timestamp used for retrieving block balance is constant Closes status-desktop: #8175, #8226, #8862
2022-11-15 12:14:41 +00:00
history *history.Service
currency *currency.Service
activity *activity.Service
2023-07-06 07:37:04 +00:00
decoder *Decoder
blockChainState *blockchainstate.BlockChainState
keycardPairings *KeycardPairings
config *params.NodeConfig
featureFlags *protocolCommon.FeatureFlags
router *router.Router
routeExecutionManager *routeexecution.Manager
}
// Start signals transmitter.
func (s *Service) Start() error {
2022-12-01 09:19:32 +00:00
s.transferController.Start()
s.currency.Start()
2022-12-01 09:19:32 +00:00
err := s.signals.Start()
s.history.Start()
2023-07-18 15:02:56 +00:00
s.collectibles.Start()
s.started = true
return err
}
// Set external Collectibles community info provider
func (s *Service) SetWalletCommunityInfoProvider(provider thirdparty.CommunityInfoProvider) {
s.communityManager.SetCommunityInfoProvider(provider)
}
2022-12-01 09:19:32 +00:00
// Stop reactor and close db.
func (s *Service) Stop() error {
logutils.ZapLogger().Info("wallet will be stopped")
s.router.Stop()
2022-12-01 09:19:32 +00:00
s.signals.Stop()
s.transferController.Stop()
s.currency.Stop()
2022-12-01 09:19:32 +00:00
s.reader.Stop()
feat: retrieve balance history for tokens and cache it to DB Extends wallet module with the history package with the following components: BalanceDB (balance_db.go) - Keeps track of balance information (token count, block, block timestamp) for a token identity (chain, address, currency) - The cached data is stored in `balance_history` table. - Uniqueness constrained is enforced by the `balance_history_identify_entry` UNIQUE index. - Optimal DB fetching is ensured by the `balance_history_filter_entries` index Balance (balance.go) - Provides two stages: - Fetch of balance history using RPC calls (Balance.update function) - Retrieving of cached balance data from the DB it exists (Balance.get function) - Fetching and retrieving of data is done for specific time intervals defined by TimeInterval "enumeration" - Update process is done for a token identity by the Balance.Update function - The granularity of data points returned is defined by the constant increment step define in `timeIntervalToStride` for each time interval. - The `blocksStride` values have a common divisor to have cache hit between time intervals. Service (service.go) - Main APIs - StartBalanceHistory: Regularly updates balance history for all enabled networks, available accounts and provided tokens. - GetBalanceHistory: retrieves cached token count for a token identity (chain, address, currency) for multiple chains - UpdateVisibleTokens: will set the list of tokens to have historical balance fetched. This is a simplification to limit tokens to a small list that make sense Fetch balance history for ECR20 tokens - Add token.Manager.GetTokenBalanceAt to fetch balance of a specific block number of ECR20. - Add tokenChainClientSource concrete implementation of DataSource to fetch balance of ECR20 tokens. - Chose the correct DataSource implementation based on the token "is native" property. Tests Tests are implemented using a mock of `DataSource` interface used to intercept the RPC calls. Notes: - the timestamp used for retrieving block balance is constant Closes status-desktop: #8175, #8226, #8862
2022-11-15 12:14:41 +00:00
s.history.Stop()
s.activity.Stop()
2023-07-18 15:02:56 +00:00
s.collectibles.Stop()
s.tokenManager.Stop()
s.started = false
logutils.ZapLogger().Info("wallet stopped")
return nil
}
// APIs returns list of available RPC APIs.
func (s *Service) APIs() []gethrpc.API {
return []gethrpc.API{
{
Namespace: "wallet",
Version: "0.1.0",
Service: NewAPI(s),
Public: true,
},
}
}
// Protocols returns list of p2p protocols.
func (s *Service) Protocols() []p2p.Protocol {
return nil
}
func (s *Service) IsStarted() bool {
return s.started
}
func (s *Service) KeycardPairings() *KeycardPairings {
return s.keycardPairings
}
func (s *Service) Config() *params.NodeConfig {
return s.config
}
func (s *Service) FeatureFlags() *protocolCommon.FeatureFlags {
return s.featureFlags
}
func (s *Service) GetRPCClient() *rpc.Client {
return s.rpcClient
}
func (s *Service) GetTransactor() *transactions.Transactor {
return s.transactor
}
func (s *Service) GetTokenManager() *token.Manager {
return s.tokenManager
}
func (s *Service) GetMarketManager() *market.Manager {
return s.marketManager
}
func (s *Service) GetCollectiblesService() *collectibles.Service {
return s.collectibles
}
func (s *Service) GetCollectiblesManager() *collectibles.Manager {
return s.collectiblesManager
}
func (s *Service) GetEnsService() *ens.Service {
return s.ens
}