2022-09-13 07:10:59 +00:00
package token
2021-09-09 14:28:54 +00:00
import (
"context"
"database/sql"
2024-01-04 12:22:06 +00:00
"encoding/json"
2023-12-21 14:12:50 +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"
2024-01-04 12:22:06 +00:00
"github.com/ethereum/go-ethereum/event"
2021-09-09 14:28:54 +00:00
"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"
2024-03-22 09:45:43 +00:00
"github.com/status-im/status-go/multiaccounts/accounts"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/params"
2023-12-12 07:37:57 +00:00
"github.com/status-im/status-go/protocol/communities/token"
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-12-21 15:05:29 +00:00
"github.com/status-im/status-go/server"
2024-03-22 09:45:43 +00:00
"github.com/status-im/status-go/services/accounts/accountsevent"
2024-03-14 08:39:06 +00:00
"github.com/status-im/status-go/services/communitytokens/communitytokensdatabase"
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"
2024-03-08 12:52:39 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
2023-12-14 16:50:46 +00:00
"github.com/status-im/status-go/services/wallet/community"
2024-01-04 12:22:06 +00:00
"github.com/status-im/status-go/services/wallet/walletevent"
)
const (
EventCommunityTokenReceived walletevent . EventType = "wallet-community-token-received"
2021-09-09 14:28:54 +00:00
)
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-12-12 07:37:57 +00:00
Image string ` json:"image,omitempty" `
2023-08-31 07:47:24 +00:00
2023-12-22 09:43:19 +00:00
CommunityData * community . Data ` json:"community_data,omitempty" `
Verified bool ` json:"verified" `
TokenListID string ` json:"tokenListId" `
2021-09-09 14:28:54 +00:00
}
2024-01-04 12:22:06 +00:00
type ReceivedToken struct {
2024-03-25 17:30:59 +00:00
Token
Amount float64 ` json:"amount" `
TxHash common . Hash ` json:"txHash" `
IsFirst bool ` json:"isFirst" `
2024-01-04 12:22:06 +00:00
}
2022-09-13 07:10:59 +00:00
func ( t * Token ) IsNative ( ) bool {
2024-04-01 13:39:17 +00:00
return strings . EqualFold ( t . Symbol , "ETH" )
2022-09-13 07:10:59 +00:00
}
2023-10-17 15:05:05 +00:00
type List struct {
2024-02-01 12:00:06 +00:00
Name string ` json:"name" `
Tokens [ ] * Token ` json:"tokens" `
Source string ` json:"source" `
Version string ` json:"version" `
}
type ListWrapper struct {
UpdatedAt int64 ` json:"updatedAt" `
Data [ ] * List ` json:"data" `
2023-10-17 15:05:05 +00:00
}
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-12-12 07:37:57 +00:00
db * sql . DB
RPCClient * rpc . Client
2024-01-19 15:57:04 +00:00
ContractMaker * contracts . ContractMaker
2023-12-12 07:37:57 +00:00
networkManager * network . Manager
stores [ ] store // Set on init, not changed afterwards
2024-03-14 08:39:06 +00:00
communityTokensDB * communitytokensdatabase . Database
2023-12-14 16:50:46 +00:00
communityManager * community . Manager
2023-12-21 15:05:29 +00:00
mediaServer * server . MediaServer
2024-01-04 12:22:06 +00:00
walletFeed * event . Feed
2024-03-22 09:45:43 +00:00
accountFeed * event . Feed
accountWatcher * accountsevent . Watcher
accountsDB * accounts . Database
2023-09-11 14:44:43 +00:00
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
}
2024-03-22 09:45:43 +00:00
func prepareTokens ( networkManager * network . Manager , stores [ ] store ) [ ] * Token {
2023-10-17 15:05:05 +00:00
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
}
2024-03-22 09:45:43 +00:00
return tokens
}
func NewTokenManager (
db * sql . DB ,
RPCClient * rpc . Client ,
communityManager * community . Manager ,
networkManager * network . Manager ,
appDB * sql . DB ,
mediaServer * server . MediaServer ,
walletFeed * event . Feed ,
accountFeed * event . Feed ,
accountsDB * accounts . Database ,
) * Manager {
maker , _ := contracts . NewContractMaker ( RPCClient )
stores := [ ] store { newUniswapStore ( ) , newDefaultStore ( ) }
tokens := prepareTokens ( networkManager , stores )
2023-09-11 14:44:43 +00:00
2023-10-17 15:05:05 +00:00
return & Manager {
2023-12-12 07:37:57 +00:00
db : db ,
RPCClient : RPCClient ,
2024-01-19 15:57:04 +00:00
ContractMaker : maker ,
2023-12-12 07:37:57 +00:00
networkManager : networkManager ,
2023-12-14 16:50:46 +00:00
communityManager : communityManager ,
2023-12-12 07:37:57 +00:00
stores : stores ,
2024-03-14 08:39:06 +00:00
communityTokensDB : communitytokensdatabase . NewCommunityTokensDatabase ( appDB ) ,
2023-12-12 07:37:57 +00:00
tokens : tokens ,
2023-12-21 15:05:29 +00:00
mediaServer : mediaServer ,
2024-01-04 12:22:06 +00:00
walletFeed : walletFeed ,
2024-03-22 09:45:43 +00:00
accountFeed : accountFeed ,
accountsDB : accountsDB ,
}
}
func ( tm * Manager ) Start ( ) {
tm . startAccountsWatcher ( )
}
func ( tm * Manager ) startAccountsWatcher ( ) {
if tm . accountWatcher != nil {
return
}
tm . accountWatcher = accountsevent . NewWatcher ( tm . accountsDB , tm . accountFeed , tm . onAccountsChange )
tm . accountWatcher . Start ( )
}
func ( tm * Manager ) Stop ( ) {
tm . stopAccountsWatcher ( )
}
func ( tm * Manager ) stopAccountsWatcher ( ) {
if tm . accountWatcher != nil {
tm . accountWatcher . Stop ( )
tm . accountWatcher = nil
2023-10-17 15:05:05 +00:00
}
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
}
2024-02-19 13:55:38 +00:00
func ( tm * Manager ) MarkAsPreviouslyOwnedToken ( token * Token , owner common . Address ) ( bool , error ) {
2024-03-22 09:45:43 +00:00
log . Info ( "Marking token as previously owned" , "token" , token , "owner" , owner )
2023-12-21 14:12:50 +00:00
if token == nil {
2024-02-19 13:55:38 +00:00
return false , errors . New ( "token is nil" )
2023-12-21 14:12:50 +00:00
}
if ( owner == common . Address { } ) {
2024-02-19 13:55:38 +00:00
return false , errors . New ( "owner is nil" )
2023-12-21 14:12:50 +00:00
}
count := 0
err := tm . db . QueryRow ( ` SELECT EXISTS(SELECT 1 FROM token_balances WHERE user_address = ? AND token_address = ? AND chain_id = ?) ` , owner . Hex ( ) , token . Address . Hex ( ) , token . ChainID ) . Scan ( & count )
if err != nil || count > 0 {
2024-02-19 13:55:38 +00:00
return false , err
2023-12-21 14:12:50 +00:00
}
_ , err = tm . db . Exec ( ` INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_decimals,chain_id,token_decimals,raw_balance,balance) VALUES (?,?,?,?,?,?,?,?,?) ` , owner . Hex ( ) , token . Name , token . Symbol , token . Address . Hex ( ) , token . Decimals , token . ChainID , 0 , "0" , "0" )
2024-02-19 13:55:38 +00:00
return true , err
2023-12-21 14:12:50 +00:00
}
2023-10-25 16:49:18 +00:00
func ( tm * Manager ) discoverTokenCommunityID ( ctx context . Context , token * Token , address common . Address ) {
2023-12-12 07:37:57 +00:00
if token == nil || token . CommunityData != 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 )
2024-01-04 12:22:06 +00:00
token . CommunityData = & community . Data {
ID : communityID ,
}
2023-10-25 16:49:18 +00:00
_ , 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
}
2024-02-01 12:00:06 +00:00
func ( tm * Manager ) GetList ( ) * ListWrapper {
data := make ( [ ] * List , 0 )
2023-10-17 15:05:05 +00:00
nativeTokens , err := tm . getNativeTokens ( )
if err == nil {
2024-02-01 12:00:06 +00:00
data = append ( data , & List {
Name : "native" ,
Tokens : nativeTokens ,
Source : "native" ,
Version : "1.0.0" ,
2023-10-17 15:05:05 +00:00
} )
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 {
2024-02-01 12:00:06 +00:00
data = append ( data , & List {
Name : "custom" ,
Tokens : customTokens ,
Source : "custom" ,
Version : "1.0.0" ,
2023-10-17 15:05:05 +00:00
} )
2023-08-22 09:32:27 +00:00
}
2024-02-01 12:00:06 +00:00
updatedAt := time . Now ( ) . Unix ( )
2023-10-17 15:05:05 +00:00
for _ , store := range tm . stores {
2024-02-01 12:00:06 +00:00
updatedAt = store . GetUpdatedAt ( )
data = append ( data , & List {
Name : store . GetName ( ) ,
Tokens : store . GetTokens ( ) ,
Source : store . GetSource ( ) ,
Version : store . GetVersion ( ) ,
2023-10-17 15:05:05 +00:00
} )
}
2024-02-01 12:00:06 +00:00
return & ListWrapper {
Data : data ,
UpdatedAt : updatedAt ,
}
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 ) {
2024-01-19 15:57:04 +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-12-12 07:37:57 +00:00
communityTokens := [ ] * token . CommunityToken { }
if tm . communityTokensDB != nil {
// Error is skipped because it's only returning optional metadata
communityTokens , _ = tm . communityTokensDB . GetCommunityERC20Metadata ( )
}
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 {
2023-12-12 07:37:57 +00:00
communityID := communityIDDB . String
for _ , communityToken := range communityTokens {
if communityToken . CommunityID != communityID || uint64 ( communityToken . ChainID ) != token . ChainID || communityToken . Symbol != token . Symbol {
continue
}
2023-12-21 15:05:29 +00:00
token . Image = tm . mediaServer . MakeCommunityTokenImagesURL ( communityID , token . ChainID , token . Symbol )
2023-12-12 07:37:57 +00:00
break
}
2023-12-22 09:43:19 +00:00
token . CommunityData = & community . Data {
2023-12-12 07:37:57 +00:00
ID : communityID ,
}
2023-10-25 16:49:18 +00:00
}
2024-01-04 12:22:06 +00:00
_ = tm . fillCommunityData ( token )
2023-12-14 16:50:46 +00:00
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 ]
2024-01-19 15:57:04 +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 {
2024-05-23 10:01:55 +00:00
log . Error ( "can't fetch chain balance 5" , "err" , 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 ) {
2024-05-23 10:01:55 +00:00
log . Error ( "can't fetch erc20 token balance 7" , "account" , account , "error" , "response not complete" )
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
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 {
2024-05-23 10:01:55 +00:00
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 ( )
}
2024-01-04 12:22:06 +00:00
2024-02-19 13:55:38 +00:00
func ( tm * Manager ) SignalCommunityTokenReceived ( address common . Address , txHash common . Hash , value * big . Int , t * Token , isFirst bool ) {
2024-01-04 12:22:06 +00:00
if tm . walletFeed == nil || t == nil || t . CommunityData == nil {
return
}
if len ( t . CommunityData . Name ) == 0 {
_ = tm . fillCommunityData ( t )
}
if len ( t . CommunityData . Name ) == 0 && tm . communityManager != nil {
communityData , _ := tm . communityManager . FetchCommunityMetadata ( t . CommunityData . ID )
if communityData != nil {
t . CommunityData . Name = communityData . CommunityName
t . CommunityData . Color = communityData . CommunityColor
t . CommunityData . Image = tm . communityManager . GetCommunityImageURL ( t . CommunityData . ID )
}
}
2024-02-19 13:55:38 +00:00
floatAmount , _ := new ( big . Float ) . Quo ( new ( big . Float ) . SetInt ( value ) , new ( big . Float ) . SetInt ( new ( big . Int ) . Exp ( big . NewInt ( 10 ) , big . NewInt ( int64 ( t . Decimals ) ) , nil ) ) ) . Float64 ( )
2024-03-25 17:30:59 +00:00
t . Image = tm . mediaServer . MakeCommunityTokenImagesURL ( t . CommunityData . ID , t . ChainID , t . Symbol )
2024-02-19 13:55:38 +00:00
2024-01-04 12:22:06 +00:00
receivedToken := ReceivedToken {
2024-03-25 17:30:59 +00:00
Token : * t ,
Amount : floatAmount ,
TxHash : txHash ,
IsFirst : isFirst ,
2024-01-04 12:22:06 +00:00
}
encodedMessage , err := json . Marshal ( receivedToken )
if err != nil {
return
}
tm . walletFeed . Send ( walletevent . Event {
Type : EventCommunityTokenReceived ,
ChainID : t . ChainID ,
Accounts : [ ] common . Address {
address ,
} ,
Message : string ( encodedMessage ) ,
} )
}
func ( tm * Manager ) fillCommunityData ( token * Token ) error {
if token == nil || token . CommunityData == nil || tm . communityManager == nil {
return nil
}
communityInfo , _ , err := tm . communityManager . GetCommunityInfo ( token . CommunityData . ID )
if err != nil {
return err
}
if err == nil && communityInfo != nil {
// Fetched data from cache. Cache is refreshed during every wallet token list call.
token . CommunityData . Name = communityInfo . CommunityName
token . CommunityData . Color = communityInfo . CommunityColor
token . CommunityData . Image = communityInfo . CommunityImage
}
return nil
}
2024-03-08 12:52:39 +00:00
func ( tm * Manager ) GetTokenHistoricalBalance ( account common . Address , chainID uint64 , symbol string , timestamp int64 ) ( * big . Int , error ) {
var balance big . Int
err := tm . db . QueryRow ( "SELECT balance FROM balance_history WHERE currency = ? AND chain_id = ? AND address = ? AND timestamp < ? order by timestamp DESC LIMIT 1" , symbol , chainID , account , timestamp ) . Scan ( ( * bigint . SQLBigIntBytes ) ( & balance ) )
if err == sql . ErrNoRows {
return nil , nil
} else if err != nil {
return nil , err
}
return & balance , nil
}
2024-03-22 09:45:43 +00:00
func ( tm * Manager ) GetPreviouslyOwnedTokens ( ) ( map [ common . Address ] [ ] * Token , error ) {
tokenMap := make ( map [ common . Address ] [ ] * Token )
rows , err := tm . db . Query ( "SELECT user_address, token_name, token_symbol, token_address, token_decimals, chain_id FROM token_balances" )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
token := & Token { }
var addressStr , tokenAddressStr string
err := rows . Scan ( & addressStr , & token . Name , & token . Symbol , & tokenAddressStr , & token . Decimals , & token . ChainID )
if err != nil {
return nil , err
}
address := common . HexToAddress ( addressStr )
if ( address == common . Address { } ) {
continue
}
token . Address = common . HexToAddress ( tokenAddressStr )
if ( token . Address == common . Address { } ) {
continue
}
if _ , ok := tokenMap [ address ] ; ! ok {
tokenMap [ address ] = make ( [ ] * Token , 0 )
}
tokenMap [ address ] = append ( tokenMap [ address ] , token )
}
return tokenMap , nil
}
func ( tm * Manager ) removeTokenBalances ( account common . Address ) error {
_ , err := tm . db . Exec ( "DELETE FROM token_balances WHERE user_address = ?" , account . String ( ) )
return err
}
func ( tm * Manager ) onAccountsChange ( changedAddresses [ ] common . Address , eventType accountsevent . EventType , currentAddresses [ ] common . Address ) {
if eventType == accountsevent . EventTypeRemoved {
for _ , account := range changedAddresses {
err := tm . removeTokenBalances ( account )
if err != nil {
log . Error ( "token.Manager: can't remove token balances" , "error" , err )
}
}
}
}