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

     1  package activity
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  
     8  	// used for embedding the sql query in the binary
     9  	_ "embed"
    10  
    11  	eth "github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/common/hexutil"
    13  	"github.com/status-im/status-go/services/wallet/common"
    14  	"github.com/status-im/status-go/services/wallet/thirdparty"
    15  	"github.com/status-im/status-go/transactions"
    16  )
    17  
    18  const NoLimitTimestampForPeriod = 0
    19  
    20  //go:embed get_collectibles.sql
    21  var getCollectiblesQueryFormatString string
    22  
    23  //go:embed oldest_timestamp.sql
    24  var oldestTimestampQueryFormatString string
    25  
    26  //go:embed recipients.sql
    27  var recipientsQueryFormatString string
    28  
    29  type Period struct {
    30  	StartTimestamp int64 `json:"startTimestamp"`
    31  	EndTimestamp   int64 `json:"endTimestamp"`
    32  }
    33  
    34  type Type int
    35  
    36  const (
    37  	SendAT Type = iota
    38  	ReceiveAT
    39  	BuyAT
    40  	SwapAT
    41  	BridgeAT
    42  	ContractDeploymentAT
    43  	MintAT
    44  	ApproveAT
    45  )
    46  
    47  func allActivityTypesFilter() []Type {
    48  	return []Type{}
    49  }
    50  
    51  type Status int
    52  
    53  const (
    54  	FailedAS    Status = iota // failed status or at least one failed transaction for multi-transactions
    55  	PendingAS                 // in pending DB or at least one transaction in pending for multi-transactions
    56  	CompleteAS                // success status
    57  	FinalizedAS               // all multi-transactions have success status
    58  )
    59  
    60  func allActivityStatusesFilter() []Status {
    61  	return []Status{}
    62  }
    63  
    64  type TokenType int
    65  
    66  const (
    67  	Native TokenType = iota
    68  	Erc20
    69  	Erc721
    70  	Erc1155
    71  )
    72  
    73  // Token supports all tokens. Some fields might be optional, depending on the TokenType
    74  type Token struct {
    75  	TokenType TokenType `json:"tokenType"`
    76  	// ChainID is used for TokenType.Native only to lookup the symbol, all chains will be included in the token filter
    77  	ChainID common.ChainID `json:"chainId"`
    78  	Address eth.Address    `json:"address,omitempty"`
    79  	TokenID *hexutil.Big   `json:"tokenId,omitempty"`
    80  }
    81  
    82  func allTokensFilter() []Token {
    83  	return []Token{}
    84  }
    85  
    86  func allNetworksFilter() []common.ChainID {
    87  	return []common.ChainID{}
    88  }
    89  
    90  type Filter struct {
    91  	Period                Period        `json:"period"`
    92  	Types                 []Type        `json:"types"`
    93  	Statuses              []Status      `json:"statuses"`
    94  	CounterpartyAddresses []eth.Address `json:"counterpartyAddresses"`
    95  
    96  	// Tokens
    97  	Assets                []Token `json:"assets"`
    98  	Collectibles          []Token `json:"collectibles"`
    99  	FilterOutAssets       bool    `json:"filterOutAssets"`
   100  	FilterOutCollectibles bool    `json:"filterOutCollectibles"`
   101  }
   102  
   103  func (f *Filter) IsEmpty() bool {
   104  	return f.Period.StartTimestamp == NoLimitTimestampForPeriod &&
   105  		f.Period.EndTimestamp == NoLimitTimestampForPeriod &&
   106  		len(f.Types) == 0 &&
   107  		len(f.Statuses) == 0 &&
   108  		len(f.CounterpartyAddresses) == 0 &&
   109  		len(f.Assets) == 0 &&
   110  		len(f.Collectibles) == 0 &&
   111  		!f.FilterOutAssets &&
   112  		!f.FilterOutCollectibles
   113  }
   114  
   115  func GetRecipients(ctx context.Context, db *sql.DB, chainIDs []common.ChainID, addresses []eth.Address, offset int, limit int) (recipients []eth.Address, hasMore bool, err error) {
   116  	filterAllAddresses := len(addresses) == 0
   117  	involvedAddresses := noEntriesInTmpTableSQLValues
   118  	if !filterAllAddresses {
   119  		involvedAddresses = joinAddresses(addresses)
   120  	}
   121  
   122  	includeAllNetworks := len(chainIDs) == 0
   123  	networks := noEntriesInTmpTableSQLValues
   124  	if !includeAllNetworks {
   125  		networks = joinItems(chainIDs, nil)
   126  	}
   127  
   128  	queryString := fmt.Sprintf(recipientsQueryFormatString, involvedAddresses, networks)
   129  
   130  	rows, err := db.QueryContext(ctx, queryString, filterAllAddresses, includeAllNetworks, transactions.Pending, limit, offset)
   131  	if err != nil {
   132  		return nil, false, err
   133  	}
   134  	defer rows.Close()
   135  
   136  	var entries []eth.Address
   137  	for rows.Next() {
   138  		var toAddress eth.Address
   139  		var timestamp int64
   140  		err := rows.Scan(&toAddress, &timestamp)
   141  		if err != nil {
   142  			return nil, false, err
   143  		}
   144  		entries = append(entries, toAddress)
   145  	}
   146  
   147  	if err = rows.Err(); err != nil {
   148  		return nil, false, err
   149  	}
   150  
   151  	hasMore = len(entries) == limit
   152  
   153  	return entries, hasMore, nil
   154  }
   155  
   156  func GetOldestTimestamp(ctx context.Context, db *sql.DB, addresses []eth.Address) (timestamp uint64, err error) {
   157  	filterAllAddresses := len(addresses) == 0
   158  	involvedAddresses := noEntriesInTmpTableSQLValues
   159  	if !filterAllAddresses {
   160  		involvedAddresses = joinAddresses(addresses)
   161  	}
   162  
   163  	queryString := fmt.Sprintf(oldestTimestampQueryFormatString, involvedAddresses)
   164  
   165  	row := db.QueryRowContext(ctx, queryString, filterAllAddresses)
   166  	var fromAddress, toAddress sql.NullString
   167  	err = row.Scan(&fromAddress, &toAddress, &timestamp)
   168  	if err == sql.ErrNoRows {
   169  		return 0, nil
   170  	}
   171  
   172  	if err != nil {
   173  		return 0, err
   174  	}
   175  
   176  	return timestamp, nil
   177  }
   178  
   179  func GetActivityCollectibles(ctx context.Context, db *sql.DB, chainIDs []common.ChainID, owners []eth.Address, offset int, limit int) ([]thirdparty.CollectibleUniqueID, error) {
   180  	filterAllAddresses := len(owners) == 0
   181  	involvedAddresses := noEntriesInTmpTableSQLValues
   182  	if !filterAllAddresses {
   183  		involvedAddresses = joinAddresses(owners)
   184  	}
   185  
   186  	includeAllNetworks := len(chainIDs) == 0
   187  	networks := noEntriesInTmpTableSQLValues
   188  	if !includeAllNetworks {
   189  		networks = joinItems(chainIDs, nil)
   190  	}
   191  
   192  	queryString := fmt.Sprintf(getCollectiblesQueryFormatString, involvedAddresses, networks)
   193  
   194  	rows, err := db.QueryContext(ctx, queryString, filterAllAddresses, includeAllNetworks, limit, offset)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	defer rows.Close()
   199  
   200  	return thirdparty.RowsToCollectibles(rows)
   201  }