2022-09-13 07:10:59 +00:00
package token
2021-09-09 14:28:54 +00:00
2024-10-03 19:59:44 +00:00
//go:generate mockgen -source=token.go -destination=mock/token/tokenmanager.go
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"
2024-06-11 21:00:04 +00:00
"fmt"
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"
2024-10-28 20:54:17 +00:00
"go.uber.org/zap"
2021-09-09 14:28:54 +00:00
"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"
2024-09-26 22:37:32 +00:00
gocommon "github.com/status-im/status-go/common"
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"
eth_node_types "github.com/status-im/status-go/eth-node/types"
2024-10-28 20:54:17 +00:00
"github.com/status-im/status-go/logutils"
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"
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"
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-06-26 08:03:01 +00:00
"github.com/status-im/status-go/services/wallet/token/balancefetcher"
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
)
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 {
2024-06-26 08:03:01 +00:00
balancefetcher . BalanceFetcher
2023-08-11 17:28:46 +00:00
LookupTokenIdentity ( chainID uint64 , address common . Address , native bool ) * Token
LookupToken ( chainID * uint64 , tokenSymbol string ) ( token * Token , isNative bool )
2024-06-06 19:57:29 +00:00
GetTokenHistoricalBalance ( account common . Address , chainID uint64 , symbol string , timestamp int64 ) ( * big . Int , error )
2024-06-26 08:03:01 +00:00
GetTokensByChainIDs ( chainIDs [ ] uint64 ) ( [ ] * Token , error )
2023-08-11 17:28:46 +00:00
}
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 {
2024-06-26 08:03:01 +00:00
balancefetcher . BalanceFetcher
2024-06-06 19:57:29 +00:00
db * sql . DB
2024-06-27 21:27:09 +00:00
RPCClient rpc . ClientInterface
2024-06-06 19:57:29 +00:00
ContractMaker * contracts . ContractMaker
2024-06-27 21:27:09 +00:00
networkManager network . ManagerInterface
2024-06-06 19:57:29 +00:00
stores [ ] store // Set on init, not changed afterwards
communityTokensDB * communitytokensdatabase . Database
communityManager * community . Manager
mediaServer * server . MediaServer
walletFeed * event . Feed
accountFeed * event . Feed
accountWatcher * accountsevent . Watcher
accountsDB * accounts . Database
tokenBalancesStorage TokenBalancesStorage
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-06-27 21:27:09 +00:00
func prepareTokens ( networkManager network . ManagerInterface , 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 ,
2024-06-27 21:27:09 +00:00
RPCClient rpc . ClientInterface ,
2024-03-22 09:45:43 +00:00
communityManager * community . Manager ,
2024-06-27 21:27:09 +00:00
networkManager network . ManagerInterface ,
2024-03-22 09:45:43 +00:00
appDB * sql . DB ,
mediaServer * server . MediaServer ,
walletFeed * event . Feed ,
accountFeed * event . Feed ,
accountsDB * accounts . Database ,
2024-06-06 19:57:29 +00:00
tokenBalancesStorage TokenBalancesStorage ,
2024-03-22 09:45:43 +00:00
) * 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 {
2024-06-27 21:27:09 +00:00
BalanceFetcher : balancefetcher . NewDefaultBalanceFetcher ( maker ) ,
db : db ,
RPCClient : RPCClient ,
ContractMaker : maker ,
2024-06-06 19:57:29 +00:00
networkManager : networkManager ,
communityManager : communityManager ,
stores : stores ,
communityTokensDB : communitytokensdatabase . NewCommunityTokensDatabase ( appDB ) ,
tokens : tokens ,
mediaServer : mediaServer ,
walletFeed : walletFeed ,
accountFeed : accountFeed ,
accountsDB : accountsDB ,
tokenBalancesStorage : tokenBalancesStorage ,
2024-03-22 09:45:43 +00:00
}
}
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-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Info ( "Marking token as previously owned" ,
zap . Any ( "token" , token ) ,
zap . Stringer ( "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
}
2024-06-06 19:57:29 +00:00
tokens , err := tm . tokenBalancesStorage . GetTokens ( )
if err != nil {
return false , err
}
if tokens [ owner ] == nil {
tokens [ owner ] = make ( [ ] StorageToken , 0 )
} else {
for _ , t := range tokens [ owner ] {
if t . Address == token . Address && t . ChainID == token . ChainID && t . Symbol == token . Symbol {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Info ( "Token already marked as previously owned" ,
zap . Any ( "token" , token ) ,
zap . Stringer ( "owner" , owner ) ,
)
2024-06-06 19:57:29 +00:00
return false , nil
}
}
}
// append token to the list of tokens
tokens [ owner ] = append ( tokens [ owner ] , StorageToken {
Token : * token ,
BalancesPerChain : map [ uint64 ] ChainBalance {
token . ChainID : {
RawBalance : "0" ,
Balance : & big . Float { } ,
Address : token . Address ,
ChainID : token . ChainID ,
} ,
} ,
} )
// save the updated list of tokens
err = tm . tokenBalancesStorage . SaveTokens ( tokens )
if err != nil {
2024-02-19 13:55:38 +00:00
return false , err
2023-12-21 14:12:50 +00:00
}
2024-06-06 19:57:29 +00:00
return true , nil
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 {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Error ( "Cannot prepare token update query" , zap . Error ( err ) )
2023-10-25 16:49:18 +00:00
return
}
if uri == "" {
// Update token community ID to prevent further checks
_ , err := update . Exec ( "" , token . ChainID , token . Address )
if err != nil {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Error ( "Cannot update community id" , zap . Error ( err ) )
2023-10-25 16:49:18 +00:00
}
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 {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Error ( "Cannot update community id" , zap . Error ( err ) )
2023-10-25 16:49:18 +00:00
}
}
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 {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Error ( "can't fetch custom tokens" , zap . 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
}
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-09-26 22:37:32 +00:00
defer gocommon . LogOnPanic ( )
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
2024-06-06 19:57:29 +00:00
func ( tm * Manager ) GetPreviouslyOwnedTokens ( ) ( map [ common . Address ] [ ] Token , error ) {
storageTokens , err := tm . tokenBalancesStorage . GetTokens ( )
2024-03-22 09:45:43 +00:00
if err != nil {
return nil , err
}
2024-06-06 19:57:29 +00:00
tokens := make ( map [ common . Address ] [ ] Token )
for account , storageToken := range storageTokens {
for _ , token := range storageToken {
tokens [ account ] = append ( tokens [ account ] , token . Token )
2024-03-22 09:45:43 +00:00
}
}
2024-06-06 19:57:29 +00:00
return tokens , nil
2024-03-22 09:45:43 +00:00
}
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 {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Error ( "token.Manager: can't remove token balances" , zap . Error ( err ) )
2024-03-22 09:45:43 +00:00
}
}
}
}
2024-06-11 21:00:04 +00:00
func ( tm * Manager ) GetCachedBalancesByChain ( accounts , tokenAddresses [ ] common . Address , chainIDs [ ] uint64 ) ( map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big , error ) {
accountStrings := make ( [ ] string , len ( accounts ) )
for i , account := range accounts {
accountStrings [ i ] = fmt . Sprintf ( "'%s'" , account . Hex ( ) )
}
tokenAddressStrings := make ( [ ] string , len ( tokenAddresses ) )
for i , tokenAddress := range tokenAddresses {
tokenAddressStrings [ i ] = fmt . Sprintf ( "'%s'" , tokenAddress . Hex ( ) )
}
chainIDStrings := make ( [ ] string , len ( chainIDs ) )
for i , chainID := range chainIDs {
chainIDStrings [ i ] = fmt . Sprintf ( "%d" , chainID )
}
2024-06-25 08:14:40 +00:00
query := ` SELECT chain_id , user_address , token_address , raw_balance
FROM token_balances
WHERE user_address IN ( ` + strings.Join(accountStrings, ",") + ` )
AND token_address IN ( ` + strings.Join(tokenAddressStrings, ",") + ` )
2024-06-11 21:00:04 +00:00
AND chain_id IN ( ` + strings.Join(chainIDStrings, ",") + ` ) `
rows , err := tm . db . Query ( query )
if err != nil {
return nil , err
}
defer rows . Close ( )
ret := make ( map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big )
for rows . Next ( ) {
var chainID uint64
var userAddressStr , tokenAddressStr string
2024-06-25 08:14:40 +00:00
var rawBalance string
2024-06-11 21:00:04 +00:00
2024-06-25 08:14:40 +00:00
err := rows . Scan ( & chainID , & userAddressStr , & tokenAddressStr , & rawBalance )
2024-06-11 21:00:04 +00:00
if err != nil {
return nil , err
}
num := new ( hexutil . Big )
2024-06-25 08:14:40 +00:00
_ , ok := num . ToInt ( ) . SetString ( rawBalance , 10 )
2024-06-11 21:00:04 +00:00
if ! ok {
return ret , nil
}
if ret [ chainID ] == nil {
ret [ chainID ] = make ( map [ common . Address ] map [ common . Address ] * hexutil . Big )
}
if ret [ chainID ] [ common . HexToAddress ( userAddressStr ) ] == nil {
ret [ chainID ] [ common . HexToAddress ( userAddressStr ) ] = make ( map [ common . Address ] * hexutil . Big )
}
ret [ chainID ] [ common . HexToAddress ( userAddressStr ) ] [ common . HexToAddress ( tokenAddressStr ) ] = num
}
return ret , nil
}