2023-04-21 11:59:29 +00:00
package activity
import (
2023-06-08 23:52:45 +00:00
"context"
2023-04-21 11:59:29 +00:00
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
2023-06-14 19:43:28 +00:00
"math/big"
2023-04-21 11:59:29 +00:00
"strconv"
"strings"
2023-09-05 12:37:00 +00:00
// used for embedding the sql query in the binary
_ "embed"
2023-05-11 07:50:07 +00:00
eth "github.com/ethereum/go-ethereum/common"
2023-06-14 19:43:28 +00:00
"github.com/ethereum/go-ethereum/common/hexutil"
2023-05-11 07:50:07 +00:00
2024-10-28 20:54:17 +00:00
"github.com/status-im/status-go/logutils"
2023-08-11 17:28:46 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
2023-05-11 07:50:07 +00:00
"github.com/status-im/status-go/services/wallet/common"
2023-08-11 17:28:46 +00:00
"github.com/status-im/status-go/services/wallet/thirdparty"
2023-04-21 11:59:29 +00:00
"github.com/status-im/status-go/services/wallet/transfer"
2023-08-01 18:50:30 +00:00
"github.com/status-im/status-go/transactions"
2023-05-11 07:50:07 +00:00
"golang.org/x/exp/constraints"
2023-04-21 11:59:29 +00:00
)
type PayloadType = int
2023-09-06 08:03:50 +00:00
// Beware: please update multiTransactionTypeToActivityType if changing this enum
2023-04-21 11:59:29 +00:00
const (
MultiTransactionPT PayloadType = iota + 1
SimpleTransactionPT
PendingTransactionPT
)
2023-06-20 02:50:49 +00:00
var (
ZeroAddress = eth . Address { }
)
2024-11-25 20:44:39 +00:00
type TransferType = int64
2023-07-18 15:57:44 +00:00
const (
TransferTypeEth TransferType = iota + 1
TransferTypeErc20
TransferTypeErc721
TransferTypeErc1155
)
2024-11-25 20:44:39 +00:00
const (
L1FinalizationDuration = 960 // A block on layer 1 is every 12s, finalization require 64 blocks. A buffer of 16 blocks is added to not create false positives.
L2FinalizationDuration = 648000 // 7.5 days in seconds for layer 2 finalization. 0.5 day is buffer to not create false positive.
)
const (
NoLimit = 0
)
2023-04-21 11:59:29 +00:00
type Entry struct {
2024-11-25 20:44:39 +00:00
payloadType PayloadType
transaction * transfer . TransactionIdentity // ID for SimpleTransactionPT and PendingTransactionPT. Origin transaction for MultiTransactionPT
id common . MultiTransactionIDType // ID for MultiTransactionPT
transactions [ ] * transfer . TransactionIdentity // List of transactions for MultiTransactionPT
timestamp int64
activityType Type
activityStatus Status
amountOut * hexutil . Big // Used for activityType SendAT, SwapAT, BridgeAT
amountIn * hexutil . Big // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT, ApproveAT
tokenOut * Token // Used for activityType SendAT, SwapAT, BridgeAT
tokenIn * Token // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT, ApproveAT
symbolOut * string
symbolIn * string
sender * eth . Address
recipient * eth . Address
chainIDOut * common . ChainID
chainIDIn * common . ChainID
transferType * TransferType
contractAddress * eth . Address // Used for contract deployment
communityID * string
interactedContractAddress * eth . Address
approvalSpender * eth . Address
2024-01-26 04:31:18 +00:00
2024-02-08 23:13:12 +00:00
isNew bool // isNew is used to indicate if the entry is newer than session start (changed state also)
2023-04-21 11:59:29 +00:00
}
2024-11-29 14:37:56 +00:00
func ( e * Entry ) Key ( ) string {
if e . payloadType == MultiTransactionPT {
key := fmt . Sprintf ( "%d" , e . id )
for _ , t := range e . transactions {
key += fmt . Sprintf ( "-%s" , t . Key ( ) )
}
return key
}
return e . transaction . Key ( )
}
2024-01-08 21:24:30 +00:00
// Only used for JSON marshalling
2023-08-11 17:28:46 +00:00
type EntryData struct {
2024-11-25 20:44:39 +00:00
PayloadType PayloadType ` json:"payloadType" `
2024-11-29 14:37:56 +00:00
Key string ` json:"key" `
2024-11-25 20:44:39 +00:00
Transaction * transfer . TransactionIdentity ` json:"transaction,omitempty" `
ID * common . MultiTransactionIDType ` json:"id,omitempty" `
Transactions [ ] * transfer . TransactionIdentity ` json:"transactions,omitempty" `
Timestamp * int64 ` json:"timestamp,omitempty" `
ActivityType * Type ` json:"activityType,omitempty" `
ActivityStatus * Status ` json:"activityStatus,omitempty" `
AmountOut * hexutil . Big ` json:"amountOut,omitempty" `
AmountIn * hexutil . Big ` json:"amountIn,omitempty" `
TokenOut * Token ` json:"tokenOut,omitempty" `
TokenIn * Token ` json:"tokenIn,omitempty" `
SymbolOut * string ` json:"symbolOut,omitempty" `
SymbolIn * string ` json:"symbolIn,omitempty" `
Sender * eth . Address ` json:"sender,omitempty" `
Recipient * eth . Address ` json:"recipient,omitempty" `
ChainIDOut * common . ChainID ` json:"chainIdOut,omitempty" `
ChainIDIn * common . ChainID ` json:"chainIdIn,omitempty" `
TransferType * TransferType ` json:"transferType,omitempty" `
ContractAddress * eth . Address ` json:"contractAddress,omitempty" `
CommunityID * string ` json:"communityId,omitempty" `
InteractedContractAddress * eth . Address ` json:"interactedContractAddress,omitempty" `
ApprovalSpender * eth . Address ` json:"approvalSpender,omitempty" `
2023-08-11 17:28:46 +00:00
2024-01-26 04:31:18 +00:00
IsNew * bool ` json:"isNew,omitempty" `
2023-08-11 17:28:46 +00:00
NftName * string ` json:"nftName,omitempty" `
NftURL * string ` json:"nftUrl,omitempty" `
2023-04-21 11:59:29 +00:00
}
func ( e * Entry ) MarshalJSON ( ) ( [ ] byte , error ) {
2023-08-11 17:28:46 +00:00
data := EntryData {
2024-11-29 14:37:56 +00:00
Key : e . Key ( ) ,
2024-11-25 20:44:39 +00:00
Timestamp : & e . timestamp ,
ActivityType : & e . activityType ,
ActivityStatus : & e . activityStatus ,
AmountOut : e . amountOut ,
AmountIn : e . amountIn ,
TokenOut : e . tokenOut ,
TokenIn : e . tokenIn ,
SymbolOut : e . symbolOut ,
SymbolIn : e . symbolIn ,
Sender : e . sender ,
Recipient : e . recipient ,
ChainIDOut : e . chainIDOut ,
ChainIDIn : e . chainIDIn ,
TransferType : e . transferType ,
ContractAddress : e . contractAddress ,
CommunityID : e . communityID ,
InteractedContractAddress : e . interactedContractAddress ,
ApprovalSpender : e . approvalSpender ,
2023-08-11 17:28:46 +00:00
}
if e . payloadType == MultiTransactionPT {
data . ID = common . NewAndSet ( e . id )
2024-11-25 20:44:39 +00:00
data . Transactions = e . transactions
2023-08-11 17:28:46 +00:00
} else {
data . Transaction = e . transaction
}
data . PayloadType = e . payloadType
2024-01-26 04:31:18 +00:00
if e . isNew {
data . IsNew = & e . isNew
}
2023-08-11 17:28:46 +00:00
return json . Marshal ( data )
2023-04-21 11:59:29 +00:00
}
func ( e * Entry ) UnmarshalJSON ( data [ ] byte ) error {
2023-08-11 17:28:46 +00:00
aux := EntryData { }
2023-04-21 11:59:29 +00:00
if err := json . Unmarshal ( data , & aux ) ; err != nil {
return err
}
2023-05-11 07:50:07 +00:00
e . payloadType = aux . PayloadType
2023-04-21 11:59:29 +00:00
e . transaction = aux . Transaction
2023-08-11 17:28:46 +00:00
if aux . ID != nil {
e . id = * aux . ID
}
2024-11-25 20:44:39 +00:00
e . transactions = aux . Transactions
2023-08-11 17:28:46 +00:00
if aux . Timestamp != nil {
e . timestamp = * aux . Timestamp
}
if aux . ActivityType != nil {
e . activityType = * aux . ActivityType
}
if aux . ActivityStatus != nil {
e . activityStatus = * aux . ActivityStatus
}
2023-06-14 19:43:28 +00:00
e . amountOut = aux . AmountOut
e . amountIn = aux . AmountIn
2023-07-18 15:57:44 +00:00
e . tokenOut = aux . TokenOut
e . tokenIn = aux . TokenIn
2023-08-28 09:02:05 +00:00
e . symbolOut = aux . SymbolOut
e . symbolIn = aux . SymbolIn
2023-07-18 15:57:44 +00:00
e . sender = aux . Sender
e . recipient = aux . Recipient
e . chainIDOut = aux . ChainIDOut
e . chainIDIn = aux . ChainIDIn
e . transferType = aux . TransferType
2024-02-29 10:22:14 +00:00
e . communityID = aux . CommunityID
2024-11-25 20:44:39 +00:00
e . interactedContractAddress = aux . InteractedContractAddress
e . approvalSpender = aux . ApprovalSpender
2024-01-26 04:31:18 +00:00
e . isNew = aux . IsNew != nil && * aux . IsNew
2023-04-21 11:59:29 +00:00
return nil
}
2023-07-18 15:57:44 +00:00
func newActivityEntryWithPendingTransaction ( transaction * transfer . TransactionIdentity , timestamp int64 , activityType Type , activityStatus Status ) Entry {
return newActivityEntryWithTransaction ( true , transaction , timestamp , activityType , activityStatus )
2023-05-19 15:31:45 +00:00
}
2023-07-18 15:57:44 +00:00
func newActivityEntryWithSimpleTransaction ( transaction * transfer . TransactionIdentity , timestamp int64 , activityType Type , activityStatus Status ) Entry {
return newActivityEntryWithTransaction ( false , transaction , timestamp , activityType , activityStatus )
2023-05-19 15:31:45 +00:00
}
2023-07-18 15:57:44 +00:00
func newActivityEntryWithTransaction ( pending bool , transaction * transfer . TransactionIdentity , timestamp int64 , activityType Type , activityStatus Status ) Entry {
2023-05-19 15:31:45 +00:00
payloadType := SimpleTransactionPT
if pending {
payloadType = PendingTransactionPT
2023-04-21 11:59:29 +00:00
}
return Entry {
2023-05-11 07:50:07 +00:00
payloadType : payloadType ,
transaction : transaction ,
id : 0 ,
timestamp : timestamp ,
activityType : activityType ,
activityStatus : activityStatus ,
2023-04-21 11:59:29 +00:00
}
}
2024-03-12 09:15:30 +00:00
func NewActivityEntryWithMultiTransaction ( id common . MultiTransactionIDType , timestamp int64 , activityType Type , activityStatus Status ) Entry {
2023-04-21 11:59:29 +00:00
return Entry {
2023-05-11 07:50:07 +00:00
payloadType : MultiTransactionPT ,
id : id ,
timestamp : timestamp ,
activityType : activityType ,
activityStatus : activityStatus ,
2023-04-21 11:59:29 +00:00
}
}
2023-05-11 07:50:07 +00:00
func ( e * Entry ) PayloadType ( ) PayloadType {
return e . payloadType
2023-04-21 11:59:29 +00:00
}
2023-08-11 17:28:46 +00:00
func ( e * Entry ) isNFT ( ) bool {
tt := e . transferType
return tt != nil && ( * tt == TransferTypeErc721 || * tt == TransferTypeErc1155 ) && ( ( e . tokenIn != nil && e . tokenIn . TokenID != nil ) || ( e . tokenOut != nil && e . tokenOut . TokenID != nil ) )
}
func tokenIDToWalletBigInt ( tokenID * hexutil . Big ) * bigint . BigInt {
if tokenID == nil {
return nil
}
bi := new ( big . Int ) . Set ( ( * big . Int ) ( tokenID ) )
return & bigint . BigInt { Int : bi }
}
func ( e * Entry ) anyIdentity ( ) * thirdparty . CollectibleUniqueID {
if e . tokenIn != nil {
return & thirdparty . CollectibleUniqueID {
ContractID : thirdparty . ContractID {
ChainID : e . tokenIn . ChainID ,
Address : e . tokenIn . Address ,
} ,
TokenID : tokenIDToWalletBigInt ( e . tokenIn . TokenID ) ,
}
} else if e . tokenOut != nil {
return & thirdparty . CollectibleUniqueID {
ContractID : thirdparty . ContractID {
ChainID : e . tokenOut . ChainID ,
Address : e . tokenOut . Address ,
} ,
TokenID : tokenIDToWalletBigInt ( e . tokenOut . TokenID ) ,
}
}
return nil
}
2024-01-26 04:31:18 +00:00
func ( e * Entry ) getIdentity ( ) EntryIdentity {
return EntryIdentity {
payloadType : e . payloadType ,
id : e . id ,
transaction : e . transaction ,
}
}
2023-04-21 11:59:29 +00:00
func multiTransactionTypeToActivityType ( mtType transfer . MultiTransactionType ) Type {
if mtType == transfer . MultiTransactionSend {
return SendAT
} else if mtType == transfer . MultiTransactionSwap {
return SwapAT
} else if mtType == transfer . MultiTransactionBridge {
return BridgeAT
2024-06-05 03:35:43 +00:00
} else if mtType == transfer . MultiTransactionApprove {
return ApproveAT
2023-04-21 11:59:29 +00:00
}
panic ( "unknown multi transaction type" )
}
2023-05-11 07:50:07 +00:00
func sliceContains [ T constraints . Ordered ] ( slice [ ] T , item T ) bool {
2023-04-21 11:59:29 +00:00
for _ , a := range slice {
if a == item {
return true
}
}
return false
}
2023-06-13 09:25:23 +00:00
func sliceChecksCondition [ T any ] ( slice [ ] T , condition func ( * T ) bool ) bool {
for i := range slice {
if condition ( & slice [ i ] ) {
return true
}
}
return false
}
2023-05-11 07:50:07 +00:00
func joinItems [ T interface { } ] ( items [ ] T , itemConversion func ( T ) string ) string {
if len ( items ) == 0 {
return ""
}
2023-04-21 11:59:29 +00:00
var sb strings . Builder
2023-05-11 07:50:07 +00:00
if itemConversion == nil {
itemConversion = func ( item T ) string {
return fmt . Sprintf ( "%v" , item )
2023-04-21 11:59:29 +00:00
}
}
2023-05-11 07:50:07 +00:00
for i , item := range items {
2023-04-21 11:59:29 +00:00
if i == 0 {
2023-05-11 07:50:07 +00:00
sb . WriteString ( "(" )
2023-04-21 11:59:29 +00:00
} else {
2023-05-11 07:50:07 +00:00
sb . WriteString ( "),(" )
2023-04-21 11:59:29 +00:00
}
2023-05-11 07:50:07 +00:00
sb . WriteString ( itemConversion ( item ) )
2023-04-21 11:59:29 +00:00
}
2023-05-11 07:50:07 +00:00
sb . WriteString ( ")" )
2023-04-21 11:59:29 +00:00
return sb . String ( )
}
2023-05-11 07:50:07 +00:00
func joinAddresses ( addresses [ ] eth . Address ) string {
return joinItems ( addresses , func ( a eth . Address ) string {
2023-09-14 21:50:51 +00:00
return fmt . Sprintf ( "X'%s'" , hex . EncodeToString ( a [ : ] ) )
2023-05-11 07:50:07 +00:00
} )
}
2023-04-21 11:59:29 +00:00
func activityTypesToMultiTransactionTypes ( trTypes [ ] Type ) [ ] transfer . MultiTransactionType {
mtTypes := make ( [ ] transfer . MultiTransactionType , 0 , len ( trTypes ) )
for _ , t := range trTypes {
var mtType transfer . MultiTransactionType
if t == SendAT {
mtType = transfer . MultiTransactionSend
} else if t == SwapAT {
mtType = transfer . MultiTransactionSwap
} else if t == BridgeAT {
mtType = transfer . MultiTransactionBridge
2024-06-05 03:35:43 +00:00
} else if t == ApproveAT {
mtType = transfer . MultiTransactionApprove
2023-04-21 11:59:29 +00:00
} else {
continue
}
mtTypes = append ( mtTypes , mtType )
}
return mtTypes
}
2023-05-11 07:50:07 +00:00
const (
fromTrType = byte ( 1 )
2023-05-28 10:40:50 +00:00
toTrType = byte ( 2 )
2023-05-11 07:50:07 +00:00
2023-09-21 06:58:36 +00:00
noEntriesInTmpTableSQLValues = "(NULL)"
noEntriesInTwoColumnsTmpTableSQLValues = "(NULL, NULL)"
noEntriesInThreeColumnsTmpTableSQLValues = "(NULL, NULL, NULL)"
2023-05-11 07:50:07 +00:00
)
2023-09-05 12:37:00 +00:00
//go:embed filter.sql
var queryFormatString string
2023-10-11 05:10:08 +00:00
var mintATQuery = "SELECT hash FROM input_data WHERE method IN ('mint', 'mintToken')"
2023-09-05 12:37:00 +00:00
2023-06-13 09:25:23 +00:00
type FilterDependencies struct {
2023-09-12 10:19:15 +00:00
db * sql . DB
2023-08-28 09:02:05 +00:00
// use token.TokenType, token.ChainID and token.Address to find the available symbol
tokenSymbol func ( token Token ) string
// use the chainID and symbol to look up token.TokenType and token.Address. Return nil if not found
2023-06-13 09:25:23 +00:00
tokenFromSymbol func ( chainID * common . ChainID , symbol string ) * Token
2023-09-20 08:30:31 +00:00
// use to get current timestamp
currentTimestamp func ( ) int64
2023-06-14 19:43:28 +00:00
}
2023-09-05 12:37:00 +00:00
// getActivityEntries queries the transfers, pending_transactions, and multi_transactions tables based on filter parameters and arguments
2023-05-11 07:50:07 +00:00
// it returns metadata for all entries ordered by timestamp column
//
2023-09-12 10:19:15 +00:00
// addresses are mandatory and used to detect activity types SendAT and ReceiveAT for transfers entries
//
// allAddresses optimization indicates if the passed addresses include all the owners in the wallet DB
//
2023-05-11 07:50:07 +00:00
// Adding a no-limit option was never considered or required.
2023-09-12 10:19:15 +00:00
func getActivityEntries ( ctx context . Context , deps FilterDependencies , addresses [ ] eth . Address , allAddresses bool , chainIDs [ ] common . ChainID , filter Filter , offset int , limit int ) ( [ ] Entry , error ) {
if len ( addresses ) == 0 {
return nil , errors . New ( "no addresses provided" )
}
2023-06-13 09:25:23 +00:00
includeAllTokenTypeAssets := len ( filter . Assets ) == 0 && ! filter . FilterOutAssets
// Used for symbol bearing tables multi_transactions and pending_transactions
assetsTokenCodes := noEntriesInTmpTableSQLValues
// Used for identity bearing tables transfers
assetsERC20 := noEntriesInTwoColumnsTmpTableSQLValues
if ! includeAllTokenTypeAssets && ! filter . FilterOutAssets {
symbolsSet := make ( map [ string ] struct { } )
var symbols [ ] string
for _ , item := range filter . Assets {
symbol := deps . tokenSymbol ( item )
if _ , ok := symbolsSet [ symbol ] ; ! ok {
symbols = append ( symbols , symbol )
symbolsSet [ symbol ] = struct { } { }
}
}
assetsTokenCodes = joinItems ( symbols , func ( s string ) string {
return fmt . Sprintf ( "'%s'" , s )
} )
if sliceChecksCondition ( filter . Assets , func ( item * Token ) bool { return item . TokenType == Erc20 } ) {
assetsERC20 = joinItems ( filter . Assets , func ( item Token ) string {
if item . TokenType == Erc20 {
2023-09-14 21:50:51 +00:00
return fmt . Sprintf ( "%d, X'%s'" , item . ChainID , item . Address . Hex ( ) [ 2 : ] )
2023-06-13 09:25:23 +00:00
}
return ""
} )
}
2023-05-11 07:50:07 +00:00
}
2023-09-21 06:58:36 +00:00
includeAllCollectibles := len ( filter . Collectibles ) == 0 && ! filter . FilterOutCollectibles
assetsERC721 := noEntriesInThreeColumnsTmpTableSQLValues
if ! includeAllCollectibles && ! filter . FilterOutCollectibles {
assetsERC721 = joinItems ( filter . Collectibles , func ( item Token ) string {
2023-09-14 21:50:51 +00:00
tokenID := item . TokenID . String ( ) [ 2 : ]
address := item . Address . Hex ( ) [ 2 : ]
// SQLite mandates that byte length is an even number which hexutil.EncodeBig doesn't guarantee
2023-09-21 06:58:36 +00:00
if len ( tokenID ) % 2 == 1 {
tokenID = "0" + tokenID
}
2023-09-14 21:50:51 +00:00
return fmt . Sprintf ( "%d, X'%s', X'%s'" , item . ChainID , tokenID , address )
2023-09-21 06:58:36 +00:00
} )
}
2023-06-13 09:25:23 +00:00
// construct chain IDs
2023-05-11 07:50:07 +00:00
includeAllNetworks := len ( chainIDs ) == 0
networks := noEntriesInTmpTableSQLValues
if ! includeAllNetworks {
networks = joinItems ( chainIDs , nil )
}
2023-04-21 11:59:29 +00:00
2024-10-15 07:29:21 +00:00
layer2Chains := [ ] uint64 { common . OptimismMainnet , common . OptimismSepolia , common . ArbitrumMainnet , common . ArbitrumSepolia }
2023-09-20 08:30:31 +00:00
layer2Networks := joinItems ( layer2Chains , func ( chainID uint64 ) string {
return fmt . Sprintf ( "%d" , chainID )
} )
2023-04-21 11:59:29 +00:00
startFilterDisabled := ! ( filter . Period . StartTimestamp > 0 )
endFilterDisabled := ! ( filter . Period . EndTimestamp > 0 )
2023-05-11 07:50:07 +00:00
filterActivityTypeAll := len ( filter . Types ) == 0
filterAllToAddresses := len ( filter . CounterpartyAddresses ) == 0
includeAllStatuses := len ( filter . Statuses ) == 0
2023-04-21 11:59:29 +00:00
2023-05-28 10:40:50 +00:00
filterStatusPending := false
filterStatusCompleted := false
filterStatusFailed := false
filterStatusFinalized := false
2023-05-11 07:50:07 +00:00
if ! includeAllStatuses {
2023-05-28 10:40:50 +00:00
filterStatusPending = sliceContains ( filter . Statuses , PendingAS )
filterStatusCompleted = sliceContains ( filter . Statuses , CompleteAS )
filterStatusFailed = sliceContains ( filter . Statuses , FailedAS )
filterStatusFinalized = sliceContains ( filter . Statuses , FinalizedAS )
2023-05-11 07:50:07 +00:00
}
2023-04-21 11:59:29 +00:00
2023-09-12 10:19:15 +00:00
involvedAddresses := joinAddresses ( addresses )
2023-05-11 07:50:07 +00:00
toAddresses := noEntriesInTmpTableSQLValues
if ! filterAllToAddresses {
toAddresses = joinAddresses ( filter . CounterpartyAddresses )
2023-04-21 11:59:29 +00:00
}
mtTypes := activityTypesToMultiTransactionTypes ( filter . Types )
2023-05-11 07:50:07 +00:00
joinedMTTypes := joinItems ( mtTypes , func ( t transfer . MultiTransactionType ) string {
return strconv . Itoa ( int ( t ) )
} )
2023-04-21 11:59:29 +00:00
2023-10-11 05:10:08 +00:00
inputDataMethods := make ( [ ] string , 0 )
if includeAllStatuses || sliceContains ( filter . Types , MintAT ) || sliceContains ( filter . Types , ReceiveAT ) {
inputDataRows , err := deps . db . QueryContext ( ctx , mintATQuery )
if err != nil {
return nil , err
}
for inputDataRows . Next ( ) {
var inputData sql . NullString
err := inputDataRows . Scan ( & inputData )
if err == nil && inputData . Valid {
inputDataMethods = append ( inputDataMethods , inputData . String )
}
}
}
2023-09-21 06:58:36 +00:00
queryString := fmt . Sprintf ( queryFormatString , involvedAddresses , toAddresses , assetsTokenCodes , assetsERC20 , assetsERC721 , networks ,
2023-10-11 05:10:08 +00:00
layer2Networks , mintATQuery , joinedMTTypes )
2023-09-12 10:19:15 +00:00
// The duplicated temporary table UNION with CTE acts as an optimization
// As soon as we use filter_addresses CTE or filter_addresses_table temp table
// or switch them alternatively for JOIN or IN clauses the performance drops significantly
2023-09-14 21:50:51 +00:00
_ , err := deps . db . Exec ( fmt . Sprintf ( "DROP TABLE IF EXISTS filter_addresses_table; CREATE TEMP TABLE filter_addresses_table (address VARCHAR PRIMARY KEY); INSERT INTO filter_addresses_table (address) VALUES %s;\n" , involvedAddresses ) )
2023-08-11 11:25:14 +00:00
if err != nil {
return nil , err
}
2023-06-13 09:25:23 +00:00
rows , err := deps . db . QueryContext ( ctx , queryString ,
2023-04-21 11:59:29 +00:00
startFilterDisabled , filter . Period . StartTimestamp , endFilterDisabled , filter . Period . EndTimestamp ,
2023-05-11 07:50:07 +00:00
filterActivityTypeAll , sliceContains ( filter . Types , SendAT ) , sliceContains ( filter . Types , ReceiveAT ) ,
2023-09-11 09:54:37 +00:00
sliceContains ( filter . Types , ContractDeploymentAT ) , sliceContains ( filter . Types , MintAT ) ,
2023-09-06 19:15:07 +00:00
transfer . MultiTransactionSend ,
2023-05-28 10:40:50 +00:00
fromTrType , toTrType ,
2023-09-12 10:19:15 +00:00
allAddresses , filterAllToAddresses ,
2023-05-28 10:40:50 +00:00
includeAllStatuses , filterStatusCompleted , filterStatusFailed , filterStatusFinalized , filterStatusPending ,
2023-09-20 08:30:31 +00:00
FailedAS , CompleteAS , FinalizedAS , PendingAS ,
2023-05-28 10:40:50 +00:00
includeAllTokenTypeAssets ,
2023-09-21 06:58:36 +00:00
includeAllCollectibles ,
2023-05-11 07:50:07 +00:00
includeAllNetworks ,
2023-08-01 18:50:30 +00:00
transactions . Pending ,
2023-09-20 08:30:31 +00:00
deps . currentTimestamp ( ) ,
2024-11-25 20:44:39 +00:00
L2FinalizationDuration ,
L1FinalizationDuration ,
2023-04-21 11:59:29 +00:00
limit , offset )
if err != nil {
return nil , err
}
defer rows . Close ( )
var entries [ ] Entry
for rows . Next ( ) {
var transferHash , pendingHash [ ] byte
2023-07-18 15:57:44 +00:00
var chainID , outChainIDDB , inChainIDDB , multiTxID , aggregatedCount sql . NullInt64
2023-04-21 11:59:29 +00:00
var timestamp int64
2023-05-11 07:50:07 +00:00
var dbMtType , dbTrType sql . NullByte
var toAddress , fromAddress eth . Address
2023-08-04 11:40:36 +00:00
var toAddressDB , ownerAddressDB , contractAddressDB , dbTokenID sql . RawBytes
2023-07-20 14:04:30 +00:00
var tokenAddress , contractAddress * eth . Address
2023-05-28 10:40:50 +00:00
var aggregatedStatus int
2023-06-14 19:43:28 +00:00
var dbTrAmount sql . NullString
2024-01-31 19:06:03 +00:00
dbPTrAmount := new ( big . Int )
2023-07-18 15:57:44 +00:00
var dbMtFromAmount , dbMtToAmount , contractType sql . NullString
2023-06-20 02:50:49 +00:00
var tokenCode , fromTokenCode , toTokenCode sql . NullString
2024-02-29 10:22:14 +00:00
var methodHash , communityID sql . NullString
2023-07-20 14:04:30 +00:00
var transferType * TransferType
2023-10-26 18:39:31 +00:00
var communityMintEventDB sql . NullBool
var communityMintEvent bool
2023-06-13 09:25:23 +00:00
err := rows . Scan ( & transferHash , & pendingHash , & chainID , & multiTxID , & timestamp , & dbMtType , & dbTrType , & fromAddress ,
2024-01-31 19:06:03 +00:00
& toAddressDB , & ownerAddressDB , & dbTrAmount , ( * bigint . SQLBigIntBytes ) ( dbPTrAmount ) , & dbMtFromAmount , & dbMtToAmount , & aggregatedStatus , & aggregatedCount ,
2023-10-11 05:10:08 +00:00
& tokenAddress , & dbTokenID , & tokenCode , & fromTokenCode , & toTokenCode , & outChainIDDB , & inChainIDDB , & contractType ,
2024-02-29 10:22:14 +00:00
& contractAddressDB , & methodHash , & communityMintEventDB , & communityID )
2023-04-21 11:59:29 +00:00
if err != nil {
return nil , err
}
2023-07-04 12:01:45 +00:00
if len ( toAddressDB ) > 0 {
toAddress = eth . BytesToAddress ( toAddressDB )
}
2023-07-20 14:04:30 +00:00
if contractType . Valid {
transferType = contractTypeFromDBType ( contractType . String )
}
2023-10-11 05:10:08 +00:00
2023-10-26 18:39:31 +00:00
if communityMintEventDB . Valid {
communityMintEvent = communityMintEventDB . Bool
}
2023-07-20 14:04:30 +00:00
if len ( contractAddressDB ) > 0 {
contractAddress = new ( eth . Address )
* contractAddress = eth . BytesToAddress ( contractAddressDB )
}
2023-05-11 07:50:07 +00:00
getActivityType := func ( trType sql . NullByte ) ( activityType Type , filteredAddress eth . Address ) {
2023-05-28 10:40:50 +00:00
if trType . Valid {
if trType . Byte == fromTrType {
2023-07-20 14:04:30 +00:00
if toAddress == ZeroAddress && transferType != nil && * transferType == TransferTypeEth && contractAddress != nil && * contractAddress != ZeroAddress {
return ContractDeploymentAT , fromAddress
}
2023-05-28 10:40:50 +00:00
return SendAT , fromAddress
} else if trType . Byte == toTrType {
2023-10-11 05:10:08 +00:00
at := ReceiveAT
if fromAddress == ZeroAddress && transferType != nil {
2024-07-08 09:02:06 +00:00
if * transferType == TransferTypeErc721 || * transferType == TransferTypeErc1155 || ( * transferType == TransferTypeErc20 && methodHash . Valid && ( communityMintEvent || sliceContains ( inputDataMethods , methodHash . String ) ) ) {
2023-10-11 05:10:08 +00:00
at = MintAT
}
2023-08-04 10:47:22 +00:00
}
2023-10-11 05:10:08 +00:00
return at , toAddress
2023-05-28 10:40:50 +00:00
}
2023-05-11 07:50:07 +00:00
}
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Warn ( fmt . Sprintf ( "unexpected activity type. Missing from [%s] or to [%s] in addresses?" , fromAddress , toAddress ) )
2023-05-11 07:50:07 +00:00
return ReceiveAT , toAddress
}
2023-05-28 10:40:50 +00:00
// Can be mapped directly because the values are injected into the query
activityStatus := Status ( aggregatedStatus )
2023-07-18 15:57:44 +00:00
var outChainID , inChainID * common . ChainID
2023-04-21 11:59:29 +00:00
var entry Entry
2023-08-11 17:28:46 +00:00
var tokenID * hexutil . Big
2023-08-04 11:40:36 +00:00
if len ( dbTokenID ) > 0 {
2023-08-11 17:28:46 +00:00
tokenID = ( * hexutil . Big ) ( new ( big . Int ) . SetBytes ( dbTokenID ) )
2023-08-04 11:40:36 +00:00
}
2023-07-20 14:04:30 +00:00
2023-04-21 11:59:29 +00:00
if transferHash != nil && chainID . Valid {
2023-08-11 17:28:46 +00:00
// Process `transfers` row
2023-06-13 09:25:23 +00:00
// Extract activity type: SendAT/ReceiveAT
2023-07-04 22:21:07 +00:00
activityType , _ := getActivityType ( dbTrType )
2023-06-13 09:25:23 +00:00
2023-07-04 22:21:07 +00:00
ownerAddress := eth . BytesToAddress ( ownerAddressDB )
2024-01-31 19:06:03 +00:00
inAmount , outAmount := getTrInAndOutAmounts ( activityType , dbTrAmount , dbPTrAmount )
2023-06-13 09:25:23 +00:00
2023-07-18 15:57:44 +00:00
// Extract tokens and chains
2024-07-04 11:51:43 +00:00
var tokenContractAddress eth . Address
2023-06-20 02:50:49 +00:00
if tokenAddress != nil && * tokenAddress != ZeroAddress {
2024-07-04 11:51:43 +00:00
tokenContractAddress = * tokenAddress
}
involvedToken := & Token {
TokenType : transferTypeToTokenType ( transferType ) ,
ChainID : common . ChainID ( chainID . Int64 ) ,
Address : tokenContractAddress ,
TokenID : tokenID ,
2023-06-13 09:25:23 +00:00
}
2023-07-20 14:04:30 +00:00
2023-08-28 09:02:05 +00:00
entry = newActivityEntryWithSimpleTransaction (
& transfer . TransactionIdentity { ChainID : common . ChainID ( chainID . Int64 ) ,
Hash : eth . BytesToHash ( transferHash ) ,
Address : ownerAddress ,
} ,
timestamp , activityType , activityStatus ,
)
// Extract tokens
2023-09-11 09:54:37 +00:00
if activityType == SendAT || activityType == ContractDeploymentAT {
2023-08-28 09:02:05 +00:00
entry . tokenOut = involvedToken
2023-07-18 15:57:44 +00:00
outChainID = new ( common . ChainID )
* outChainID = common . ChainID ( chainID . Int64 )
2023-06-13 09:25:23 +00:00
} else {
2023-08-28 09:02:05 +00:00
entry . tokenIn = involvedToken
2023-07-18 15:57:44 +00:00
inChainID = new ( common . ChainID )
* inChainID = common . ChainID ( chainID . Int64 )
2023-06-13 09:25:23 +00:00
}
2023-08-28 09:02:05 +00:00
entry . symbolOut , entry . symbolIn = lookupAndFillInTokens ( deps , entry . tokenOut , entry . tokenIn )
2023-07-18 15:57:44 +00:00
// Complete the data
entry . amountOut = outAmount
entry . amountIn = inAmount
2023-04-21 11:59:29 +00:00
} else if pendingHash != nil && chainID . Valid {
2023-08-11 17:28:46 +00:00
// Process `pending_transactions` row
2023-07-05 12:36:23 +00:00
// Extract activity type: SendAT/ReceiveAT
2023-05-11 07:50:07 +00:00
activityType , _ := getActivityType ( dbTrType )
2023-06-13 09:25:23 +00:00
2024-01-31 19:06:03 +00:00
inAmount , outAmount := getTrInAndOutAmounts ( activityType , dbTrAmount , dbPTrAmount )
2023-06-13 09:25:23 +00:00
2023-07-18 15:57:44 +00:00
outChainID = new ( common . ChainID )
* outChainID = common . ChainID ( chainID . Int64 )
2023-06-13 09:25:23 +00:00
2023-07-05 12:36:23 +00:00
entry = newActivityEntryWithPendingTransaction (
& transfer . TransactionIdentity { ChainID : common . ChainID ( chainID . Int64 ) ,
Hash : eth . BytesToHash ( pendingHash ) ,
} ,
2023-08-28 09:02:05 +00:00
timestamp , activityType , activityStatus ,
)
// Extract tokens
if tokenCode . Valid {
cID := common . ChainID ( chainID . Int64 )
entry . tokenOut = deps . tokenFromSymbol ( & cID , tokenCode . String )
}
entry . symbolOut , entry . symbolIn = lookupAndFillInTokens ( deps , entry . tokenOut , nil )
2023-07-18 15:57:44 +00:00
// Complete the data
entry . amountOut = outAmount
entry . amountIn = inAmount
2023-08-28 09:02:05 +00:00
2023-04-21 11:59:29 +00:00
} else if multiTxID . Valid {
2023-08-11 17:28:46 +00:00
// Process `multi_transactions` row
2023-06-14 19:43:28 +00:00
mtInAmount , mtOutAmount := getMtInAndOutAmounts ( dbMtFromAmount , dbMtToAmount )
2023-06-13 09:25:23 +00:00
2024-06-05 03:35:43 +00:00
// Extract activity type: SendAT/SwapAT/BridgeAT/ApproveAT
2023-05-11 07:50:07 +00:00
activityType := multiTransactionTypeToActivityType ( transfer . MultiTransactionType ( dbMtType . Byte ) )
2023-06-13 09:25:23 +00:00
2023-07-18 15:57:44 +00:00
if outChainIDDB . Valid && outChainIDDB . Int64 != 0 {
outChainID = new ( common . ChainID )
* outChainID = common . ChainID ( outChainIDDB . Int64 )
}
if inChainIDDB . Valid && inChainIDDB . Int64 != 0 {
inChainID = new ( common . ChainID )
* inChainID = common . ChainID ( inChainIDDB . Int64 )
}
2024-03-12 09:15:30 +00:00
entry = NewActivityEntryWithMultiTransaction ( common . MultiTransactionIDType ( multiTxID . Int64 ) ,
2023-08-28 09:02:05 +00:00
timestamp , activityType , activityStatus )
2023-06-13 09:25:23 +00:00
// Extract tokens
if fromTokenCode . Valid {
2023-08-28 09:02:05 +00:00
entry . tokenOut = deps . tokenFromSymbol ( outChainID , fromTokenCode . String )
2023-08-30 16:14:57 +00:00
entry . symbolOut = common . NewAndSet ( fromTokenCode . String )
2023-06-13 09:25:23 +00:00
}
if toTokenCode . Valid {
2023-08-28 09:02:05 +00:00
entry . tokenIn = deps . tokenFromSymbol ( inChainID , toTokenCode . String )
2023-08-30 16:14:57 +00:00
entry . symbolIn = common . NewAndSet ( toTokenCode . String )
2023-06-13 09:25:23 +00:00
}
2023-07-18 15:57:44 +00:00
// Complete the data
entry . amountOut = mtOutAmount
entry . amountIn = mtInAmount
2023-04-21 11:59:29 +00:00
} else {
return nil , errors . New ( "invalid row data" )
}
2023-08-28 09:02:05 +00:00
2024-02-29 10:22:14 +00:00
if communityID . Valid {
entry . communityID = common . NewAndSet ( communityID . String )
}
2023-07-18 15:57:44 +00:00
// Complete common data
entry . recipient = & toAddress
entry . sender = & fromAddress
entry . recipient = & toAddress
entry . chainIDOut = outChainID
entry . chainIDIn = inChainID
2023-07-20 14:04:30 +00:00
entry . transferType = transferType
2023-07-18 15:57:44 +00:00
2023-04-21 11:59:29 +00:00
entries = append ( entries , entry )
}
if err = rows . Err ( ) ; err != nil {
return nil , err
}
return entries , nil
}
2023-06-13 09:25:23 +00:00
2024-01-31 19:06:03 +00:00
func getTrInAndOutAmounts ( activityType Type , trAmount sql . NullString , pTrAmount * big . Int ) ( inAmount * hexutil . Big , outAmount * hexutil . Big ) {
var amount * big . Int
ok := false
2023-06-13 09:25:23 +00:00
if trAmount . Valid {
2024-01-31 19:06:03 +00:00
amount , ok = new ( big . Int ) . SetString ( trAmount . String , 16 )
} else if pTrAmount != nil {
// Process pending transaction value
amount = pTrAmount
ok = true
2023-06-13 09:25:23 +00:00
} else {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Warn ( fmt . Sprintf ( "invalid transaction amount for type %d" , activityType ) )
2023-06-13 09:25:23 +00:00
}
2024-01-31 19:06:03 +00:00
if ok {
switch activityType {
2024-07-17 08:09:49 +00:00
case ApproveAT :
fallthrough
2024-01-31 19:06:03 +00:00
case ContractDeploymentAT :
fallthrough
case SendAT :
inAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
outAmount = ( * hexutil . Big ) ( amount )
return
case MintAT :
fallthrough
case ReceiveAT :
inAmount = ( * hexutil . Big ) ( amount )
outAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
return
default :
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Warn ( fmt . Sprintf ( "unexpected activity type %d" , activityType ) )
2024-01-31 19:06:03 +00:00
}
} else {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Warn ( fmt . Sprintf ( "could not parse amount %s" , trAmount . String ) )
2024-01-31 19:06:03 +00:00
}
2023-06-13 09:25:23 +00:00
inAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
outAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
return
}
func getMtInAndOutAmounts ( dbFromAmount sql . NullString , dbToAmount sql . NullString ) ( inAmount * hexutil . Big , outAmount * hexutil . Big ) {
if dbFromAmount . Valid && dbToAmount . Valid {
fromHexStr := dbFromAmount . String
toHexStr := dbToAmount . String
if len ( fromHexStr ) > 2 && len ( toHexStr ) > 2 {
fromAmount , frOk := new ( big . Int ) . SetString ( dbFromAmount . String [ 2 : ] , 16 )
toAmount , toOk := new ( big . Int ) . SetString ( dbToAmount . String [ 2 : ] , 16 )
if frOk && toOk {
inAmount = ( * hexutil . Big ) ( toAmount )
outAmount = ( * hexutil . Big ) ( fromAmount )
return
}
}
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Warn ( fmt . Sprintf ( "could not parse amounts %s %s" , fromHexStr , toHexStr ) )
2023-06-13 09:25:23 +00:00
} else {
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Warn ( "invalid transaction amounts" )
2023-06-13 09:25:23 +00:00
}
inAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
outAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
return
}
2023-07-18 15:57:44 +00:00
func contractTypeFromDBType ( dbType string ) ( transferType * TransferType ) {
transferType = new ( TransferType )
switch common . Type ( dbType ) {
case common . EthTransfer :
* transferType = TransferTypeEth
case common . Erc20Transfer :
* transferType = TransferTypeErc20
case common . Erc721Transfer :
* transferType = TransferTypeErc721
2024-07-08 09:02:06 +00:00
case common . Erc1155Transfer :
* transferType = TransferTypeErc1155
2023-07-18 15:57:44 +00:00
default :
return nil
}
return transferType
}
2023-08-11 11:25:14 +00:00
2024-07-04 11:51:43 +00:00
func transferTypeToTokenType ( transferType * TransferType ) TokenType {
if transferType == nil {
return Native
}
switch * transferType {
case TransferTypeEth :
return Native
case TransferTypeErc20 :
return Erc20
case TransferTypeErc721 :
return Erc721
case TransferTypeErc1155 :
return Erc1155
default :
2024-10-28 20:54:17 +00:00
logutils . ZapLogger ( ) . Error ( fmt . Sprintf ( "unexpected transfer type %d" , transferType ) )
2024-07-04 11:51:43 +00:00
}
return Native
}
2023-08-11 17:28:46 +00:00
// lookupAndFillInTokens ignores NFTs
2023-08-28 09:02:05 +00:00
func lookupAndFillInTokens ( deps FilterDependencies , tokenOut * Token , tokenIn * Token ) ( symbolOut * string , symbolIn * string ) {
2023-08-11 17:28:46 +00:00
if tokenOut != nil && tokenOut . TokenID == nil {
2023-08-28 09:02:05 +00:00
symbol := deps . tokenSymbol ( * tokenOut )
if len ( symbol ) > 0 {
2023-08-30 16:14:57 +00:00
symbolOut = common . NewAndSet ( symbol )
2023-08-28 09:02:05 +00:00
}
}
2023-08-11 17:28:46 +00:00
if tokenIn != nil && tokenIn . TokenID == nil {
2023-08-28 09:02:05 +00:00
symbol := deps . tokenSymbol ( * tokenIn )
if len ( symbol ) > 0 {
2023-08-30 16:14:57 +00:00
symbolIn = common . NewAndSet ( symbol )
2023-08-28 09:02:05 +00:00
}
}
return symbolOut , symbolIn
}