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-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-05-11 07:50:07 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/common"
|
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-05-19 15:31:45 +00:00
|
|
|
// Beware: pleas 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-24 12:23:40 +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
|
2023-08-28 09:02:05 +00:00
|
|
|
symbolOut *string
|
|
|
|
symbolIn *string
|
2023-08-24 12:23:40 +00:00
|
|
|
sender *eth.Address
|
|
|
|
recipient *eth.Address
|
|
|
|
chainIDOut *common.ChainID
|
|
|
|
chainIDIn *common.ChainID
|
|
|
|
transferType *TransferType
|
2023-04-21 11:59:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type jsonSerializationTemplate struct {
|
2023-08-24 12:23:40 +00:00
|
|
|
PayloadType PayloadType `json:"payloadType"`
|
|
|
|
Transaction *transfer.TransactionIdentity `json:"transaction"`
|
|
|
|
ID transfer.MultiTransactionIDType `json:"id"`
|
|
|
|
Timestamp int64 `json:"timestamp"`
|
|
|
|
ActivityType Type `json:"activityType"`
|
|
|
|
ActivityStatus Status `json:"activityStatus"`
|
|
|
|
AmountOut *hexutil.Big `json:"amountOut"`
|
|
|
|
AmountIn *hexutil.Big `json:"amountIn"`
|
|
|
|
TokenOut *Token `json:"tokenOut,omitempty"`
|
|
|
|
TokenIn *Token `json:"tokenIn,omitempty"`
|
2023-08-28 09:02:05 +00:00
|
|
|
SymbolOut *string `json:"symbolOut,omitempty"`
|
|
|
|
SymbolIn *string `json:"symbolIn,omitempty"`
|
2023-08-24 12:23:40 +00:00
|
|
|
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"`
|
2023-04-21 11:59:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Entry) MarshalJSON() ([]byte, error) {
|
|
|
|
return json.Marshal(jsonSerializationTemplate{
|
2023-08-24 12:23:40 +00:00
|
|
|
PayloadType: e.payloadType,
|
|
|
|
Transaction: e.transaction,
|
|
|
|
ID: e.id,
|
|
|
|
Timestamp: e.timestamp,
|
|
|
|
ActivityType: e.activityType,
|
|
|
|
ActivityStatus: e.activityStatus,
|
|
|
|
AmountOut: e.amountOut,
|
|
|
|
AmountIn: e.amountIn,
|
|
|
|
TokenOut: e.tokenOut,
|
|
|
|
TokenIn: e.tokenIn,
|
2023-08-28 09:02:05 +00:00
|
|
|
SymbolOut: e.symbolOut,
|
|
|
|
SymbolIn: e.symbolIn,
|
2023-08-24 12:23:40 +00:00
|
|
|
Sender: e.sender,
|
|
|
|
Recipient: e.recipient,
|
|
|
|
ChainIDOut: e.chainIDOut,
|
|
|
|
ChainIDIn: e.chainIDIn,
|
|
|
|
TransferType: e.transferType,
|
2023-04-21 11:59:29 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Entry) UnmarshalJSON(data []byte) error {
|
|
|
|
aux := jsonSerializationTemplate{}
|
|
|
|
|
|
|
|
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
|
|
|
|
e.id = aux.ID
|
|
|
|
e.timestamp = aux.Timestamp
|
|
|
|
e.activityType = aux.ActivityType
|
2023-07-18 15:57:44 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
// TODO optimization: consider implementing nullable []byte instead of using strings for addresses
|
2023-06-13 09:25:23 +00:00
|
|
|
// or insert binary (X'...' syntax) directly into the query
|
2023-05-11 07:50:07 +00:00
|
|
|
//
|
|
|
|
// Query includes duplicates, will return multiple rows for the same transaction if both to and from addresses
|
|
|
|
// are in the address list.
|
|
|
|
//
|
|
|
|
// The addresses list will have priority in deciding the source of the duplicate transaction. However, if the
|
|
|
|
// if the addresses list is empty, and all addresses should be included, the accounts table will be used
|
|
|
|
// see filter_addresses temp table is used
|
|
|
|
// The switch for tr_type is used to de-conflict the source for the two entries for the same transaction
|
|
|
|
//
|
|
|
|
// UNION ALL is used to avoid the overhead of DISTINCT given that we don't expect to have duplicate entries outside
|
|
|
|
// the sender and receiver addresses being in the list which is handled separately
|
2023-05-28 10:40:50 +00:00
|
|
|
//
|
|
|
|
// Only status FailedAS, PendingAS and CompleteAS are returned. FinalizedAS requires correlation with blockchain
|
|
|
|
// current state. As an optimization we can approximate it by using timestamp information or last known block number
|
2023-06-13 09:25:23 +00:00
|
|
|
//
|
|
|
|
// Token filtering has two parts
|
|
|
|
// 1. Filtering by symbol (multi_transactions and pending_transactions tables) where the chain ID is ignored, basically the
|
|
|
|
// filter_networks will account for that
|
|
|
|
// 2. Filtering by token identity (chain and address for transfers table) where the symbol is ignored and all the
|
|
|
|
// token identities must be provided
|
2023-05-11 07:50:07 +00:00
|
|
|
queryFormatString = `
|
2023-07-25 16:03:33 +00:00
|
|
|
WITH filter_conditions AS (
|
|
|
|
SELECT
|
|
|
|
? AS startFilterDisabled,
|
|
|
|
? AS startTimestamp,
|
|
|
|
? AS endFilterDisabled,
|
|
|
|
? AS endTimestamp,
|
|
|
|
|
|
|
|
? AS filterActivityTypeAll,
|
|
|
|
? AS filterActivityTypeSend,
|
|
|
|
? AS filterActivityTypeReceive,
|
|
|
|
|
|
|
|
? AS fromTrType,
|
|
|
|
? AS toTrType,
|
|
|
|
|
|
|
|
? AS filterAllAddresses,
|
|
|
|
? AS filterAllToAddresses,
|
|
|
|
|
|
|
|
? AS filterAllActivityStatus,
|
|
|
|
? AS filterStatusCompleted,
|
|
|
|
? AS filterStatusFailed,
|
|
|
|
? AS filterStatusFinalized,
|
|
|
|
? AS filterStatusPending,
|
|
|
|
|
|
|
|
? AS statusFailed,
|
|
|
|
? AS statusSuccess,
|
|
|
|
? AS statusPending,
|
|
|
|
|
|
|
|
? AS includeAllTokenTypeAssets,
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
? AS includeAllNetworks,
|
|
|
|
|
|
|
|
? AS pendingStatus
|
2023-07-25 16:03:33 +00:00
|
|
|
),
|
|
|
|
filter_addresses(address) AS (
|
2023-08-10 11:41:04 +00:00
|
|
|
SELECT HEX(address) FROM %s WHERE (SELECT filterAllAddresses FROM filter_conditions) != 0
|
2023-07-25 16:03:33 +00:00
|
|
|
UNION ALL
|
|
|
|
SELECT * FROM (VALUES %s) WHERE (SELECT filterAllAddresses FROM filter_conditions) = 0
|
|
|
|
),
|
|
|
|
filter_to_addresses(address) AS (
|
|
|
|
VALUES %s
|
|
|
|
),
|
|
|
|
assets_token_codes(token_code) AS (
|
|
|
|
VALUES %s
|
|
|
|
),
|
|
|
|
assets_erc20(chain_id, token_address) AS (
|
|
|
|
VALUES %s
|
|
|
|
),
|
|
|
|
filter_networks(network_id) AS (
|
|
|
|
VALUES %s
|
|
|
|
),
|
|
|
|
tr_status AS (
|
|
|
|
SELECT
|
|
|
|
multi_transaction_id,
|
|
|
|
MIN(status) AS min_status,
|
|
|
|
COUNT(*) AS count,
|
|
|
|
network_id
|
|
|
|
FROM
|
|
|
|
transfers
|
|
|
|
WHERE transfers.loaded == 1
|
|
|
|
AND transfers.multi_transaction_id != 0
|
2023-07-10 15:00:35 +00:00
|
|
|
GROUP BY transfers.multi_transaction_id
|
2023-07-25 16:03:33 +00:00
|
|
|
),
|
|
|
|
tr_network_ids AS (
|
|
|
|
SELECT
|
|
|
|
multi_transaction_id
|
|
|
|
FROM
|
|
|
|
transfers
|
|
|
|
WHERE transfers.loaded == 1
|
|
|
|
AND transfers.multi_transaction_id != 0
|
|
|
|
AND network_id IN filter_networks
|
|
|
|
GROUP BY transfers.multi_transaction_id
|
|
|
|
),
|
|
|
|
pending_status AS (
|
|
|
|
SELECT
|
|
|
|
multi_transaction_id,
|
|
|
|
COUNT(*) AS count,
|
|
|
|
network_id
|
|
|
|
FROM
|
2023-08-01 18:50:30 +00:00
|
|
|
pending_transactions, filter_conditions
|
|
|
|
WHERE pending_transactions.multi_transaction_id != 0 AND pending_transactions.status = pendingStatus
|
2023-07-25 16:03:33 +00:00
|
|
|
GROUP BY pending_transactions.multi_transaction_id
|
|
|
|
),
|
|
|
|
pending_network_ids AS (
|
|
|
|
SELECT
|
|
|
|
multi_transaction_id
|
|
|
|
FROM
|
2023-08-01 18:50:30 +00:00
|
|
|
pending_transactions, filter_conditions
|
|
|
|
WHERE pending_transactions.multi_transaction_id != 0 AND pending_transactions.status = pendingStatus
|
2023-07-25 16:03:33 +00:00
|
|
|
AND pending_transactions.network_id IN filter_networks
|
|
|
|
GROUP BY pending_transactions.multi_transaction_id
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
transfers.hash AS transfer_hash,
|
|
|
|
NULL AS pending_hash,
|
|
|
|
transfers.network_id AS network_id,
|
|
|
|
0 AS multi_tx_id,
|
|
|
|
transfers.timestamp AS timestamp,
|
|
|
|
NULL AS mt_type,
|
|
|
|
|
|
|
|
CASE
|
|
|
|
WHEN from_join.address IS NOT NULL AND to_join.address IS NULL THEN fromTrType
|
|
|
|
WHEN to_join.address IS NOT NULL AND from_join.address IS NULL THEN toTrType
|
|
|
|
WHEN from_join.address IS NOT NULL AND to_join.address IS NOT NULL THEN
|
|
|
|
CASE
|
|
|
|
WHEN from_join.address < to_join.address THEN fromTrType
|
|
|
|
ELSE toTrType
|
|
|
|
END
|
|
|
|
ELSE NULL
|
|
|
|
END as tr_type,
|
|
|
|
|
|
|
|
transfers.tx_from_address AS from_address,
|
|
|
|
transfers.tx_to_address AS to_address,
|
2023-07-04 22:21:07 +00:00
|
|
|
transfers.address AS owner_address,
|
2023-07-25 16:03:33 +00:00
|
|
|
transfers.amount_padded128hex AS tr_amount,
|
|
|
|
NULL AS mt_from_amount,
|
|
|
|
NULL AS mt_to_amount,
|
2023-06-13 09:25:23 +00:00
|
|
|
|
2023-07-25 16:03:33 +00:00
|
|
|
CASE
|
|
|
|
WHEN transfers.status IS 1 THEN statusSuccess
|
|
|
|
ELSE statusFailed
|
|
|
|
END AS agg_status,
|
2023-06-13 09:25:23 +00:00
|
|
|
|
2023-07-25 16:03:33 +00:00
|
|
|
1 AS agg_count,
|
2023-06-13 09:25:23 +00:00
|
|
|
|
2023-07-25 16:03:33 +00:00
|
|
|
transfers.token_address AS token_address,
|
2023-08-04 11:40:36 +00:00
|
|
|
transfers.token_id AS token_id,
|
2023-07-25 16:03:33 +00:00
|
|
|
NULL AS token_code,
|
|
|
|
NULL AS from_token_code,
|
|
|
|
NULL AS to_token_code,
|
2023-07-18 15:57:44 +00:00
|
|
|
NULL AS out_network_id,
|
|
|
|
NULL AS in_network_id,
|
|
|
|
transfers.type AS type,
|
|
|
|
transfers.contract_address AS contract_address
|
2023-07-25 16:03:33 +00:00
|
|
|
FROM transfers, filter_conditions
|
|
|
|
LEFT JOIN
|
|
|
|
filter_addresses from_join ON HEX(transfers.tx_from_address) = from_join.address
|
|
|
|
LEFT JOIN
|
|
|
|
filter_addresses to_join ON HEX(transfers.tx_to_address) = to_join.address
|
|
|
|
WHERE
|
2023-07-10 15:00:35 +00:00
|
|
|
transfers.loaded == 1
|
2023-07-25 16:03:33 +00:00
|
|
|
AND transfers.multi_transaction_id = 0
|
|
|
|
AND ((startFilterDisabled OR transfers.timestamp >= startTimestamp)
|
|
|
|
AND (endFilterDisabled OR transfers.timestamp <= endTimestamp)
|
|
|
|
)
|
|
|
|
AND (filterActivityTypeAll
|
|
|
|
OR (filterActivityTypeSend
|
|
|
|
AND (filterAllAddresses
|
|
|
|
OR (HEX(transfers.tx_from_address) IN filter_addresses)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
OR (filterActivityTypeReceive
|
|
|
|
AND (filterAllAddresses
|
2023-06-21 08:01:31 +00:00
|
|
|
OR (HEX(transfers.tx_to_address) IN filter_addresses))
|
2023-07-25 16:03:33 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
AND (filterAllAddresses
|
|
|
|
OR (HEX(transfers.tx_from_address) IN filter_addresses)
|
|
|
|
OR (HEX(transfers.tx_to_address) IN filter_addresses)
|
|
|
|
)
|
|
|
|
AND (filterAllToAddresses
|
|
|
|
OR (HEX(transfers.tx_to_address) IN filter_to_addresses)
|
|
|
|
)
|
|
|
|
AND (includeAllTokenTypeAssets OR (transfers.type = "eth" AND ("ETH" IN assets_token_codes))
|
|
|
|
OR (transfers.type = "erc20" AND ((transfers.network_id, HEX(transfers.token_address)) IN assets_erc20))
|
|
|
|
)
|
|
|
|
AND (includeAllNetworks OR (transfers.network_id IN filter_networks))
|
|
|
|
AND (filterAllActivityStatus OR ((filterStatusCompleted OR filterStatusFinalized) AND transfers.status = 1)
|
|
|
|
OR (filterStatusFailed AND transfers.status = 0)
|
|
|
|
)
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
NULL AS transfer_hash,
|
|
|
|
pending_transactions.hash AS pending_hash,
|
|
|
|
pending_transactions.network_id AS network_id,
|
|
|
|
0 AS multi_tx_id,
|
|
|
|
pending_transactions.timestamp AS timestamp,
|
|
|
|
NULL AS mt_type,
|
|
|
|
|
|
|
|
CASE
|
|
|
|
WHEN from_join.address IS NOT NULL AND to_join.address IS NULL THEN fromTrType
|
|
|
|
WHEN to_join.address IS NOT NULL AND from_join.address IS NULL THEN toTrType
|
|
|
|
WHEN from_join.address IS NOT NULL AND to_join.address IS NOT NULL THEN
|
|
|
|
CASE
|
|
|
|
WHEN from_join.address < to_join.address THEN fromTrType
|
|
|
|
ELSE toTrType
|
|
|
|
END
|
|
|
|
ELSE NULL
|
|
|
|
END as tr_type,
|
|
|
|
|
|
|
|
pending_transactions.from_address AS from_address,
|
|
|
|
pending_transactions.to_address AS to_address,
|
2023-07-04 22:21:07 +00:00
|
|
|
NULL AS owner_address,
|
2023-07-25 16:03:33 +00:00
|
|
|
pending_transactions.value AS tr_amount,
|
|
|
|
NULL AS mt_from_amount,
|
|
|
|
NULL AS mt_to_amount,
|
2023-06-13 09:25:23 +00:00
|
|
|
|
2023-07-25 16:03:33 +00:00
|
|
|
statusPending AS agg_status,
|
|
|
|
1 AS agg_count,
|
2023-06-13 09:25:23 +00:00
|
|
|
|
2023-07-25 16:03:33 +00:00
|
|
|
NULL AS token_address,
|
2023-08-04 11:40:36 +00:00
|
|
|
NULL AS token_id,
|
2023-07-25 16:03:33 +00:00
|
|
|
pending_transactions.symbol AS token_code,
|
|
|
|
NULL AS from_token_code,
|
|
|
|
NULL AS to_token_code,
|
2023-07-18 15:57:44 +00:00
|
|
|
NULL AS out_network_id,
|
|
|
|
NULL AS in_network_id,
|
|
|
|
pending_transactions.type AS type,
|
|
|
|
NULL as contract_address
|
2023-07-25 16:03:33 +00:00
|
|
|
FROM pending_transactions, filter_conditions
|
|
|
|
LEFT JOIN
|
|
|
|
filter_addresses from_join ON HEX(pending_transactions.from_address) = from_join.address
|
|
|
|
LEFT JOIN
|
|
|
|
filter_addresses to_join ON HEX(pending_transactions.to_address) = to_join.address
|
2023-08-01 18:50:30 +00:00
|
|
|
WHERE pending_transactions.multi_transaction_id = 0 AND pending_transactions.status = pendingStatus
|
2023-07-25 16:03:33 +00:00
|
|
|
AND (filterAllActivityStatus OR filterStatusPending)
|
|
|
|
AND ((startFilterDisabled OR timestamp >= startTimestamp)
|
|
|
|
AND (endFilterDisabled OR timestamp <= endTimestamp)
|
|
|
|
)
|
|
|
|
AND (filterActivityTypeAll OR filterActivityTypeSend)
|
|
|
|
AND (filterAllAddresses
|
|
|
|
OR (HEX(pending_transactions.from_address) IN filter_addresses)
|
|
|
|
OR (HEX(pending_transactions.to_address) IN filter_addresses)
|
|
|
|
)
|
|
|
|
AND (filterAllToAddresses
|
|
|
|
OR (HEX(pending_transactions.to_address) IN filter_to_addresses)
|
|
|
|
)
|
|
|
|
AND (includeAllTokenTypeAssets OR (UPPER(pending_transactions.symbol) IN assets_token_codes))
|
|
|
|
AND (includeAllNetworks OR (pending_transactions.network_id IN filter_networks))
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
NULL AS transfer_hash,
|
|
|
|
NULL AS pending_hash,
|
|
|
|
NULL AS network_id,
|
|
|
|
multi_transactions.ROWID AS multi_tx_id,
|
|
|
|
multi_transactions.timestamp AS timestamp,
|
|
|
|
multi_transactions.type AS mt_type,
|
|
|
|
NULL as tr_type,
|
|
|
|
multi_transactions.from_address AS from_address,
|
|
|
|
multi_transactions.to_address AS to_address,
|
2023-07-04 22:21:07 +00:00
|
|
|
NULL AS owner_address,
|
2023-07-25 16:03:33 +00:00
|
|
|
NULL AS tr_amount,
|
|
|
|
multi_transactions.from_amount AS mt_from_amount,
|
|
|
|
multi_transactions.to_amount AS mt_to_amount,
|
|
|
|
|
|
|
|
CASE
|
|
|
|
WHEN tr_status.min_status = 1 AND COALESCE(pending_status.count, 0) = 0 THEN statusSuccess
|
|
|
|
WHEN tr_status.min_status = 0 THEN statusFailed
|
|
|
|
ELSE statusPending
|
|
|
|
END AS agg_status,
|
|
|
|
|
|
|
|
COALESCE(tr_status.count, 0) + COALESCE(pending_status.count, 0) AS agg_count,
|
|
|
|
|
|
|
|
NULL AS token_address,
|
2023-08-04 11:40:36 +00:00
|
|
|
NULL AS token_id,
|
2023-07-25 16:03:33 +00:00
|
|
|
NULL AS token_code,
|
|
|
|
multi_transactions.from_asset AS from_token_code,
|
|
|
|
multi_transactions.to_asset AS to_token_code,
|
2023-07-18 15:57:44 +00:00
|
|
|
multi_transactions.from_network_id AS out_network_id,
|
|
|
|
multi_transactions.to_network_id AS in_network_id,
|
|
|
|
NULL AS type,
|
|
|
|
NULL as contract_address
|
2023-07-25 16:03:33 +00:00
|
|
|
FROM multi_transactions, filter_conditions
|
|
|
|
LEFT JOIN tr_status ON multi_transactions.ROWID = tr_status.multi_transaction_id
|
|
|
|
LEFT JOIN pending_status ON multi_transactions.ROWID = pending_status.multi_transaction_id
|
|
|
|
WHERE
|
|
|
|
((startFilterDisabled OR multi_transactions.timestamp >= startTimestamp)
|
|
|
|
AND (endFilterDisabled OR multi_transactions.timestamp <= endTimestamp)
|
|
|
|
)
|
|
|
|
AND (filterActivityTypeAll OR (multi_transactions.type IN (%s)))
|
|
|
|
AND (filterAllAddresses
|
|
|
|
OR (HEX(multi_transactions.from_address) IN filter_addresses)
|
|
|
|
OR (HEX(multi_transactions.to_address) IN filter_addresses)
|
|
|
|
)
|
|
|
|
AND (filterAllToAddresses
|
|
|
|
OR (HEX(multi_transactions.to_address) IN filter_to_addresses)
|
|
|
|
)
|
|
|
|
AND (includeAllTokenTypeAssets
|
|
|
|
OR (multi_transactions.from_asset != '' AND (UPPER(multi_transactions.from_asset) IN assets_token_codes))
|
|
|
|
OR (multi_transactions.to_asset != '' AND (UPPER(multi_transactions.to_asset) IN assets_token_codes))
|
|
|
|
)
|
|
|
|
AND (filterAllActivityStatus
|
|
|
|
OR ((filterStatusCompleted OR filterStatusFinalized) AND agg_status = statusSuccess)
|
|
|
|
OR (filterStatusFailed AND agg_status = statusFailed) OR (filterStatusPending AND agg_status = statusPending)
|
|
|
|
)
|
|
|
|
AND (includeAllNetworks
|
|
|
|
OR (multi_transactions.from_network_id IN filter_networks)
|
|
|
|
OR (multi_transactions.to_network_id IN filter_networks)
|
|
|
|
OR (multi_transactions.from_network_id IS NULL
|
|
|
|
AND multi_transactions.to_network_id IS NULL
|
|
|
|
AND (EXISTS (SELECT 1 FROM tr_network_ids WHERE multi_transactions.ROWID = tr_network_ids.multi_transaction_id)
|
|
|
|
OR EXISTS (SELECT 1 FROM pending_network_ids WHERE multi_transactions.ROWID = pending_network_ids.multi_transaction_id)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
ORDER BY timestamp DESC
|
|
|
|
LIMIT ? OFFSET ?`
|
2023-06-13 09:25:23 +00:00
|
|
|
|
|
|
|
noEntriesInTmpTableSQLValues = "(NULL)"
|
|
|
|
noEntriesInTwoColumnsTmpTableSQLValues = "(NULL, NULL)"
|
2023-05-11 07:50:07 +00:00
|
|
|
)
|
|
|
|
|
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-06-08 23:52:45 +00:00
|
|
|
// getActivityEntries queries the transfers, pending_transactions, and multi_transactions tables
|
2023-05-11 07:50:07 +00:00
|
|
|
// based on filter parameters and arguments
|
|
|
|
// it returns metadata for all entries ordered by timestamp column
|
|
|
|
//
|
|
|
|
// Adding a no-limit option was never considered or required.
|
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, ×tamp, &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-04 11:40:36 +00:00
|
|
|
var tokenID TokenID
|
|
|
|
if len(dbTokenID) > 0 {
|
|
|
|
t := new(big.Int).SetBytes(dbTokenID)
|
|
|
|
tokenID = (*hexutil.Big)(t)
|
|
|
|
}
|
2023-07-20 14:04:30 +00:00
|
|
|
|
2023-04-21 11:59:29 +00:00
|
|
|
if transferHash != nil && chainID.Valid {
|
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-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-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
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
func lookupAndFillInTokens(deps FilterDependencies, tokenOut *Token, tokenIn *Token) (symbolOut *string, symbolIn *string) {
|
|
|
|
if tokenOut != nil {
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
if tokenIn != nil {
|
|
|
|
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
|
|
|
|
}
|