status-go/services/wallet/reader.go

248 lines
6.8 KiB
Go
Raw Normal View History

2022-05-10 07:48:05 +00:00
package wallet
import (
"context"
2022-10-27 07:38:05 +00:00
"math"
2022-05-10 07:48:05 +00:00
"math/big"
"time"
2022-05-10 07:48:05 +00:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
2022-12-01 09:19:32 +00:00
"github.com/ethereum/go-ethereum/event"
2022-05-10 07:48:05 +00:00
"github.com/status-im/status-go/multiaccounts/accounts"
2022-12-01 09:19:32 +00:00
"github.com/status-im/status-go/rpc"
2022-10-27 07:38:05 +00:00
"github.com/status-im/status-go/services/wallet/async"
2022-05-10 07:48:05 +00:00
"github.com/status-im/status-go/services/wallet/chain"
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/walletevent"
2022-05-10 07:48:05 +00:00
)
// WalletTickReload emitted every 15mn to reload the wallet balance and history
const EventWalletTickReload walletevent.EventType = "wallet-tick-reload"
2022-12-01 09:19:32 +00:00
func NewReader(rpcClient *rpc.Client, tokenManager *token.Manager, accountsDB *accounts.Database, walletFeed *event.Feed) *Reader {
return &Reader{rpcClient, tokenManager, accountsDB, walletFeed, nil}
2022-05-10 07:48:05 +00:00
}
type Reader struct {
2022-12-01 09:19:32 +00:00
rpcClient *rpc.Client
tokenManager *token.Manager
accountsDB *accounts.Database
walletFeed *event.Feed
cancel context.CancelFunc
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
type ChainBalance struct {
Balance *big.Float `json:"balance"`
Address common.Address `json:"address"`
ChainID uint64 `json:"chainId"`
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
type Token struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Color string `json:"color"`
Decimals uint `json:"decimals"`
BalancesPerChain map[uint64]ChainBalance `json:"balancesPerChain"`
Description string `json:"description"`
AssetWebsiteURL string `json:"assetWebsiteUrl"`
BuiltOn string `json:"builtOn"`
MarketCap string `json:"marketCap"`
HighDay string `json:"highDay"`
LowDay string `json:"lowDay"`
ChangePctHour string `json:"changePctHour"`
ChangePctDay string `json:"changePctDay"`
ChangePct24hour string `json:"changePct24hour"`
Change24hour string `json:"change24hour"`
CurrencyPrice float64 `json:"currencyPrice"`
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
func getTokenBySymbols(tokens []*token.Token) map[string][]*token.Token {
res := make(map[string][]*token.Token)
2022-05-10 07:48:05 +00:00
2022-10-27 07:38:05 +00:00
for _, t := range tokens {
if _, ok := res[t.Symbol]; !ok {
res[t.Symbol] = make([]*token.Token, 0)
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
res[t.Symbol] = append(res[t.Symbol], t)
}
return res
}
func getTokenSymbols(tokens []*token.Token) []string {
tokensBySymbols := getTokenBySymbols(tokens)
res := make([]string, 0)
for symbol := range tokensBySymbols {
res = append(res, symbol)
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
return res
}
func getTokenAddresses(tokens []*token.Token) []common.Address {
set := make(map[common.Address]bool)
for _, token := range tokens {
set[token.Address] = true
}
res := make([]common.Address, 0)
for address := range set {
res = append(res, address)
}
return res
2022-05-10 07:48:05 +00:00
}
2022-12-01 09:19:32 +00:00
func (r *Reader) Start() error {
ctx, cancel := context.WithCancel(context.Background())
r.cancel = cancel
go func() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
r.walletFeed.Send(walletevent.Event{
Type: EventWalletTickReload,
})
}
}
}()
return nil
2022-05-10 07:48:05 +00:00
}
2022-12-01 09:19:32 +00:00
func (r *Reader) Stop() {
if r.cancel != nil {
r.cancel()
}
}
func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) (map[common.Address][]Token, error) {
2022-12-01 09:19:32 +00:00
networks, err := r.rpcClient.NetworkManager.Get(false)
2022-05-10 07:48:05 +00:00
if err != nil {
return nil, err
}
2022-10-27 07:38:05 +00:00
chainIDs := make([]uint64, 0)
for _, network := range networks {
chainIDs = append(chainIDs, network.ChainID)
2022-05-10 07:48:05 +00:00
}
2022-12-01 09:19:32 +00:00
currency, err := r.accountsDB.GetCurrency()
2022-05-10 07:48:05 +00:00
if err != nil {
return nil, err
}
2022-12-01 09:19:32 +00:00
allTokens, err := r.tokenManager.GetAllTokens()
2022-05-10 07:48:05 +00:00
if err != nil {
return nil, err
}
2022-10-27 07:38:05 +00:00
for _, network := range networks {
2022-12-01 09:19:32 +00:00
allTokens = append(allTokens, r.tokenManager.ToToken(network))
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
tokenSymbols := getTokenSymbols(allTokens)
tokenAddresses := getTokenAddresses(allTokens)
var (
group = async.NewAtomicGroup(ctx)
prices = map[string]float64{}
2022-10-27 07:38:05 +00:00
tokenDetails = map[string]Coin{}
tokenMarketValues = map[string]MarketCoinValues{}
2022-10-27 07:38:05 +00:00
balances = map[uint64]map[common.Address]map[common.Address]*hexutil.Big{}
)
2022-05-10 07:48:05 +00:00
2022-10-27 07:38:05 +00:00
group.Add(func(parent context.Context) error {
prices, err = fetchCryptoComparePrices(tokenSymbols, currency)
2022-10-27 07:38:05 +00:00
if err != nil {
return err
}
return nil
})
2022-05-10 07:48:05 +00:00
2022-10-27 07:38:05 +00:00
group.Add(func(parent context.Context) error {
tokenDetails, err = fetchCryptoCompareTokenDetails(tokenSymbols)
if err != nil {
return err
}
return nil
})
2022-05-10 07:48:05 +00:00
2022-10-27 07:38:05 +00:00
group.Add(func(parent context.Context) error {
tokenMarketValues, err = fetchTokenMarketValues(tokenSymbols, currency)
2022-05-10 07:48:05 +00:00
if err != nil {
2022-10-27 07:38:05 +00:00
return err
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
return nil
})
2022-05-10 07:48:05 +00:00
2022-10-27 07:38:05 +00:00
group.Add(func(parent context.Context) error {
2022-12-01 09:19:32 +00:00
clients, err := chain.NewClients(r.rpcClient, chainIDs)
2022-05-10 07:48:05 +00:00
if err != nil {
2022-10-27 07:38:05 +00:00
return err
2022-05-10 07:48:05 +00:00
}
balances, err = r.tokenManager.GetBalancesByChain(ctx, clients, addresses, tokenAddresses)
2022-10-27 07:38:05 +00:00
if err != nil {
return err
}
return nil
})
select {
case <-group.WaitAsync():
case <-ctx.Done():
return nil, ctx.Err()
}
err = group.Error()
2022-05-10 07:48:05 +00:00
if err != nil {
return nil, err
}
2022-10-27 07:38:05 +00:00
result := make(map[common.Address][]Token)
for _, address := range addresses {
2022-10-27 07:38:05 +00:00
for symbol, tokens := range getTokenBySymbols(allTokens) {
balancesPerChain := make(map[uint64]ChainBalance)
decimals := tokens[0].Decimals
for _, token := range tokens {
hexBalance := balances[token.ChainID][address][token.Address]
2022-10-27 07:38:05 +00:00
balance := big.NewFloat(0.0)
if hexBalance != nil {
balance = new(big.Float).Quo(
new(big.Float).SetInt(hexBalance.ToInt()),
big.NewFloat(math.Pow(10, float64(decimals))),
)
}
balancesPerChain[token.ChainID] = ChainBalance{
Balance: balance,
Address: token.Address,
ChainID: token.ChainID,
}
}
walletToken := Token{
Name: tokens[0].Name,
Color: tokens[0].Color,
Symbol: symbol,
BalancesPerChain: balancesPerChain,
Decimals: decimals,
Description: tokenDetails[symbol].Description,
AssetWebsiteURL: tokenDetails[symbol].AssetWebsiteURL,
BuiltOn: tokenDetails[symbol].BuiltOn,
MarketCap: tokenMarketValues[symbol].MKTCAP,
HighDay: tokenMarketValues[symbol].HIGHDAY,
LowDay: tokenMarketValues[symbol].LOWDAY,
ChangePctHour: tokenMarketValues[symbol].CHANGEPCTHOUR,
ChangePctDay: tokenMarketValues[symbol].CHANGEPCTDAY,
ChangePct24hour: tokenMarketValues[symbol].CHANGEPCT24HOUR,
Change24hour: tokenMarketValues[symbol].CHANGE24HOUR,
CurrencyPrice: prices[symbol],
2022-10-27 07:38:05 +00:00
}
result[address] = append(result[address], walletToken)
2022-05-10 07:48:05 +00:00
}
}
2022-10-27 07:38:05 +00:00
return result, nil
2022-05-10 07:48:05 +00:00
}