feat: get wallet api (#2619)
This commit is contained in:
parent
017ae1e205
commit
e199ddbe9d
|
@ -510,6 +510,10 @@ func (db *Database) GetPreferredUsername() (string, error) {
|
|||
return db.makeSelectString(PreferredName)
|
||||
}
|
||||
|
||||
func (db *Database) GetCurrency() (string, error) {
|
||||
return db.makeSelectString(Currency)
|
||||
}
|
||||
|
||||
func (db *Database) GetInstalledStickerPacks() (rst *json.RawMessage, err error) {
|
||||
err = db.makeSelectRow(StickersPacksInstalled).Scan(&rst)
|
||||
return
|
||||
|
|
|
@ -124,7 +124,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig) error {
|
|||
if len(openseaKey) == 0 {
|
||||
openseaKey = OpenseaKeyFromEnv
|
||||
}
|
||||
walletService := b.walletService(accountsFeed, openseaKey)
|
||||
walletService := b.walletService(accDB, accountsFeed, openseaKey)
|
||||
services = append(services, walletService)
|
||||
}
|
||||
|
||||
|
@ -441,9 +441,9 @@ func (b *StatusNode) appmetricsService() common.StatusService {
|
|||
return b.appMetricsSrvc
|
||||
}
|
||||
|
||||
func (b *StatusNode) walletService(accountsFeed *event.Feed, openseaAPIKey string) 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, b.rpcClient, accountsFeed, openseaAPIKey)
|
||||
b.walletSrvc = wallet.NewService(b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey)
|
||||
}
|
||||
return b.walletSrvc
|
||||
}
|
||||
|
|
|
@ -12,12 +12,22 @@ import (
|
|||
)
|
||||
|
||||
func NewAPI(s *Service) *API {
|
||||
return &API{s}
|
||||
r := NewReader(s)
|
||||
return &API{s, r}
|
||||
}
|
||||
|
||||
// API is class with methods available over RPC.
|
||||
type API struct {
|
||||
s *Service
|
||||
r *Reader
|
||||
}
|
||||
|
||||
func (api *API) StartWallet(ctx context.Context, chainIDs []uint64) error {
|
||||
return api.r.Start(ctx, chainIDs)
|
||||
}
|
||||
|
||||
func (api *API) GetWallet(ctx context.Context, chainIDs []uint64) (*Wallet, error) {
|
||||
return api.r.GetWallet(ctx, chainIDs)
|
||||
}
|
||||
|
||||
// SetInitialBlocksRange sets initial blocks range
|
||||
|
@ -139,7 +149,7 @@ func (api *API) DeleteCustomTokenByChainID(ctx context.Context, chainID uint64,
|
|||
return err
|
||||
}
|
||||
|
||||
func (api *API) GetSavedAddresses(ctx context.Context) ([]*SavedAddress, error) {
|
||||
func (api *API) GetSavedAddresses(ctx context.Context) ([]SavedAddress, error) {
|
||||
log.Debug("call to get saved addresses")
|
||||
rst, err := api.s.savedAddressesManager.GetSavedAddresses(api.s.rpcClient.UpstreamChainID)
|
||||
log.Debug("result from database for saved addresses", "len", len(rst))
|
||||
|
@ -231,7 +241,7 @@ func (api *API) WatchTransactionByChainID(ctx context.Context, chainID uint64, t
|
|||
return api.s.transactionManager.watch(ctx, transactionHash, chainClient)
|
||||
}
|
||||
|
||||
func (api *API) GetFavourites(ctx context.Context) ([]*Favourite, error) {
|
||||
func (api *API) GetFavourites(ctx context.Context) ([]Favourite, error) {
|
||||
log.Debug("call to get favourites")
|
||||
rst, err := api.s.favouriteManager.GetFavourites()
|
||||
log.Debug("result from database for favourites", "len", len(rst))
|
||||
|
|
|
@ -15,16 +15,16 @@ type FavouriteManager struct {
|
|||
db *sql.DB
|
||||
}
|
||||
|
||||
func (fm *FavouriteManager) GetFavourites() ([]*Favourite, error) {
|
||||
func (fm *FavouriteManager) GetFavourites() ([]Favourite, error) {
|
||||
rows, err := fm.db.Query(`SELECT address, name FROM favourites`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var rst []*Favourite
|
||||
var rst []Favourite
|
||||
for rows.Next() {
|
||||
favourite := &Favourite{}
|
||||
favourite := Favourite{}
|
||||
err := rows.Scan(&favourite.Address, &favourite.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/services/wallet/chain"
|
||||
"github.com/status-im/status-go/services/wallet/transfer"
|
||||
)
|
||||
|
||||
func NewReader(s *Service) *Reader {
|
||||
return &Reader{s}
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
type ReaderToken struct {
|
||||
Token *Token `json:"token"`
|
||||
OraclePrice float64 `json:"oraclePrice"`
|
||||
CryptoBalance *hexutil.Big `json:"cryptoBalance"`
|
||||
FiatBalance *big.Float `json:"fiatBalance"`
|
||||
}
|
||||
|
||||
type ReaderAccount struct {
|
||||
Account *accounts.Account `json:"account"`
|
||||
Collections map[uint64][]OpenseaCollection `json:"collections"`
|
||||
Tokens map[uint64][]ReaderToken `json:"tokens"`
|
||||
Transactions map[uint64][]transfer.View `json:"transactions"`
|
||||
|
||||
FiatBalance *big.Float `json:"fiatBalance"`
|
||||
}
|
||||
|
||||
type Wallet struct {
|
||||
Accounts []ReaderAccount `json:"accounts"`
|
||||
Favorites []Favourite `json:"favorites"`
|
||||
OnRamp []CryptoOnRamp `json:"onRamp"`
|
||||
SavedAddresses map[uint64][]SavedAddress `json:"savedAddresses"`
|
||||
Tokens map[uint64][]*Token `json:"tokens"`
|
||||
CustomTokens []*Token `json:"customTokens"`
|
||||
PendingTransactions map[uint64][]*PendingTransaction `json:"pendingTransactions"`
|
||||
|
||||
FiatBalance *big.Float `json:"fiatBalance"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
func getAddresses(accounts []accounts.Account) []common.Address {
|
||||
addresses := make([]common.Address, len(accounts))
|
||||
for _, account := range accounts {
|
||||
addresses = append(addresses, common.Address(account.Address))
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (r *Reader) buildReaderAccount(
|
||||
ctx context.Context,
|
||||
chainIDs []uint64,
|
||||
account accounts.Account,
|
||||
visibleTokens map[uint64][]*Token,
|
||||
prices map[string]float64,
|
||||
balances map[common.Address]*hexutil.Big,
|
||||
) (ReaderAccount, error) {
|
||||
limit := (*hexutil.Big)(big.NewInt(20))
|
||||
toBlock := (*hexutil.Big)(big.NewInt(0))
|
||||
|
||||
collections := make(map[uint64][]OpenseaCollection)
|
||||
tokens := make(map[uint64][]ReaderToken)
|
||||
transactions := make(map[uint64][]transfer.View)
|
||||
accountFiatBalance := big.NewFloat(0)
|
||||
for _, chainID := range chainIDs {
|
||||
client, err := newOpenseaClient(chainID, r.s.openseaAPIKey)
|
||||
if err == nil {
|
||||
c, _ := client.fetchAllCollectionsByOwner(common.Address(account.Address))
|
||||
collections[chainID] = c
|
||||
}
|
||||
|
||||
for _, token := range visibleTokens[chainID] {
|
||||
oraclePrice := prices[token.Symbol]
|
||||
cryptoBalance := balances[token.Address].ToInt()
|
||||
fiatBalance := big.NewFloat(0).Mul(big.NewFloat(oraclePrice), new(big.Float).SetInt(cryptoBalance))
|
||||
tokens[chainID] = append(tokens[chainID], ReaderToken{
|
||||
Token: token,
|
||||
OraclePrice: oraclePrice,
|
||||
CryptoBalance: balances[token.Address],
|
||||
FiatBalance: fiatBalance,
|
||||
})
|
||||
accountFiatBalance = accountFiatBalance.Add(accountFiatBalance, fiatBalance)
|
||||
}
|
||||
t, err := r.s.transferController.GetTransfersByAddress(ctx, chainID, common.Address(account.Address), toBlock, limit, false)
|
||||
if err == nil {
|
||||
transactions[chainID] = t
|
||||
}
|
||||
}
|
||||
return ReaderAccount{
|
||||
Account: &account,
|
||||
Collections: collections,
|
||||
Tokens: tokens,
|
||||
Transactions: transactions,
|
||||
FiatBalance: accountFiatBalance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Start(ctx context.Context, chainIDs []uint64) error {
|
||||
accounts, err := r.s.accountsDB.GetAccounts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.s.transferController.CheckRecentHistory(chainIDs, getAddresses(accounts))
|
||||
}
|
||||
|
||||
func (r *Reader) GetWallet(ctx context.Context, chainIDs []uint64) (*Wallet, error) {
|
||||
currency, err := r.s.accountsDB.GetCurrency()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokensMap := make(map[uint64][]*Token)
|
||||
for _, chainID := range chainIDs {
|
||||
tokens, err := r.s.tokenManager.getTokens(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokensMap[chainID] = tokens
|
||||
}
|
||||
|
||||
customTokens, err := r.s.tokenManager.getCustoms()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
visibleTokens, err := r.s.tokenManager.getVisible(chainIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenAddresses := make([]common.Address, 0)
|
||||
tokenSymbols := make([]string, 0)
|
||||
for _, tokens := range visibleTokens {
|
||||
for _, token := range tokens {
|
||||
tokenAddresses = append(tokenAddresses, token.Address)
|
||||
tokenSymbols = append(tokenSymbols, token.Symbol)
|
||||
}
|
||||
}
|
||||
|
||||
accounts, err := r.s.accountsDB.GetAccounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prices, err := fetchCryptoComparePrices(tokenSymbols, currency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clients, err := chain.NewClients(r.s.rpcClient, chainIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
balances, err := r.s.tokenManager.getBalances(ctx, clients, getAddresses(accounts), tokenAddresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
readerAccounts := make([]ReaderAccount, len(accounts))
|
||||
walletFiatBalance := big.NewFloat(0)
|
||||
for i, account := range accounts {
|
||||
readerAccount, err := r.buildReaderAccount(
|
||||
ctx,
|
||||
chainIDs,
|
||||
account,
|
||||
visibleTokens,
|
||||
prices,
|
||||
balances[common.Address(account.Address)],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
walletFiatBalance = walletFiatBalance.Add(walletFiatBalance, readerAccount.FiatBalance)
|
||||
readerAccounts[i] = readerAccount
|
||||
}
|
||||
|
||||
savedAddressesMap := make(map[uint64][]SavedAddress)
|
||||
for _, chainID := range chainIDs {
|
||||
savedAddresses, err := r.s.savedAddressesManager.GetSavedAddresses(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
savedAddressesMap[chainID] = savedAddresses
|
||||
}
|
||||
|
||||
onRamp, err := r.s.cryptoOnRampManager.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
favorites, err := r.s.favouriteManager.GetFavourites()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pendingTransactions := make(map[uint64][]*PendingTransaction)
|
||||
for _, chainID := range chainIDs {
|
||||
pendingTx, err := r.s.transactionManager.getAllPendings(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pendingTransactions[chainID] = pendingTx
|
||||
}
|
||||
return &Wallet{
|
||||
Accounts: readerAccounts,
|
||||
Favorites: favorites,
|
||||
OnRamp: onRamp,
|
||||
SavedAddresses: savedAddressesMap,
|
||||
Tokens: tokensMap,
|
||||
CustomTokens: customTokens,
|
||||
PendingTransactions: pendingTransactions,
|
||||
Currency: currency,
|
||||
FiatBalance: walletFiatBalance,
|
||||
}, nil
|
||||
}
|
|
@ -18,16 +18,16 @@ type SavedAddressesManager struct {
|
|||
db *sql.DB
|
||||
}
|
||||
|
||||
func (sam *SavedAddressesManager) GetSavedAddresses(chainID uint64) ([]*SavedAddress, error) {
|
||||
func (sam *SavedAddressesManager) GetSavedAddresses(chainID uint64) ([]SavedAddress, error) {
|
||||
rows, err := sam.db.Query("SELECT address, name, network_id FROM saved_addresses WHERE network_id = ?", chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var rst []*SavedAddress
|
||||
var rst []SavedAddress
|
||||
for rows.Next() {
|
||||
sa := &SavedAddress{}
|
||||
sa := SavedAddress{}
|
||||
err := rows.Scan(&sa.Address, &sa.Name, &sa.ChainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -43,7 +43,7 @@ func TestSavedAddresses(t *testing.T) {
|
|||
rst, err = manager.GetSavedAddresses(777)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(rst))
|
||||
require.Equal(t, sa, *rst[0])
|
||||
require.Equal(t, sa, rst[0])
|
||||
|
||||
err = manager.DeleteSavedAddress(777, sa.Address)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -8,12 +8,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"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, 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) *Service {
|
||||
cryptoOnRampManager := NewCryptoOnRampManager(&CryptoOnRampOptions{
|
||||
dataSourceType: DataSourceStatic,
|
||||
})
|
||||
|
@ -24,6 +25,8 @@ func NewService(db *sql.DB, rpcClient *rpc.Client, accountFeed *event.Feed, open
|
|||
transferController := transfer.NewTransferController(db, rpcClient, accountFeed)
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
accountsDB: accountsDB,
|
||||
rpcClient: rpcClient,
|
||||
favouriteManager: favouriteManager,
|
||||
tokenManager: tokenManager,
|
||||
|
@ -38,6 +41,8 @@ func NewService(db *sql.DB, rpcClient *rpc.Client, accountFeed *event.Feed, open
|
|||
|
||||
// Service is a wallet service.
|
||||
type Service struct {
|
||||
db *sql.DB
|
||||
accountsDB *accounts.Database
|
||||
rpcClient *rpc.Client
|
||||
savedAddressesManager *SavedAddressesManager
|
||||
tokenManager *TokenManager
|
||||
|
|
|
@ -242,6 +242,21 @@ func (tm *TokenManager) deleteCustom(chainID uint64, address common.Address) err
|
|||
return err
|
||||
}
|
||||
|
||||
func (tm *TokenManager) getTokenBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) {
|
||||
caller, err := ierc20.NewIERC20Caller(token, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return caller.BalanceOf(&bind.CallOpts{
|
||||
Context: ctx,
|
||||
}, account)
|
||||
}
|
||||
|
||||
func (tm *TokenManager) getChainBalance(ctx context.Context, client *chain.Client, account common.Address) (*big.Int, error) {
|
||||
return client.BalanceAt(ctx, account, nil)
|
||||
}
|
||||
|
||||
func (tm *TokenManager) getBalances(parent context.Context, clients []*chain.Client, accounts, tokens []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
|
||||
var (
|
||||
group = async.NewAtomicGroup(parent)
|
||||
|
@ -250,10 +265,6 @@ func (tm *TokenManager) getBalances(parent context.Context, clients []*chain.Cli
|
|||
)
|
||||
for _, client := range clients {
|
||||
for tokenIdx := range tokens {
|
||||
caller, err := ierc20.NewIERC20Caller(tokens[tokenIdx], client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for accountIdx := range accounts {
|
||||
// Below, we set account and token from idx on purpose to avoid override
|
||||
account := accounts[accountIdx]
|
||||
|
@ -261,9 +272,14 @@ func (tm *TokenManager) getBalances(parent context.Context, clients []*chain.Cli
|
|||
group.Add(func(parent context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(parent, requestTimeout)
|
||||
defer cancel()
|
||||
balance, err := caller.BalanceOf(&bind.CallOpts{
|
||||
Context: ctx,
|
||||
}, account)
|
||||
var balance *big.Int
|
||||
var err error
|
||||
if token == common.HexToAddress("0x") {
|
||||
balance, err = tm.getChainBalance(ctx, client, account)
|
||||
} else {
|
||||
balance, err = tm.getTokenBalance(ctx, client, account, token)
|
||||
}
|
||||
|
||||
// We don't want to return an error here and prevent
|
||||
// the rest from completing
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue