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-28 10:40:50 +00:00
"github.com/ethereum/go-ethereum/log"
2023-05-11 07:50:07 +00:00
2023-08-11 11:25:14 +00:00
"github.com/status-im/status-go/multiaccounts/accounts"
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-08-11 11:25:14 +00:00
const keypairAccountsTable = "keypairs_accounts"
2023-06-20 02:50:49 +00:00
var (
ZeroAddress = eth . Address { }
)
2023-07-18 15:57:44 +00:00
type TransferType = int
const (
TransferTypeEth TransferType = iota + 1
TransferTypeErc20
TransferTypeErc721
TransferTypeErc1155
)
2023-04-21 11:59:29 +00:00
type Entry struct {
2023-08-11 17:28:46 +00:00
payloadType PayloadType
transaction * transfer . TransactionIdentity
id transfer . MultiTransactionIDType
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
tokenOut * Token // Used for activityType SendAT, SwapAT, BridgeAT
tokenIn * Token // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT
symbolOut * string
symbolIn * string
sender * eth . Address
recipient * eth . Address
chainIDOut * common . ChainID
chainIDIn * common . ChainID
transferType * TransferType
contractAddress * eth . Address
2023-04-21 11:59:29 +00:00
}
2023-08-11 17:28:46 +00:00
type EntryData struct {
PayloadType PayloadType ` json:"payloadType" `
Transaction * transfer . TransactionIdentity ` json:"transaction,omitempty" `
ID * transfer . MultiTransactionIDType ` json:"id,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" `
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 {
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 ,
}
if e . payloadType == MultiTransactionPT {
data . ID = common . NewAndSet ( e . id )
} else {
data . Transaction = e . transaction
}
data . PayloadType = e . payloadType
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
}
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
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
}
}
2023-07-18 15:57:44 +00:00
func NewActivityEntryWithMultiTransaction ( id transfer . 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 ) )
}
// TODO - #11952: use only one of (big.Int, bigint.BigInt and hexutil.Big)
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
}
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
}
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 {
return fmt . Sprintf ( "'%s'" , strings . ToUpper ( hex . EncodeToString ( a [ : ] ) ) )
} )
}
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
} 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-06-13 09:25:23 +00:00
noEntriesInTmpTableSQLValues = "(NULL)"
noEntriesInTwoColumnsTmpTableSQLValues = "(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-06-13 09:25:23 +00:00
type FilterDependencies struct {
2023-08-28 09:02:05 +00:00
db * sql . DB
accountsDb * accounts . Database
// 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-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
//
// Adding a no-limit option was never considered or required.
2023-09-05 12:37:00 +00:00
//
// TODO: optimization: consider implementing nullable []byte instead of using strings for addresses or insert binary (X'...' syntax) directly into the query
2023-06-13 09:25:23 +00:00
func getActivityEntries ( ctx context . Context , deps FilterDependencies , addresses [ ] eth . Address , chainIDs [ ] common . ChainID , filter Filter , offset int , limit int ) ( [ ] Entry , error ) {
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-06-20 02:50:49 +00:00
// SQL HEX() (Blob->Hex) conversion returns uppercase digits with no 0x prefix
return fmt . Sprintf ( "%d, '%s'" , item . ChainID , strings . ToUpper ( item . Address . Hex ( ) [ 2 : ] ) )
2023-06-13 09:25:23 +00:00
}
return ""
} )
}
2023-05-11 07:50:07 +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
startFilterDisabled := ! ( filter . Period . StartTimestamp > 0 )
endFilterDisabled := ! ( filter . Period . EndTimestamp > 0 )
2023-05-11 07:50:07 +00:00
filterActivityTypeAll := len ( filter . Types ) == 0
2023-04-21 11:59:29 +00:00
filterAllAddresses := len ( addresses ) == 0
2023-05-11 07:50:07 +00:00
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-05-11 07:50:07 +00:00
involvedAddresses := noEntriesInTmpTableSQLValues
2023-04-21 11:59:29 +00:00
if ! filterAllAddresses {
2023-05-11 07:50:07 +00:00
involvedAddresses = joinAddresses ( addresses )
}
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-08-11 11:25:14 +00:00
// Since the filter query needs addresses which are in a different database, we need to update the
// keypairs_accounts table in the current database with the latest addresses from the accounts database
err := updateKeypairsAccountsTable ( deps . accountsDb , deps . db )
if err != nil {
return nil , err
}
2023-08-10 11:41:04 +00:00
queryString := fmt . Sprintf ( queryFormatString , keypairAccountsTable , involvedAddresses , toAddresses , assetsTokenCodes , assetsERC20 , networks ,
2023-05-11 07:50:07 +00:00
joinedMTTypes )
2023-04-21 11:59:29 +00:00
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-05-28 10:40:50 +00:00
fromTrType , toTrType ,
filterAllAddresses , filterAllToAddresses ,
includeAllStatuses , filterStatusCompleted , filterStatusFailed , filterStatusFinalized , filterStatusPending ,
FailedAS , CompleteAS , PendingAS ,
includeAllTokenTypeAssets ,
2023-05-11 07:50:07 +00:00
includeAllNetworks ,
2023-08-01 18:50:30 +00:00
transactions . Pending ,
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
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
2023-07-20 14:04:30 +00:00
var transferType * TransferType
2023-06-13 09:25:23 +00:00
err := rows . Scan ( & transferHash , & pendingHash , & chainID , & multiTxID , & timestamp , & dbMtType , & dbTrType , & fromAddress ,
2023-07-04 22:21:07 +00:00
& toAddressDB , & ownerAddressDB , & dbTrAmount , & dbMtFromAmount , & dbMtToAmount , & aggregatedStatus , & aggregatedCount ,
2023-08-04 11:40:36 +00:00
& tokenAddress , & dbTokenID , & tokenCode , & fromTokenCode , & toTokenCode , & outChainIDDB , & inChainIDDB , & contractType , & contractAddressDB )
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 )
}
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-08-04 10:47:22 +00:00
if fromAddress == ZeroAddress && transferType != nil && * transferType == TransferTypeErc721 {
return MintAT , toAddress
}
2023-05-28 10:40:50 +00:00
return ReceiveAT , toAddress
}
2023-05-11 07:50:07 +00:00
}
2023-07-04 12:01:45 +00:00
log . 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 )
2023-06-14 19:43:28 +00:00
inAmount , outAmount := getTrInAndOutAmounts ( activityType , dbTrAmount )
2023-06-13 09:25:23 +00:00
2023-07-18 15:57:44 +00:00
// Extract tokens and chains
2023-06-13 09:25:23 +00:00
var involvedToken * Token
2023-06-20 02:50:49 +00:00
if tokenAddress != nil && * tokenAddress != ZeroAddress {
2023-08-04 11:40:36 +00:00
involvedToken = & Token { TokenType : Erc20 , ChainID : common . ChainID ( chainID . Int64 ) , TokenID : tokenID , Address : * tokenAddress }
2023-06-13 09:25:23 +00:00
} else {
2023-08-04 11:40:36 +00:00
involvedToken = & Token { TokenType : Native , ChainID : common . ChainID ( chainID . Int64 ) , 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-06-13 09:25:23 +00:00
if activityType == SendAT {
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
2023-06-14 19:43:28 +00:00
inAmount , outAmount := getTrInAndOutAmounts ( activityType , dbTrAmount )
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
// Extract activity type: SendAT/SwapAT/BridgeAT
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 )
}
2023-08-28 09:02:05 +00:00
entry = NewActivityEntryWithMultiTransaction ( transfer . MultiTransactionIDType ( multiTxID . Int64 ) ,
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
2023-07-18 15:57:44 +00:00
// Complete common data
entry . sender = & fromAddress
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
func getTrInAndOutAmounts ( activityType Type , trAmount sql . NullString ) ( inAmount * hexutil . Big , outAmount * hexutil . Big ) {
if trAmount . Valid {
amount , ok := new ( big . Int ) . SetString ( trAmount . String , 16 )
if ok {
switch activityType {
case SendAT :
inAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
outAmount = ( * hexutil . Big ) ( amount )
return
case ReceiveAT :
inAmount = ( * hexutil . Big ) ( amount )
outAmount = ( * hexutil . Big ) ( big . NewInt ( 0 ) )
return
default :
log . Warn ( fmt . Sprintf ( "unexpected activity type %d" , activityType ) )
}
} else {
log . Warn ( fmt . Sprintf ( "could not parse amount %s" , trAmount . String ) )
}
} else {
log . Warn ( fmt . Sprintf ( "invalid transaction amount for type %d" , activityType ) )
}
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
}
}
log . Warn ( fmt . Sprintf ( "could not parse amounts %s %s" , fromHexStr , toHexStr ) )
} else {
log . Warn ( "invalid transaction amounts" )
}
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
default :
return nil
}
return transferType
}
2023-08-11 11:25:14 +00:00
func updateKeypairsAccountsTable ( accountsDb * accounts . Database , db * sql . DB ) error {
_ , err := db . Exec ( fmt . Sprintf ( "CREATE TEMP TABLE IF NOT EXISTS %s (address VARCHAR PRIMARY KEY)" ,
keypairAccountsTable ) )
if err != nil {
log . Error ( "failed to create 'keypairs_accounts' table" , "err" , err )
return err
}
2023-08-11 17:28:46 +00:00
// TODO: remove dependency on accounts table by removing"all accounts filter" optimization; see #11980
if accountsDb == nil {
return nil
}
2023-08-11 11:25:14 +00:00
addresses , err := accountsDb . GetWalletAddresses ( )
if err != nil {
log . Error ( "failed to get wallet addresses" , "err" , err )
return err
}
tx , err := db . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
for _ , address := range addresses {
_ , err = tx . Exec ( fmt . Sprintf ( "INSERT OR IGNORE INTO %s (address) VALUES (?)" , keypairAccountsTable ) , address )
if err != nil {
log . Error ( "failed to insert wallet addresses" , "err" , err )
return err
}
}
return nil
}
2023-08-28 09:02:05 +00:00
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
}