2022-09-13 07:10:59 +00:00
package token
2021-09-09 14:28:54 +00:00
import (
"context"
"database/sql"
"math/big"
2023-03-14 17:33:05 +00:00
"strconv"
2023-10-25 16:49:18 +00:00
"strings"
2021-09-09 14:28:54 +00:00
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
2022-09-09 06:58:36 +00:00
"github.com/status-im/status-go/contracts"
2023-10-25 16:49:18 +00:00
"github.com/status-im/status-go/contracts/community-tokens/assets"
2023-09-20 08:41:23 +00:00
"github.com/status-im/status-go/contracts/ethscan"
2022-02-02 22:50:55 +00:00
"github.com/status-im/status-go/contracts/ierc20"
2023-10-25 16:49:18 +00:00
eth_node_types "github.com/status-im/status-go/eth-node/types"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/params"
2022-04-04 16:54:44 +00:00
"github.com/status-im/status-go/rpc"
2023-02-20 09:32:45 +00:00
"github.com/status-im/status-go/rpc/chain"
2022-04-13 07:55:38 +00:00
"github.com/status-im/status-go/rpc/network"
2023-10-25 16:49:18 +00:00
"github.com/status-im/status-go/services/utils"
2021-09-09 14:28:54 +00:00
"github.com/status-im/status-go/services/wallet/async"
)
var requestTimeout = 20 * time . Second
2022-09-09 06:58:36 +00:00
var nativeChainAddress = common . HexToAddress ( "0x" )
2021-09-09 14:28:54 +00:00
type Token struct {
Address common . Address ` json:"address" `
Name string ` json:"name" `
Symbol string ` json:"symbol" `
// Decimals defines how divisible the token is. For example, 0 would be
// indivisible, whereas 18 would allow very small amounts of the token
// to be traded.
Decimals uint ` json:"decimals" `
ChainID uint64 ` json:"chainId" `
2023-01-12 17:54:14 +00:00
// PegSymbol indicates that the token is pegged to some fiat currency, using the
// ISO 4217 alphabetic code. For example, an empty string means it is not
// pegged, while "USD" means it's pegged to the United States Dollar.
PegSymbol string ` json:"pegSymbol" `
2023-08-31 07:47:24 +00:00
2023-10-25 16:49:18 +00:00
CommunityID * string ` json:"communityId,omitempty" `
2023-10-17 15:05:05 +00:00
Verified bool ` json:"verified" `
TokenListID string ` json:"tokenListId" `
2021-09-09 14:28:54 +00:00
}
2022-09-13 07:10:59 +00:00
func ( t * Token ) IsNative ( ) bool {
return t . Address == nativeChainAddress
}
2023-10-17 15:05:05 +00:00
type List struct {
Name string ` json:"name" `
Tokens [ ] * Token ` json:"tokens" `
UpdatedAt int64 ` json:"updatedAt" `
Source string ` json:"source" `
Version string ` json:"version" `
}
type addressTokenMap = map [ common . Address ] * Token
type storeMap = map [ uint64 ] addressTokenMap
2023-08-11 17:28:46 +00:00
type ManagerInterface interface {
LookupTokenIdentity ( chainID uint64 , address common . Address , native bool ) * Token
LookupToken ( chainID * uint64 , tokenSymbol string ) ( token * Token , isNative bool )
}
2023-02-06 17:05:58 +00:00
// Manager is used for accessing token store. It changes the token store based on overridden tokens
2022-10-25 14:50:32 +00:00
type Manager struct {
2023-09-11 14:44:43 +00:00
db * sql . DB
RPCClient * rpc . Client
contractMaker * contracts . ContractMaker
networkManager * network . Manager
stores [ ] store // Set on init, not changed afterwards
2023-10-17 15:05:05 +00:00
tokens [ ] * Token
2023-09-11 14:44:43 +00:00
tokenLock sync . RWMutex
2022-04-13 07:55:38 +00:00
}
2023-10-17 15:05:05 +00:00
func mergeTokens ( sliceLists [ ] [ ] * Token ) [ ] * Token {
2023-03-14 17:33:05 +00:00
allKeys := make ( map [ string ] bool )
res := [ ] * Token { }
for _ , list := range sliceLists {
for _ , token := range list {
key := strconv . FormatUint ( token . ChainID , 10 ) + token . Address . String ( )
if _ , value := allKeys [ key ] ; ! value {
allKeys [ key ] = true
res = append ( res , token )
}
2023-02-06 17:05:58 +00:00
}
}
2023-03-14 17:33:05 +00:00
return res
2022-09-13 09:30:52 +00:00
}
2023-10-17 15:05:05 +00:00
func NewTokenManager (
db * sql . DB ,
RPCClient * rpc . Client ,
networkManager * network . Manager ,
) * Manager {
maker , _ := contracts . NewContractMaker ( RPCClient )
stores := [ ] store { newUniswapStore ( ) , newDefaultStore ( ) }
tokens := make ( [ ] * Token , 0 )
2023-03-15 15:10:36 +00:00
2023-10-17 15:05:05 +00:00
networks , err := networkManager . GetAll ( )
2023-03-28 13:15:34 +00:00
if err != nil {
2023-10-17 15:05:05 +00:00
return nil
2023-03-28 13:15:34 +00:00
}
2023-10-17 15:05:05 +00:00
for _ , store := range stores {
2023-03-28 13:15:34 +00:00
validTokens := make ( [ ] * Token , 0 )
2023-10-17 15:05:05 +00:00
for _ , token := range store . GetTokens ( ) {
2023-08-31 07:47:24 +00:00
token . Verified = true
2023-03-28 13:15:34 +00:00
for _ , network := range networks {
if network . ChainID == token . ChainID {
validTokens = append ( validTokens , token )
break
}
}
}
2023-03-14 17:33:05 +00:00
2023-10-17 15:05:05 +00:00
tokens = mergeTokens ( [ ] [ ] * Token { tokens , validTokens } )
2023-03-14 17:33:05 +00:00
}
2023-09-11 14:44:43 +00:00
2023-10-17 15:05:05 +00:00
return & Manager {
db : db ,
RPCClient : RPCClient ,
contractMaker : maker ,
networkManager : networkManager ,
stores : stores ,
tokens : tokens ,
}
2023-03-14 17:33:05 +00:00
}
2023-10-17 15:05:05 +00:00
// overrideTokensInPlace overrides tokens in the store with the ones from the networks
// BEWARE: overridden tokens will have their original address removed and replaced by the one in networks
func overrideTokensInPlace ( networks [ ] params . Network , tokens [ ] * Token ) {
for _ , network := range networks {
if len ( network . TokenOverrides ) == 0 {
continue
}
2023-06-02 20:07:42 +00:00
2023-10-17 15:05:05 +00:00
for _ , overrideToken := range network . TokenOverrides {
for _ , token := range tokens {
if token . Symbol == overrideToken . Symbol {
token . Address = overrideToken . Address
}
}
}
2022-09-13 07:10:59 +00:00
}
2023-10-17 15:05:05 +00:00
}
func ( tm * Manager ) getTokens ( ) [ ] * Token {
tm . tokenLock . RLock ( )
defer tm . tokenLock . RUnlock ( )
return tm . tokens
}
2023-06-02 20:07:42 +00:00
2023-10-17 15:05:05 +00:00
func ( tm * Manager ) SetTokens ( tokens [ ] * Token ) {
tm . tokenLock . Lock ( )
defer tm . tokenLock . Unlock ( )
tm . tokens = tokens
2023-06-02 20:07:42 +00:00
}
func ( tm * Manager ) FindToken ( network * params . Network , tokenSymbol string ) * Token {
if tokenSymbol == network . NativeCurrencySymbol {
return tm . ToToken ( network )
}
2023-06-13 09:25:23 +00:00
return tm . GetToken ( network . ChainID , tokenSymbol )
}
func ( tm * Manager ) LookupToken ( chainID * uint64 , tokenSymbol string ) ( token * Token , isNative bool ) {
if chainID == nil {
2023-10-17 15:05:05 +00:00
networks , err := tm . networkManager . Get ( false )
2023-06-13 09:25:23 +00:00
if err != nil {
return nil , false
}
for _ , network := range networks {
if tokenSymbol == network . NativeCurrencySymbol {
return tm . ToToken ( network ) , true
}
token := tm . GetToken ( network . ChainID , tokenSymbol )
if token != nil {
return token , false
}
}
} else {
network := tm . networkManager . Find ( * chainID )
2023-07-20 14:04:30 +00:00
if network != nil && tokenSymbol == network . NativeCurrencySymbol {
2023-06-13 09:25:23 +00:00
return tm . ToToken ( network ) , true
}
return tm . GetToken ( * chainID , tokenSymbol ) , false
}
return nil , false
}
// GetToken returns token by chainID and tokenSymbol. Use ToToken for native token
func ( tm * Manager ) GetToken ( chainID uint64 , tokenSymbol string ) * Token {
2023-10-17 15:05:05 +00:00
allTokens , err := tm . GetTokens ( chainID )
if err != nil {
return nil
}
2022-09-13 07:10:59 +00:00
for _ , token := range allTokens {
if token . Symbol == tokenSymbol {
return token
}
}
return nil
}
2023-06-13 09:25:23 +00:00
func ( tm * Manager ) LookupTokenIdentity ( chainID uint64 , address common . Address , native bool ) * Token {
network := tm . networkManager . Find ( chainID )
if native {
return tm . ToToken ( network )
}
return tm . FindTokenByAddress ( chainID , address )
}
2023-06-02 20:07:42 +00:00
func ( tm * Manager ) FindTokenByAddress ( chainID uint64 , address common . Address ) * Token {
2023-10-17 15:05:05 +00:00
allTokens , err := tm . GetTokens ( chainID )
if err != nil {
return nil
}
2023-06-02 20:07:42 +00:00
for _ , token := range allTokens {
if token . Address == address {
return token
}
}
2023-08-31 07:47:24 +00:00
2023-06-02 20:07:42 +00:00
return nil
}
2023-08-31 07:47:24 +00:00
func ( tm * Manager ) FindOrCreateTokenByAddress ( ctx context . Context , chainID uint64 , address common . Address ) * Token {
2023-10-17 15:05:05 +00:00
// If token comes datasource, simply returns it
for _ , token := range tm . getTokens ( ) {
if token . ChainID != chainID {
continue
}
if token . Address == address {
return token
}
}
// Create custom token if not known or try to link with a community
customTokens , err := tm . GetCustoms ( false )
if err != nil {
return nil
}
for _ , token := range customTokens {
2023-08-31 07:47:24 +00:00
if token . Address == address {
2023-11-17 17:30:06 +00:00
tm . discoverTokenCommunityID ( ctx , token , address )
2023-08-31 07:47:24 +00:00
return token
}
}
token , err := tm . DiscoverToken ( ctx , chainID , address )
if err != nil {
return nil
}
err = tm . UpsertCustom ( * token )
if err != nil {
return nil
}
2023-11-17 17:30:06 +00:00
tm . discoverTokenCommunityID ( ctx , token , address )
2023-08-31 07:47:24 +00:00
return token
}
2023-10-25 16:49:18 +00:00
func ( tm * Manager ) discoverTokenCommunityID ( ctx context . Context , token * Token , address common . Address ) {
2023-11-02 08:11:48 +00:00
if token == nil || token . CommunityID != nil {
2023-10-25 16:49:18 +00:00
// Token is invalid or is alrady discovered. Nothing to do here.
return
}
backend , err := tm . RPCClient . EthClient ( token . ChainID )
if err != nil {
return
}
caller , err := assets . NewAssetsCaller ( address , backend )
if err != nil {
return
}
uri , err := caller . BaseTokenURI ( & bind . CallOpts {
Context : ctx ,
} )
if err != nil {
return
}
update , err := tm . db . Prepare ( "UPDATE tokens SET community_id=? WHERE network_id=? AND address=?" )
if err != nil {
log . Error ( "Cannot prepare token update query" , err )
return
}
if uri == "" {
// Update token community ID to prevent further checks
_ , err := update . Exec ( "" , token . ChainID , token . Address )
if err != nil {
log . Error ( "Cannot update community id" , err )
}
return
}
uri = strings . TrimSuffix ( uri , "/" )
communityIDHex , err := utils . DeserializePublicKey ( uri )
if err != nil {
return
}
communityID := eth_node_types . EncodeHex ( communityIDHex )
_ , err = update . Exec ( communityID , token . ChainID , token . Address )
if err != nil {
log . Error ( "Cannot update community id" , err )
}
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) FindSNT ( chainID uint64 ) * Token {
2023-10-17 15:05:05 +00:00
tokens , err := tm . GetTokens ( chainID )
2023-03-14 17:33:05 +00:00
if err != nil {
2022-04-13 07:55:38 +00:00
return nil
}
2023-03-14 17:33:05 +00:00
for _ , token := range tokens {
2022-04-13 07:55:38 +00:00
if token . Symbol == "SNT" || token . Symbol == "STT" {
return token
}
}
return nil
2021-09-09 14:28:54 +00:00
}
2023-10-17 15:05:05 +00:00
func ( tm * Manager ) getNativeTokens ( ) ( [ ] * Token , error ) {
tokens := make ( [ ] * Token , 0 )
2023-08-31 07:47:24 +00:00
networks , err := tm . networkManager . Get ( false )
2023-02-17 14:11:07 +00:00
if err != nil {
return nil , err
}
for _ , network := range networks {
2023-10-17 15:05:05 +00:00
tokens = append ( tokens , tm . ToToken ( network ) )
2023-02-17 14:11:07 +00:00
}
2023-10-17 15:05:05 +00:00
return tokens , nil
2023-02-17 14:11:07 +00:00
}
2022-10-27 07:38:05 +00:00
func ( tm * Manager ) GetAllTokens ( ) ( [ ] * Token , error ) {
2023-10-17 15:05:05 +00:00
allTokens , err := tm . GetCustoms ( true )
2022-10-27 07:38:05 +00:00
if err != nil {
2023-05-26 08:41:33 +00:00
log . Error ( "can't fetch custom tokens" , "error" , err )
2022-10-27 07:38:05 +00:00
}
2023-01-13 17:12:46 +00:00
2023-10-17 15:05:05 +00:00
allTokens = append ( tm . getTokens ( ) , allTokens ... )
2022-10-27 07:38:05 +00:00
2023-10-17 15:05:05 +00:00
overrideTokensInPlace ( tm . networkManager . GetConfiguredNetworks ( ) , allTokens )
2023-03-14 17:33:05 +00:00
2023-10-17 15:05:05 +00:00
native , err := tm . getNativeTokens ( )
if err != nil {
return nil , err
}
allTokens = append ( allTokens , native ... )
return allTokens , nil
2022-10-27 07:38:05 +00:00
}
2023-10-17 15:05:05 +00:00
func ( tm * Manager ) GetTokens ( chainID uint64 ) ( [ ] * Token , error ) {
tokens , err := tm . GetAllTokens ( )
if err != nil {
return nil , err
2023-05-22 08:55:33 +00:00
}
2023-10-17 15:05:05 +00:00
res := make ( [ ] * Token , 0 )
for _ , token := range tokens {
if token . ChainID == chainID {
res = append ( res , token )
}
2023-03-15 15:10:36 +00:00
}
2023-10-17 15:05:05 +00:00
return res , nil
}
func ( tm * Manager ) GetTokensByChainIDs ( chainIDs [ ] uint64 ) ( [ ] * Token , error ) {
tokens , err := tm . GetAllTokens ( )
if err != nil {
return nil , err
2022-01-14 09:21:00 +00:00
}
2023-10-17 15:05:05 +00:00
res := make ( [ ] * Token , 0 )
2022-01-14 09:21:00 +00:00
2023-10-17 15:05:05 +00:00
for _ , token := range tokens {
for _ , chainID := range chainIDs {
if token . ChainID == chainID {
res = append ( res , token )
}
}
2022-01-14 09:21:00 +00:00
}
2023-10-17 15:05:05 +00:00
2023-11-03 12:08:32 +00:00
return res , nil
}
2023-10-17 15:05:05 +00:00
func ( tm * Manager ) GetList ( ) [ ] * List {
res := make ( [ ] * List , 0 )
nativeTokens , err := tm . getNativeTokens ( )
if err == nil {
res = append ( res , & List {
Name : "native" ,
Tokens : nativeTokens ,
UpdatedAt : time . Now ( ) . Unix ( ) ,
Source : "native" ,
Version : "1.0.0" ,
} )
2023-11-03 12:08:32 +00:00
}
2022-01-14 09:21:00 +00:00
2023-10-17 15:05:05 +00:00
customTokens , err := tm . GetCustoms ( true )
if err == nil && len ( customTokens ) > 0 {
res = append ( res , & List {
Name : "custom" ,
Tokens : customTokens ,
UpdatedAt : time . Now ( ) . Unix ( ) ,
Source : "custom" ,
Version : "1.0.0" ,
} )
2023-08-22 09:32:27 +00:00
}
2023-10-17 15:05:05 +00:00
for _ , store := range tm . stores {
res = append ( res , & List {
Name : store . GetName ( ) ,
Tokens : store . GetTokens ( ) ,
UpdatedAt : store . GetUpdatedAt ( ) ,
Source : store . GetSource ( ) ,
Version : store . GetVersion ( ) ,
} )
}
return res
2022-01-14 09:21:00 +00:00
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) DiscoverToken ( ctx context . Context , chainID uint64 , address common . Address ) ( * Token , error ) {
2023-08-31 07:47:24 +00:00
caller , err := tm . contractMaker . NewERC20 ( chainID , address )
2022-04-04 16:54:44 +00:00
if err != nil {
return nil , err
}
name , err := caller . Name ( & bind . CallOpts {
Context : ctx ,
} )
if err != nil {
return nil , err
}
symbol , err := caller . Symbol ( & bind . CallOpts {
Context : ctx ,
} )
if err != nil {
return nil , err
}
decimal , err := caller . Decimals ( & bind . CallOpts {
Context : ctx ,
} )
if err != nil {
return nil , err
}
return & Token {
Address : address ,
Name : name ,
Symbol : symbol ,
Decimals : uint ( decimal ) ,
2023-08-31 07:47:24 +00:00
ChainID : chainID ,
2022-04-04 16:54:44 +00:00
} , nil
}
2023-10-17 15:05:05 +00:00
func ( tm * Manager ) getTokensFromDB ( query string , args ... any ) ( [ ] * Token , error ) {
2023-10-25 16:49:18 +00:00
rows , err := tm . db . Query ( query , args ... )
2021-09-09 14:28:54 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
var rst [ ] * Token
for rows . Next ( ) {
token := & Token { }
2023-10-25 16:49:18 +00:00
var communityIDDB sql . NullString
2023-10-17 15:05:05 +00:00
err := rows . Scan ( & token . Address , & token . Name , & token . Symbol , & token . Decimals , & token . ChainID , & communityIDDB )
2021-09-09 14:28:54 +00:00
if err != nil {
return nil , err
}
2023-10-25 16:49:18 +00:00
if communityIDDB . Valid {
token . CommunityID = & communityIDDB . String
}
2021-09-09 14:28:54 +00:00
rst = append ( rst , token )
}
return rst , nil
}
2023-10-17 15:05:05 +00:00
func ( tm * Manager ) GetCustoms ( onlyCommunityCustoms bool ) ( [ ] * Token , error ) {
2023-11-03 12:08:32 +00:00
if onlyCommunityCustoms {
2023-10-17 15:05:05 +00:00
return tm . getTokensFromDB ( "SELECT address, name, symbol, decimals, network_id, community_id FROM tokens WHERE community_id IS NOT NULL AND community_id != ''" )
2022-04-13 07:55:38 +00:00
}
2023-10-17 15:05:05 +00:00
return tm . getTokensFromDB ( "SELECT address, name, symbol, decimals, network_id, community_id FROM tokens" )
2022-04-13 07:55:38 +00:00
}
2022-10-27 07:38:05 +00:00
func ( tm * Manager ) ToToken ( network * params . Network ) * Token {
return & Token {
Address : common . HexToAddress ( "0x" ) ,
Name : network . NativeCurrencyName ,
Symbol : network . NativeCurrencySymbol ,
Decimals : uint ( network . NativeCurrencyDecimals ) ,
ChainID : network . ChainID ,
2023-08-31 07:47:24 +00:00
Verified : true ,
2022-10-27 07:38:05 +00:00
}
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) UpsertCustom ( token Token ) error {
2023-10-17 15:05:05 +00:00
insert , err := tm . db . Prepare ( "INSERT OR REPLACE INTO TOKENS (network_id, address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)" )
2021-09-09 14:28:54 +00:00
if err != nil {
return err
}
2023-10-17 15:05:05 +00:00
_ , err = insert . Exec ( token . ChainID , token . Address , token . Name , token . Symbol , token . Decimals )
2021-09-09 14:28:54 +00:00
return err
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) DeleteCustom ( chainID uint64 , address common . Address ) error {
2021-09-09 14:28:54 +00:00
_ , err := tm . db . Exec ( ` DELETE FROM TOKENS WHERE address = ? and network_id = ? ` , address , chainID )
return err
}
2023-09-20 08:41:23 +00:00
func ( tm * Manager ) GetTokenBalance ( ctx context . Context , client chain . ClientInterface , account common . Address , token common . Address ) ( * big . Int , error ) {
2022-05-10 07:48:05 +00:00
caller , err := ierc20 . NewIERC20Caller ( token , client )
if err != nil {
return nil , err
}
return caller . BalanceOf ( & bind . CallOpts {
Context : ctx ,
} , account )
}
2023-09-20 08:41:23 +00:00
func ( tm * Manager ) GetTokenBalanceAt ( ctx context . Context , client chain . ClientInterface , account common . Address , token common . Address , blockNumber * big . Int ) ( * big . Int , error ) {
2022-11-15 12:14:41 +00:00
caller , err := ierc20 . NewIERC20Caller ( token , client )
if err != nil {
return nil , err
}
2023-09-20 08:41:23 +00:00
balance , err := caller . BalanceOf ( & bind . CallOpts {
2022-11-15 12:14:41 +00:00
Context : ctx ,
BlockNumber : blockNumber ,
} , account )
2023-09-20 08:41:23 +00:00
if err != nil {
if err != bind . ErrNoCode {
return nil , err
}
balance = big . NewInt ( 0 )
}
return balance , nil
2022-11-15 12:14:41 +00:00
}
2023-09-20 08:41:23 +00:00
func ( tm * Manager ) GetChainBalance ( ctx context . Context , client chain . ClientInterface , account common . Address ) ( * big . Int , error ) {
2022-05-10 07:48:05 +00:00
return client . BalanceAt ( ctx , account , nil )
}
2023-09-20 08:41:23 +00:00
func ( tm * Manager ) GetBalance ( ctx context . Context , client chain . ClientInterface , account common . Address , token common . Address ) ( * big . Int , error ) {
2022-09-09 06:58:36 +00:00
if token == nativeChainAddress {
2022-09-13 07:10:59 +00:00
return tm . GetChainBalance ( ctx , client , account )
2022-06-09 13:09:56 +00:00
}
2022-09-13 07:10:59 +00:00
return tm . GetTokenBalance ( ctx , client , account , token )
2022-06-09 13:09:56 +00:00
}
2023-09-20 08:41:23 +00:00
func ( tm * Manager ) GetBalancesByChain ( parent context . Context , clients map [ uint64 ] chain . ClientInterface , accounts , tokens [ ] common . Address ) ( map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big , error ) {
return tm . GetBalancesAtByChain ( parent , clients , accounts , tokens , nil )
}
func ( tm * Manager ) GetBalancesAtByChain ( parent context . Context , clients map [ uint64 ] chain . ClientInterface , accounts , tokens [ ] common . Address , atBlocks map [ uint64 ] * big . Int ) ( map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big , error ) {
2022-10-27 07:38:05 +00:00
var (
group = async . NewAtomicGroup ( parent )
mu sync . Mutex
response = map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big { }
)
updateBalance := func ( chainID uint64 , account common . Address , token common . Address , balance * big . Int ) {
mu . Lock ( )
if _ , ok := response [ chainID ] ; ! ok {
response [ chainID ] = map [ common . Address ] map [ common . Address ] * hexutil . Big { }
}
if _ , ok := response [ chainID ] [ account ] ; ! ok {
response [ chainID ] [ account ] = map [ common . Address ] * hexutil . Big { }
}
if _ , ok := response [ chainID ] [ account ] [ token ] ; ! ok {
zeroHex := hexutil . Big ( * big . NewInt ( 0 ) )
response [ chainID ] [ account ] [ token ] = & zeroHex
}
sum := big . NewInt ( 0 ) . Add ( response [ chainID ] [ account ] [ token ] . ToInt ( ) , balance )
sumHex := hexutil . Big ( * sum )
response [ chainID ] [ account ] [ token ] = & sumHex
mu . Unlock ( )
}
2023-10-05 11:55:28 +00:00
for clientIdx := range clients {
// Keep the reference to the client. DO NOT USE A LOOP, the client will be overridden in the coroutine
client := clients [ clientIdx ]
2023-09-20 08:41:23 +00:00
ethScanContract , availableAtBlock , err := tm . contractMaker . NewEthScan ( client . NetworkID ( ) )
2023-03-30 07:06:47 +00:00
if err != nil {
2023-04-25 12:00:17 +00:00
log . Error ( "error scanning contract" , "err" , err )
2023-03-30 07:06:47 +00:00
return nil , err
}
2022-10-27 07:38:05 +00:00
2023-09-20 08:41:23 +00:00
atBlock := atBlocks [ client . NetworkID ( ) ]
2023-03-30 07:06:47 +00:00
fetchChainBalance := false
var tokenChunks [ ] [ ] common . Address
chunkSize := 500
for i := 0 ; i < len ( tokens ) ; i += chunkSize {
end := i + chunkSize
if end > len ( tokens ) {
end = len ( tokens )
2022-10-27 07:38:05 +00:00
}
2023-03-30 07:06:47 +00:00
tokenChunks = append ( tokenChunks , tokens [ i : end ] )
}
for _ , token := range tokens {
if token == nativeChainAddress {
fetchChainBalance = true
2022-10-27 07:38:05 +00:00
}
2023-03-30 07:06:47 +00:00
}
if fetchChainBalance {
group . Add ( func ( parent context . Context ) error {
ctx , cancel := context . WithTimeout ( parent , requestTimeout )
defer cancel ( )
res , err := ethScanContract . EtherBalances ( & bind . CallOpts {
2023-09-20 08:41:23 +00:00
Context : ctx ,
BlockNumber : atBlock ,
2023-03-30 07:06:47 +00:00
} , accounts )
if err != nil {
2023-09-20 08:41:23 +00:00
log . Error ( "can't fetch chain balance 5" , err )
2023-03-30 07:06:47 +00:00
return nil
}
for idx , account := range accounts {
balance := new ( big . Int )
balance . SetBytes ( res [ idx ] . Data )
2023-09-20 08:41:23 +00:00
updateBalance ( client . NetworkID ( ) , account , common . HexToAddress ( "0x" ) , balance )
2023-03-30 07:06:47 +00:00
}
return nil
} )
}
for accountIdx := range accounts {
2023-10-05 11:55:28 +00:00
// Keep the reference to the account. DO NOT USE A LOOP, the account will be overridden in the coroutine
2023-03-30 07:06:47 +00:00
account := accounts [ accountIdx ]
for idx := range tokenChunks {
2023-10-05 11:55:28 +00:00
// Keep the reference to the chunk. DO NOT USE A LOOP, the chunk will be overridden in the coroutine
2023-03-30 07:06:47 +00:00
chunk := tokenChunks [ idx ]
2023-10-05 11:55:28 +00:00
2022-10-27 07:38:05 +00:00
group . Add ( func ( parent context . Context ) error {
ctx , cancel := context . WithTimeout ( parent , requestTimeout )
defer cancel ( )
2023-09-20 08:41:23 +00:00
var res [ ] ethscan . BalanceScannerResult
if atBlock == nil || big . NewInt ( int64 ( availableAtBlock ) ) . Cmp ( atBlock ) < 0 {
res , err = ethScanContract . TokensBalance ( & bind . CallOpts {
Context : ctx ,
BlockNumber : atBlock ,
} , account , chunk )
if err != nil {
log . Error ( "can't fetch erc20 token balance 6" , "account" , account , "error" , err )
return nil
}
if len ( res ) != len ( chunk ) {
log . Error ( "can't fetch erc20 token balance 7" , "account" , account , "error response not complete" )
return nil
}
2023-03-30 07:06:47 +00:00
2023-09-20 08:41:23 +00:00
for idx , token := range chunk {
if ! res [ idx ] . Success {
continue
}
balance := new ( big . Int )
balance . SetBytes ( res [ idx ] . Data )
updateBalance ( client . NetworkID ( ) , account , token , balance )
}
2023-05-02 07:52:51 +00:00
return nil
}
2023-09-20 08:41:23 +00:00
for _ , token := range chunk {
balance , err := tm . GetTokenBalanceAt ( ctx , client , account , token , atBlock )
if err != nil {
if err != bind . ErrNoCode {
log . Error ( "can't fetch erc20 token balance 8" , "account" , account , "token" , token , "error on fetching token balance" )
2023-05-02 07:52:51 +00:00
2023-09-20 08:41:23 +00:00
return nil
}
2023-03-30 07:06:47 +00:00
}
2023-09-20 08:41:23 +00:00
updateBalance ( client . NetworkID ( ) , account , token , balance )
2022-10-27 07:38:05 +00:00
}
2023-09-20 08:41:23 +00:00
2022-10-27 07:38:05 +00:00
return nil
} )
}
}
}
select {
case <- group . WaitAsync ( ) :
case <- parent . Done ( ) :
return nil , parent . Err ( )
}
return response , group . Error ( )
}