2022-09-13 07:10:59 +00:00
package token
2021-09-09 14:28:54 +00:00
import (
"context"
"database/sql"
2022-01-14 09:21:00 +00:00
"errors"
2021-09-09 14:28:54 +00:00
"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" `
Color string ` json:"color" `
// 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
Verified bool ` json:"verified" `
CommunityID * string ` json:"communityId,omitempty" `
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-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
// member variables below are protected by mutex
2023-06-14 13:16:15 +00:00
tokenList [ ] * Token
tokenMap storeMap
areTokensFetched bool
2023-09-11 14:44:43 +00:00
tokenLock sync . RWMutex
2022-04-13 07:55:38 +00:00
}
2022-09-13 09:30:52 +00:00
func NewTokenManager (
db * sql . DB ,
RPCClient * rpc . Client ,
networkManager * network . Manager ,
2022-10-25 14:50:32 +00:00
) * Manager {
2023-08-31 07:47:24 +00:00
maker , _ := contracts . NewContractMaker ( RPCClient )
2023-03-14 17:33:05 +00:00
// Order of stores is important when merging token lists. The former prevale
2023-08-31 07:47:24 +00:00
return & Manager {
2023-09-11 14:44:43 +00:00
db : db ,
RPCClient : RPCClient ,
contractMaker : maker ,
networkManager : networkManager ,
stores : [ ] store { newUniswapStore ( ) , newDefaultStore ( ) } ,
tokenList : nil ,
tokenMap : nil ,
areTokensFetched : false ,
2023-08-31 07:47:24 +00:00
}
2023-02-06 17:05:58 +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
2023-03-14 17:33:05 +00:00
func overrideTokensInPlace ( networks [ ] params . Network , tokens [ ] * Token ) {
2022-09-13 09:30:52 +00:00
for _ , network := range networks {
if len ( network . TokenOverrides ) == 0 {
continue
}
2022-10-24 06:45:14 +00:00
2022-09-13 09:30:52 +00:00
for _ , overrideToken := range network . TokenOverrides {
2023-03-14 17:33:05 +00:00
for _ , token := range tokens {
2022-09-13 09:30:52 +00:00
if token . Symbol == overrideToken . Symbol {
2023-03-14 17:33:05 +00:00
token . Address = overrideToken . Address
2022-09-13 09:30:52 +00:00
}
}
}
2023-03-14 17:33:05 +00:00
}
}
2022-09-13 09:30:52 +00:00
2023-03-14 17:33:05 +00:00
func mergeTokenLists ( sliceLists [ ] [ ] * Token ) [ ] * Token {
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-01-09 14:54:07 +00:00
func ( tm * Manager ) inStore ( address common . Address , chainID uint64 ) bool {
if address == nativeChainAddress {
return true
}
2023-03-14 17:33:05 +00:00
2023-06-14 13:16:15 +00:00
if ! tm . areTokensFetched {
2023-03-15 15:10:36 +00:00
tm . fetchTokens ( )
}
2023-09-11 14:44:43 +00:00
tokensMap , ok := tm . getAddressTokenMap ( chainID )
2023-01-09 14:54:07 +00:00
if ! ok {
return false
}
_ , ok = tokensMap [ address ]
return ok
}
2023-09-11 14:44:43 +00:00
func ( tm * Manager ) getTokenList ( ) [ ] * Token {
tm . tokenLock . RLock ( )
defer tm . tokenLock . RUnlock ( )
return tm . tokenList
}
func ( tm * Manager ) getAddressTokenMap ( chainID uint64 ) ( addressTokenMap , bool ) {
tm . tokenLock . RLock ( )
defer tm . tokenLock . RUnlock ( )
tokenMap , chainPresent := tm . tokenMap [ chainID ]
return tokenMap , chainPresent
}
2023-09-20 08:41:23 +00:00
func ( tm * Manager ) SetTokens ( tokens [ ] * Token ) {
2023-09-11 14:44:43 +00:00
tm . tokenLock . Lock ( )
defer tm . tokenLock . Unlock ( )
tm . tokenList = tokens
tm . tokenMap = toTokenMap ( tokens )
tm . areTokensFetched = true
}
2023-03-15 15:10:36 +00:00
func ( tm * Manager ) fetchTokens ( ) {
2023-09-11 14:44:43 +00:00
tokenList := make ( [ ] * Token , 0 )
2023-03-15 15:10:36 +00:00
2023-10-03 13:27:42 +00:00
networks , err := tm . networkManager . GetAll ( )
2023-03-28 13:15:34 +00:00
if err != nil {
return
}
2023-03-14 17:33:05 +00:00
for _ , store := range tm . stores {
2023-06-14 13:16:15 +00:00
tokens := store . GetTokens ( )
2023-03-28 13:15:34 +00:00
validTokens := make ( [ ] * Token , 0 )
for _ , token := range tokens {
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-09-11 14:44:43 +00:00
tokenList = mergeTokenLists ( [ ] [ ] * Token { tokenList , validTokens } )
2023-03-14 17:33:05 +00:00
}
2023-09-11 14:44:43 +00:00
2023-09-20 08:41:23 +00:00
tm . SetTokens ( tokenList )
2023-03-14 17:33:05 +00:00
}
2023-06-02 20:07:42 +00:00
func ( tm * Manager ) getFullTokenList ( chainID uint64 ) [ ] * Token {
2023-11-03 12:08:32 +00:00
tokens , err := tm . GetTokens ( chainID , false )
2022-09-13 07:10:59 +00:00
if err != nil {
return nil
}
2023-06-02 20:07:42 +00:00
2023-11-03 12:08:32 +00:00
customTokens , err := tm . GetCustomsByChainID ( chainID , false )
2022-09-13 07:10:59 +00:00
if err != nil {
return nil
}
2023-06-02 20:07:42 +00:00
return append ( tokens , customTokens ... )
}
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 {
networks , err := tm . networkManager . Get ( true )
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 {
allTokens := tm . getFullTokenList ( chainID )
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 {
allTokens := tm . getFullTokenList ( chainID )
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 {
allTokens := tm . getFullTokenList ( chainID )
for _ , token := range allTokens {
if token . Address == address {
2023-10-25 16:49:18 +00:00
tm . discoverTokenCommunityID ( context . Background ( ) , 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-10-25 16:49:18 +00:00
tm . discoverTokenCommunityID ( context . Background ( ) , 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-11-03 12:08:32 +00:00
tokens , err := tm . GetTokens ( chainID , false )
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-02-17 14:11:07 +00:00
func ( tm * Manager ) GetAllTokensAndNativeCurrencies ( ) ( [ ] * Token , error ) {
allTokens , err := tm . GetAllTokens ( )
if err != nil {
return nil , err
}
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 {
allTokens = append ( allTokens , tm . ToToken ( network ) )
}
return allTokens , nil
}
2022-10-27 07:38:05 +00:00
func ( tm * Manager ) GetAllTokens ( ) ( [ ] * Token , error ) {
2023-06-14 13:16:15 +00:00
if ! tm . areTokensFetched {
2023-03-15 15:10:36 +00:00
tm . fetchTokens ( )
2022-10-27 07:38:05 +00:00
}
tokens , err := tm . GetCustoms ( )
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-09-11 14:44:43 +00:00
tokens = append ( tm . getTokenList ( ) , tokens ... )
2022-10-27 07:38:05 +00:00
2023-03-15 15:10:36 +00:00
overrideTokensInPlace ( tm . networkManager . GetConfiguredNetworks ( ) , tokens )
2023-03-14 17:33:05 +00:00
2023-03-15 15:10:36 +00:00
return tokens , nil
2022-10-27 07:38:05 +00:00
}
2023-11-03 12:08:32 +00:00
func ( tm * Manager ) GetTokensByChainIDs ( chainIDs [ ] uint64 , onlyCommunityCustoms bool ) ( [ ] * Token , error ) {
2023-05-22 08:55:33 +00:00
tokens := make ( [ ] * Token , 0 )
for _ , chainID := range chainIDs {
2023-11-03 12:08:32 +00:00
t , err := tm . GetTokens ( chainID , onlyCommunityCustoms )
2023-05-22 08:55:33 +00:00
if err != nil {
return nil , err
}
tokens = append ( tokens , t ... )
}
return tokens , nil
}
2023-11-03 12:08:32 +00:00
func ( tm * Manager ) GetDefaultTokens ( chainID uint64 ) ( [ ] * Token , error ) {
2023-06-14 13:16:15 +00:00
if ! tm . areTokensFetched {
2023-03-15 15:10:36 +00:00
tm . fetchTokens ( )
}
2023-09-11 14:44:43 +00:00
tokensMap , ok := tm . getAddressTokenMap ( chainID )
2022-01-14 09:21:00 +00:00
if ! ok {
return nil , errors . New ( "no tokens for this network" )
}
res := make ( [ ] * Token , 0 , len ( tokensMap ) )
for _ , token := range tokensMap {
res = append ( res , token )
}
2023-11-03 12:08:32 +00:00
return res , nil
}
func ( tm * Manager ) GetTokens ( chainID uint64 , onlyCommunityCustoms bool ) ( [ ] * Token , error ) {
res , err := tm . GetDefaultTokens ( chainID )
if err != nil {
return nil , err
}
2022-01-14 09:21:00 +00:00
2023-11-03 12:08:32 +00:00
tokens , err := tm . GetCustomsByChainID ( chainID , onlyCommunityCustoms )
2023-08-22 09:32:27 +00:00
if err != nil {
return nil , err
}
return append ( res , tokens ... ) , nil
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-25 16:49:18 +00:00
func ( tm * Manager ) getTokens ( query string , args ... any ) ( [ ] * Token , error ) {
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
err := rows . Scan ( & token . Address , & token . Name , & token . Symbol , & token . Decimals , & token . Color , & 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-25 16:49:18 +00:00
func ( tm * Manager ) GetCustoms ( ) ( [ ] * Token , error ) {
return tm . getTokens ( "SELECT address, name, symbol, decimals, color, network_id, community_id FROM tokens" )
}
2022-06-09 13:09:56 +00:00
2023-11-03 12:08:32 +00:00
func ( tm * Manager ) GetCustomsByChainID ( chainID uint64 , onlyCommunityCustoms bool ) ( [ ] * Token , error ) {
if onlyCommunityCustoms {
return tm . getTokens ( "SELECT address, name, symbol, decimals, color, network_id, community_id FROM tokens WHERE network_id=? AND community_id IS NOT NULL AND community_id != ''" , chainID )
}
return tm . getTokens ( "SELECT address, name, symbol, decimals, color, network_id, community_id FROM tokens WHERE network_id=?" , chainID )
2022-06-09 13:09:56 +00:00
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) IsTokenVisible ( chainID uint64 , address common . Address ) ( bool , error ) {
2022-04-13 07:55:38 +00:00
rows , err := tm . db . Query ( "SELECT chain_id, address FROM visible_tokens WHERE chain_id = ? AND address = ?" , chainID , address )
if err != nil {
return false , err
}
defer rows . Close ( )
return rows . Next ( ) , nil
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) Toggle ( chainID uint64 , address common . Address ) error {
2022-09-13 07:10:59 +00:00
isVisible , err := tm . IsTokenVisible ( chainID , address )
2022-04-13 07:55:38 +00:00
if err != nil {
return err
}
if isVisible {
_ , err = tm . db . Exec ( ` DELETE FROM visible_tokens WHERE address = ? and chain_id = ? ` , address , chainID )
return err
}
insert , err := tm . db . Prepare ( "INSERT OR REPLACE INTO visible_tokens (chain_id, address) VALUES (?, ?)" )
if err != nil {
return err
}
defer insert . Close ( )
_ , err = insert . Exec ( chainID , address )
return err
}
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 ) GetVisible ( chainIDs [ ] uint64 ) ( map [ uint64 ] [ ] * Token , error ) {
2022-09-13 07:10:59 +00:00
customTokens , err := tm . GetCustoms ( )
2022-04-13 07:55:38 +00:00
if err != nil {
return nil , err
}
rst := make ( map [ uint64 ] [ ] * Token )
for _ , chainID := range chainIDs {
network := tm . networkManager . Find ( chainID )
if network == nil {
continue
}
rst [ chainID ] = make ( [ ] * Token , 0 )
2022-10-27 07:38:05 +00:00
rst [ chainID ] = append ( rst [ chainID ] , tm . ToToken ( network ) )
2022-04-13 07:55:38 +00:00
}
rows , err := tm . db . Query ( "SELECT chain_id, address FROM visible_tokens" )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
address := common . HexToAddress ( "0x" )
chainID := uint64 ( 0 )
err := rows . Scan ( & chainID , & address )
if err != nil {
return nil , err
}
found := false
2023-11-03 12:08:32 +00:00
tokens , err := tm . GetTokens ( chainID , false )
2023-03-14 17:33:05 +00:00
if err != nil {
continue
}
for _ , token := range tokens {
2022-04-13 07:55:38 +00:00
if token . Address == address {
rst [ chainID ] = append ( rst [ chainID ] , token )
found = true
break
}
}
if found {
continue
}
for _ , token := range customTokens {
if token . Address == address {
rst [ chainID ] = append ( rst [ chainID ] , token )
break
}
}
}
for _ , chainID := range chainIDs {
if len ( rst [ chainID ] ) == 1 {
2022-09-13 07:10:59 +00:00
token := tm . FindSNT ( chainID )
2022-04-13 07:55:38 +00:00
if token != nil {
rst [ chainID ] = append ( rst [ chainID ] , token )
}
}
}
return rst , nil
}
2022-10-25 14:50:32 +00:00
func ( tm * Manager ) UpsertCustom ( token Token ) error {
2021-09-09 14:28:54 +00:00
insert , err := tm . db . Prepare ( "INSERT OR REPLACE INTO TOKENS (network_id, address, name, symbol, decimals, color) VALUES (?, ?, ?, ?, ?, ?)" )
if err != nil {
return err
}
_ , err = insert . Exec ( token . ChainID , token . Address , token . Name , token . Symbol , token . Decimals , token . Color )
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 ) GetBalances ( parent context . Context , clients map [ uint64 ] chain . ClientInterface , accounts , tokens [ ] common . Address ) ( map [ common . Address ] map [ common . Address ] * hexutil . Big , error ) {
2021-09-09 14:28:54 +00:00
var (
group = async . NewAtomicGroup ( parent )
mu sync . Mutex
response = map [ common . Address ] map [ common . Address ] * hexutil . Big { }
)
2022-09-09 06:58:36 +00:00
updateBalance := func ( account common . Address , token common . Address , balance * big . Int ) {
mu . Lock ( )
if _ , ok := response [ account ] ; ! ok {
response [ account ] = map [ common . Address ] * hexutil . Big { }
}
if _ , ok := response [ account ] [ token ] ; ! ok {
zeroHex := hexutil . Big ( * big . NewInt ( 0 ) )
response [ account ] [ token ] = & zeroHex
}
sum := big . NewInt ( 0 ) . Add ( response [ account ] [ token ] . ToInt ( ) , balance )
sumHex := hexutil . Big ( * sum )
response [ account ] [ token ] = & sumHex
mu . Unlock ( )
}
2023-08-31 07:47:24 +00:00
2022-05-26 10:44:29 +00:00
for clientIdx := range clients {
2022-09-09 06:58:36 +00:00
client := clients [ clientIdx ]
2023-01-09 14:54:07 +00:00
2023-09-20 08:41:23 +00:00
ethScanContract , _ , err := tm . contractMaker . NewEthScan ( client . NetworkID ( ) )
2022-09-09 06:58:36 +00:00
if err == nil {
fetchChainBalance := false
var tokenChunks [ ] [ ] common . Address
2023-03-28 12:46:46 +00:00
chunkSize := 500
2022-09-09 06:58:36 +00:00
for i := 0 ; i < len ( tokens ) ; i += chunkSize {
end := i + chunkSize
if end > len ( tokens ) {
end = len ( tokens )
}
tokenChunks = append ( tokenChunks , tokens [ i : end ] )
}
for _ , token := range tokens {
if token == nativeChainAddress {
fetchChainBalance = true
}
}
if fetchChainBalance {
2021-09-09 14:28:54 +00:00
group . Add ( func ( parent context . Context ) error {
ctx , cancel := context . WithTimeout ( parent , requestTimeout )
defer cancel ( )
2022-09-09 06:58:36 +00:00
res , err := ethScanContract . EtherBalances ( & bind . CallOpts {
Context : ctx ,
} , accounts )
2021-09-09 14:28:54 +00:00
if err != nil {
2023-09-20 08:41:23 +00:00
log . Error ( "can't fetch chain balance 2" , err )
2021-09-09 14:28:54 +00:00
return nil
}
2022-09-09 06:58:36 +00:00
for idx , account := range accounts {
balance := new ( big . Int )
balance . SetBytes ( res [ idx ] . Data )
updateBalance ( account , common . HexToAddress ( "0x" ) , balance )
2021-09-09 14:28:54 +00:00
}
return nil
} )
}
2022-09-09 06:58:36 +00:00
for accountIdx := range accounts {
account := accounts [ accountIdx ]
for idx := range tokenChunks {
chunk := tokenChunks [ idx ]
group . Add ( func ( parent context . Context ) error {
ctx , cancel := context . WithTimeout ( parent , requestTimeout )
defer cancel ( )
res , err := ethScanContract . TokensBalance ( & bind . CallOpts {
Context : ctx ,
} , account , chunk )
if err != nil {
2023-09-20 08:41:23 +00:00
log . Error ( "can't fetch erc20 token balance 3" , "account" , account , "error" , err )
2022-09-09 06:58:36 +00:00
return nil
}
for idx , token := range chunk {
if ! res [ idx ] . Success {
continue
}
balance := new ( big . Int )
balance . SetBytes ( res [ idx ] . Data )
updateBalance ( account , token , balance )
}
return nil
} )
}
}
} else {
for tokenIdx := range tokens {
for accountIdx := range accounts {
// Below, we set account, token and client from idx on purpose to avoid override
account := accounts [ accountIdx ]
token := tokens [ tokenIdx ]
client := clients [ clientIdx ]
2023-09-20 08:41:23 +00:00
if ! tm . inStore ( token , client . NetworkID ( ) ) {
2023-01-09 14:54:07 +00:00
continue
}
2022-09-09 06:58:36 +00:00
group . Add ( func ( parent context . Context ) error {
ctx , cancel := context . WithTimeout ( parent , requestTimeout )
defer cancel ( )
2022-09-13 07:10:59 +00:00
balance , err := tm . GetBalance ( ctx , client , account , token )
2022-09-09 06:58:36 +00:00
if err != nil {
2023-09-20 08:41:23 +00:00
log . Error ( "can't fetch erc20 token balance 4" , "account" , account , "token" , token , "error" , err )
2022-09-09 06:58:36 +00:00
return nil
}
updateBalance ( account , token , balance )
return nil
} )
}
}
2021-09-09 14:28:54 +00:00
}
2022-09-09 06:58:36 +00:00
2021-09-09 14:28:54 +00:00
}
select {
case <- group . WaitAsync ( ) :
case <- parent . Done ( ) :
return nil , parent . Err ( )
}
return response , group . Error ( )
}
2022-10-27 07:38:05 +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 ( )
}