github.com/status-im/status-go@v1.1.0/services/wallet/token/balance_persistence.go (about)

     1  package token
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"math/big"
     7  
     8  	"github.com/ethereum/go-ethereum/common"
     9  )
    10  
    11  type TokenMarketValues struct {
    12  	MarketCap       float64 `json:"marketCap"`
    13  	HighDay         float64 `json:"highDay"`
    14  	LowDay          float64 `json:"lowDay"`
    15  	ChangePctHour   float64 `json:"changePctHour"`
    16  	ChangePctDay    float64 `json:"changePctDay"`
    17  	ChangePct24hour float64 `json:"changePct24hour"`
    18  	Change24hour    float64 `json:"change24hour"`
    19  	Price           float64 `json:"price"`
    20  	HasError        bool    `json:"hasError"`
    21  }
    22  
    23  type StorageToken struct {
    24  	Token
    25  	BalancesPerChain        map[uint64]ChainBalance      `json:"balancesPerChain"`
    26  	Description             string                       `json:"description"`
    27  	AssetWebsiteURL         string                       `json:"assetWebsiteUrl"`
    28  	BuiltOn                 string                       `json:"builtOn"`
    29  	MarketValuesPerCurrency map[string]TokenMarketValues `json:"marketValuesPerCurrency"`
    30  }
    31  
    32  type ChainBalance struct {
    33  	RawBalance     string         `json:"rawBalance"`
    34  	Balance        *big.Float     `json:"balance"`
    35  	Balance1DayAgo string         `json:"balance1DayAgo"`
    36  	Address        common.Address `json:"address"`
    37  	ChainID        uint64         `json:"chainId"`
    38  	HasError       bool           `json:"hasError"`
    39  }
    40  
    41  type TokenBalancesStorage interface {
    42  	SaveTokens(tokens map[common.Address][]StorageToken) error
    43  	GetTokens() (map[common.Address][]StorageToken, error)
    44  }
    45  
    46  type Persistence struct {
    47  	db *sql.DB
    48  }
    49  
    50  func NewPersistence(db *sql.DB) *Persistence {
    51  	return &Persistence{db: db}
    52  }
    53  
    54  func (p *Persistence) SaveTokens(tokens map[common.Address][]StorageToken) (err error) {
    55  	tx, err := p.db.BeginTx(context.Background(), &sql.TxOptions{})
    56  	if err != nil {
    57  		return
    58  	}
    59  	defer func() {
    60  		if err == nil {
    61  			err = tx.Commit()
    62  			return
    63  		}
    64  		// don't shadow original error
    65  		_ = tx.Rollback()
    66  	}()
    67  
    68  	for address, addressTokens := range tokens {
    69  		for _, t := range addressTokens {
    70  			for chainID, b := range t.BalancesPerChain {
    71  				if b.HasError {
    72  					continue
    73  				}
    74  				_, err = tx.Exec(`INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_decimals,token_description,token_url,balance,raw_balance,chain_id) VALUES (?,?,?,?,?,?,?,?,?,?)`, address.Hex(), t.Name, t.Symbol, b.Address.Hex(), t.Decimals, t.Description, t.AssetWebsiteURL, b.Balance.String(), b.RawBalance, chainID)
    75  				if err != nil {
    76  					return err
    77  				}
    78  			}
    79  
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  func (p *Persistence) GetTokens() (map[common.Address][]StorageToken, error) {
    87  	rows, err := p.db.Query(`SELECT user_address, token_name, token_symbol, token_address, token_decimals, token_description, token_url, balance, raw_balance, chain_id FROM token_balances `)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	defer rows.Close()
    93  
    94  	acc := make(map[common.Address]map[string]StorageToken)
    95  
    96  	for rows.Next() {
    97  		var addressStr, balance, rawBalance, tokenAddress string
    98  		token := StorageToken{}
    99  		var chainID uint64
   100  
   101  		err := rows.Scan(&addressStr, &token.Name, &token.Symbol, &tokenAddress, &token.Decimals, &token.Description, &token.AssetWebsiteURL, &balance, &rawBalance, &chainID)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  
   106  		token.Address = common.HexToAddress(tokenAddress)
   107  		token.ChainID = chainID
   108  		address := common.HexToAddress(addressStr)
   109  
   110  		if acc[address] == nil {
   111  			acc[address] = make(map[string]StorageToken)
   112  		}
   113  
   114  		if acc[address][token.Name].Name == "" {
   115  			token.BalancesPerChain = make(map[uint64]ChainBalance)
   116  			acc[address][token.Name] = token
   117  		}
   118  
   119  		tokenAcc := acc[address][token.Name]
   120  
   121  		balanceFloat := new(big.Float)
   122  		_, _, err = balanceFloat.Parse(balance, 10)
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  
   127  		tokenAcc.BalancesPerChain[chainID] = ChainBalance{
   128  			RawBalance: rawBalance,
   129  			Balance:    balanceFloat,
   130  			Address:    common.HexToAddress(tokenAddress),
   131  			ChainID:    chainID,
   132  		}
   133  	}
   134  
   135  	result := make(map[common.Address][]StorageToken)
   136  
   137  	for address, tks := range acc {
   138  		for _, t := range tks {
   139  			result[address] = append(result[address], t)
   140  		}
   141  	}
   142  	return result, nil
   143  }