2021-09-09 14:28:54 +00:00
package transfer
import (
2023-04-21 11:59:29 +00:00
"context"
2021-09-09 14:28:54 +00:00
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
2023-05-19 15:31:45 +00:00
2021-09-09 14:28:54 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
2023-05-19 15:31:45 +00:00
w_common "github.com/status-im/status-go/services/wallet/common"
2024-01-08 05:21:50 +00:00
"github.com/status-im/status-go/services/wallet/thirdparty"
2023-05-19 15:31:45 +00:00
"github.com/status-im/status-go/sqlite"
2021-09-09 14:28:54 +00:00
)
// DBHeader fields from header that are stored in database.
type DBHeader struct {
2023-06-02 20:08:45 +00:00
Number * big . Int
Hash common . Hash
Timestamp uint64
PreloadedTransactions [ ] * PreloadedTransaction
Network uint64
Address common . Address
2021-09-09 14:28:54 +00:00
// Head is true if the block was a head at the time it was pulled from chain.
Head bool
2023-05-19 15:31:45 +00:00
// Loaded is true if transfers from this block have been already fetched
2021-09-09 14:28:54 +00:00
Loaded bool
}
2024-09-26 11:38:22 +00:00
func toDBHeader ( header * types . Header , account common . Address ) * DBHeader {
2021-09-09 14:28:54 +00:00
return & DBHeader {
2024-09-26 11:38:22 +00:00
Hash : header . Hash ( ) ,
2021-09-09 14:28:54 +00:00
Number : header . Number ,
Timestamp : header . Time ,
Loaded : false ,
2023-11-27 10:08:17 +00:00
Address : account ,
2021-09-09 14:28:54 +00:00
}
}
// SyncOption is used to specify that application processed transfers for that block.
type SyncOption uint
// JSONBlob type for marshaling/unmarshaling inner type to json.
type JSONBlob struct {
data interface { }
}
// Scan implements interface.
func ( blob * JSONBlob ) Scan ( value interface { } ) error {
if value == nil || reflect . ValueOf ( blob . data ) . IsNil ( ) {
return nil
}
bytes , ok := value . ( [ ] byte )
if ! ok {
return errors . New ( "not a byte slice" )
}
if len ( bytes ) == 0 {
return nil
}
err := json . Unmarshal ( bytes , blob . data )
return err
}
// Value implements interface.
func ( blob * JSONBlob ) Value ( ) ( driver . Value , error ) {
if blob . data == nil || reflect . ValueOf ( blob . data ) . IsNil ( ) {
return nil , nil
}
return json . Marshal ( blob . data )
}
func NewDB ( client * sql . DB ) * Database {
return & Database { client : client }
}
// Database sql wrapper for operations with wallet objects.
type Database struct {
client * sql . DB
}
// Close closes database.
func ( db * Database ) Close ( ) error {
return db . client . Close ( )
}
2023-11-27 10:08:17 +00:00
func ( db * Database ) SaveBlocks ( chainID uint64 , headers [ ] * DBHeader ) ( err error ) {
2021-09-09 14:28:54 +00:00
var (
tx * sql . Tx
)
tx , err = db . client . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
2023-02-15 12:28:19 +00:00
2023-11-27 10:08:17 +00:00
err = insertBlocksWithTransactions ( chainID , tx , headers )
2021-09-09 14:28:54 +00:00
if err != nil {
return
}
2023-02-15 12:28:19 +00:00
2021-09-09 14:28:54 +00:00
return
}
2023-11-27 10:08:17 +00:00
func saveTransfersMarkBlocksLoaded ( creator statementCreator , chainID uint64 , address common . Address , transfers [ ] Transfer , blocks [ ] * big . Int ) ( err error ) {
err = updateOrInsertTransfers ( chainID , creator , transfers )
2023-05-08 06:02:00 +00:00
if err != nil {
return
}
2023-11-27 10:08:17 +00:00
err = markBlocksAsLoaded ( chainID , creator , address , blocks )
2021-09-09 14:28:54 +00:00
if err != nil {
return
}
2023-05-08 06:02:00 +00:00
return
}
2021-09-09 14:28:54 +00:00
// GetTransfersInRange loads transfers for a given address between two blocks.
func ( db * Database ) GetTransfersInRange ( chainID uint64 , address common . Address , start , end * big . Int ) ( rst [ ] Transfer , err error ) {
query := newTransfersQuery ( ) . FilterNetwork ( chainID ) . FilterAddress ( address ) . FilterStart ( start ) . FilterEnd ( end ) . FilterLoaded ( 1 )
rows , err := db . client . Query ( query . String ( ) , query . Args ( ) ... )
if err != nil {
return
}
defer rows . Close ( )
2023-06-02 20:08:45 +00:00
return query . TransferScan ( rows )
2021-09-09 14:28:54 +00:00
}
// GetTransfersByAddress loads transfers for a given address between two blocks.
func ( db * Database ) GetTransfersByAddress ( chainID uint64 , address common . Address , toBlock * big . Int , limit int64 ) ( rst [ ] Transfer , err error ) {
query := newTransfersQuery ( ) .
FilterNetwork ( chainID ) .
FilterAddress ( address ) .
FilterEnd ( toBlock ) .
FilterLoaded ( 1 ) .
2024-01-08 05:21:50 +00:00
SortByBlockNumberAndHash ( ) .
2021-09-09 14:28:54 +00:00
Limit ( limit )
rows , err := db . client . Query ( query . String ( ) , query . Args ( ) ... )
if err != nil {
return
}
defer rows . Close ( )
2023-06-02 20:08:45 +00:00
return query . TransferScan ( rows )
2021-09-09 14:28:54 +00:00
}
// GetTransfersByAddressAndBlock loads transfers for a given address and block.
func ( db * Database ) GetTransfersByAddressAndBlock ( chainID uint64 , address common . Address , block * big . Int , limit int64 ) ( rst [ ] Transfer , err error ) {
query := newTransfersQuery ( ) .
FilterNetwork ( chainID ) .
FilterAddress ( address ) .
FilterBlockNumber ( block ) .
FilterLoaded ( 1 ) .
2024-01-08 05:21:50 +00:00
SortByBlockNumberAndHash ( ) .
2021-09-09 14:28:54 +00:00
Limit ( limit )
rows , err := db . client . Query ( query . String ( ) , query . Args ( ) ... )
if err != nil {
return
}
defer rows . Close ( )
2023-06-02 20:08:45 +00:00
return query . TransferScan ( rows )
2021-09-09 14:28:54 +00:00
}
2023-04-21 11:59:29 +00:00
// GetTransfers load transfers transfer between two blocks.
2021-09-09 14:28:54 +00:00
func ( db * Database ) GetTransfers ( chainID uint64 , start , end * big . Int ) ( rst [ ] Transfer , err error ) {
query := newTransfersQuery ( ) . FilterNetwork ( chainID ) . FilterStart ( start ) . FilterEnd ( end ) . FilterLoaded ( 1 )
rows , err := db . client . Query ( query . String ( ) , query . Args ( ) ... )
if err != nil {
return
}
defer rows . Close ( )
2023-06-02 20:08:45 +00:00
return query . TransferScan ( rows )
2021-09-09 14:28:54 +00:00
}
2023-04-21 11:59:29 +00:00
func ( db * Database ) GetTransfersForIdentities ( ctx context . Context , identities [ ] TransactionIdentity ) ( rst [ ] Transfer , err error ) {
query := newTransfersQuery ( )
for _ , identity := range identities {
subQuery := newSubQuery ( )
2023-11-02 17:24:23 +00:00
subQuery = subQuery . FilterNetwork ( uint64 ( identity . ChainID ) ) . FilterTransactionID ( identity . Hash ) . FilterAddress ( identity . Address )
2023-04-21 11:59:29 +00:00
query . addSubQuery ( subQuery , OrSeparator )
}
rows , err := db . client . QueryContext ( ctx , query . String ( ) , query . Args ( ) ... )
if err != nil {
return
}
defer rows . Close ( )
2023-06-02 20:08:45 +00:00
return query . TransferScan ( rows )
2023-04-21 11:59:29 +00:00
}
2023-11-02 17:24:23 +00:00
func ( db * Database ) GetTransactionsToLoad ( chainID uint64 , address common . Address , blockNumber * big . Int ) ( rst [ ] * PreloadedTransaction , err error ) {
query := newTransfersQueryForPreloadedTransactions ( ) .
2021-09-09 14:28:54 +00:00
FilterNetwork ( chainID ) .
FilterLoaded ( 0 )
2023-11-27 10:08:17 +00:00
if address != ( common . Address { } ) {
query . FilterAddress ( address )
}
if blockNumber != nil {
query . FilterBlockNumber ( blockNumber )
}
2021-09-09 14:28:54 +00:00
rows , err := db . client . Query ( query . String ( ) , query . Args ( ) ... )
if err != nil {
return
}
defer rows . Close ( )
2023-06-02 20:08:45 +00:00
return query . PreloadedTransactionScan ( rows )
2021-09-09 14:28:54 +00:00
}
// statementCreator allows to pass transaction or database to use in consumer.
type statementCreator interface {
Prepare ( query string ) ( * sql . Stmt , error )
}
2023-06-02 20:08:45 +00:00
// Only used by status-mobile
2021-11-24 12:59:45 +00:00
func ( db * Database ) InsertBlock ( chainID uint64 , account common . Address , blockNumber * big . Int , blockHash common . Hash ) error {
var (
tx * sql . Tx
)
tx , err := db . client . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
2023-06-20 02:50:49 +00:00
blockDB := blockDBFields {
chainID : chainID ,
account : account ,
blockNumber : blockNumber ,
blockHash : blockHash ,
}
return insertBlockDBFields ( tx , blockDB )
}
type blockDBFields struct {
chainID uint64
account common . Address
blockNumber * big . Int
blockHash common . Hash
}
func insertBlockDBFields ( creator statementCreator , block blockDBFields ) error {
insert , err := creator . Prepare ( "INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash, loaded) VALUES (?, ?, ?, ?, ?)" )
2021-11-24 12:59:45 +00:00
if err != nil {
return err
}
2023-06-20 02:50:49 +00:00
_ , err = insert . Exec ( block . chainID , block . account , ( * bigint . SQLBigInt ) ( block . blockNumber ) , block . blockHash , true )
2021-11-24 12:59:45 +00:00
return err
}
2023-11-27 10:08:17 +00:00
func insertBlocksWithTransactions ( chainID uint64 , creator statementCreator , headers [ ] * DBHeader ) error {
2021-09-09 14:28:54 +00:00
insert , err := creator . Prepare ( "INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash, loaded) VALUES (?, ?, ?, ?, ?)" )
if err != nil {
return err
}
updateTx , err := creator . Prepare ( ` UPDATE transfers
2023-05-19 15:31:45 +00:00
SET log = ? , log_index = ?
2021-09-09 14:28:54 +00:00
WHERE network_id = ? AND address = ? AND hash = ? ` )
if err != nil {
return err
}
2021-11-24 12:59:45 +00:00
insertTx , err := creator . Prepare ( ` INSERT OR IGNORE
2023-11-02 17:24:23 +00:00
INTO transfers ( network_id , address , sender , hash , blk_number , blk_hash , type , timestamp , log , loaded , log_index , token_id , amount_padded128hex )
VALUES ( ? , ? , ? , ? , ? , ? , ? , 0 , ? , 0 , ? , ? , ? ) ` )
2021-09-09 14:28:54 +00:00
if err != nil {
return err
}
for _ , header := range headers {
2023-11-27 10:08:17 +00:00
_ , err = insert . Exec ( chainID , header . Address , ( * bigint . SQLBigInt ) ( header . Number ) , header . Hash , header . Loaded )
2021-09-09 14:28:54 +00:00
if err != nil {
return err
}
2023-06-02 20:08:45 +00:00
for _ , transaction := range header . PreloadedTransactions {
2023-05-19 15:31:45 +00:00
var logIndex * uint
if transaction . Log != nil {
logIndex = new ( uint )
* logIndex = transaction . Log . Index
}
2023-11-27 10:08:17 +00:00
res , err := updateTx . Exec ( & JSONBlob { transaction . Log } , logIndex , chainID , header . Address , transaction . ID )
2023-06-02 20:08:45 +00:00
if err != nil {
return err
}
affected , err := res . RowsAffected ( )
if err != nil {
return err
}
if affected > 0 {
continue
}
2023-11-02 17:24:23 +00:00
tokenID := ( * bigint . SQLBigIntBytes ) ( transaction . TokenID )
txValue := sqlite . BigIntToPadded128BitsStr ( transaction . Value )
2023-11-27 10:08:17 +00:00
// Is that correct to set sender as account address?
_ , err = insertTx . Exec ( chainID , header . Address , header . Address , transaction . ID , ( * bigint . SQLBigInt ) ( header . Number ) , header . Hash , transaction . Type , & JSONBlob { transaction . Log } , logIndex , tokenID , txValue )
2023-06-02 20:08:45 +00:00
if err != nil {
2023-11-02 17:24:23 +00:00
log . Error ( "error saving token transfer" , "err" , err )
2023-06-02 20:08:45 +00:00
return err
2021-09-09 14:28:54 +00:00
}
}
}
return nil
}
func updateOrInsertTransfers ( chainID uint64 , creator statementCreator , transfers [ ] Transfer ) error {
2023-06-20 02:50:49 +00:00
txsDBFields := make ( [ ] transferDBFields , 0 , len ( transfers ) )
2024-04-18 16:48:02 +00:00
for _ , localTransfer := range transfers {
// to satisfy gosec: C601 checks
t := localTransfer
2023-05-19 15:31:45 +00:00
var receiptType * uint8
var txHash , blockHash * common . Hash
var receiptStatus , cumulativeGasUsed , gasUsed * uint64
var contractAddress * common . Address
var transactionIndex , logIndex * uint
if t . Receipt != nil {
receiptType = & t . Receipt . Type
receiptStatus = & t . Receipt . Status
txHash = & t . Receipt . TxHash
if t . Log != nil {
logIndex = new ( uint )
* logIndex = t . Log . Index
}
blockHash = & t . Receipt . BlockHash
cumulativeGasUsed = & t . Receipt . CumulativeGasUsed
contractAddress = & t . Receipt . ContractAddress
gasUsed = & t . Receipt . GasUsed
transactionIndex = & t . Receipt . TransactionIndex
}
var txProtected * bool
var txGas , txNonce , txSize * uint64
2023-06-20 02:50:49 +00:00
var txGasPrice , txGasTipCap , txGasFeeCap * big . Int
2023-05-19 15:31:45 +00:00
var txType * uint8
2023-06-20 02:50:49 +00:00
var txValue * big . Int
var tokenAddress * common . Address
2023-05-19 15:31:45 +00:00
var tokenID * big . Int
2023-06-20 02:50:49 +00:00
var txFrom * common . Address
var txTo * common . Address
2023-05-19 15:31:45 +00:00
if t . Transaction != nil {
if t . Log != nil {
2023-11-02 17:24:23 +00:00
_ , tokenAddress , txFrom , txTo = w_common . ExtractTokenTransferData ( t . Type , t . Log , t . Transaction )
tokenID = t . TokenID
// Zero tokenID can be used for ERC721 and ERC1155 transfers but when serialzed/deserialized it becomes nil
// as 0 value of big.Int bytes is nil.
if tokenID == nil && ( t . Type == w_common . Erc721Transfer || t . Type == w_common . Erc1155Transfer ) {
tokenID = big . NewInt ( 0 )
}
txValue = t . TokenValue
2023-05-19 15:31:45 +00:00
} else {
2023-06-20 02:50:49 +00:00
txValue = new ( big . Int ) . Set ( t . Transaction . Value ( ) )
txFrom = & t . From
txTo = t . Transaction . To ( )
2023-05-19 15:31:45 +00:00
}
txType = new ( uint8 )
* txType = t . Transaction . Type ( )
txProtected = new ( bool )
* txProtected = t . Transaction . Protected ( )
txGas = new ( uint64 )
* txGas = t . Transaction . Gas ( )
2023-06-20 02:50:49 +00:00
txGasPrice = t . Transaction . GasPrice ( )
txGasTipCap = t . Transaction . GasTipCap ( )
txGasFeeCap = t . Transaction . GasFeeCap ( )
2023-05-19 15:31:45 +00:00
txNonce = new ( uint64 )
* txNonce = t . Transaction . Nonce ( )
txSize = new ( uint64 )
2023-07-03 17:32:44 +00:00
* txSize = t . Transaction . Size ( )
2023-05-19 15:31:45 +00:00
}
2023-06-20 02:50:49 +00:00
dbFields := transferDBFields {
chainID : chainID ,
id : t . ID ,
blockHash : t . BlockHash ,
blockNumber : t . BlockNumber ,
timestamp : t . Timestamp ,
address : t . Address ,
transaction : t . Transaction ,
sender : t . From ,
receipt : t . Receipt ,
log : t . Log ,
transferType : t . Type ,
baseGasFees : t . BaseGasFees ,
multiTransactionID : t . MultiTransactionID ,
receiptStatus : receiptStatus ,
receiptType : receiptType ,
txHash : txHash ,
logIndex : logIndex ,
receiptBlockHash : blockHash ,
cumulativeGasUsed : cumulativeGasUsed ,
contractAddress : contractAddress ,
gasUsed : gasUsed ,
transactionIndex : transactionIndex ,
txType : txType ,
txProtected : txProtected ,
txGas : txGas ,
txGasPrice : txGasPrice ,
txGasTipCap : txGasTipCap ,
txGasFeeCap : txGasFeeCap ,
txValue : txValue ,
txNonce : txNonce ,
txSize : txSize ,
tokenAddress : tokenAddress ,
tokenID : tokenID ,
txFrom : txFrom ,
txTo : txTo ,
}
txsDBFields = append ( txsDBFields , dbFields )
}
return updateOrInsertTransfersDBFields ( creator , txsDBFields )
}
type transferDBFields struct {
chainID uint64
id common . Hash
blockHash common . Hash
blockNumber * big . Int
timestamp uint64
address common . Address
transaction * types . Transaction
sender common . Address
receipt * types . Receipt
log * types . Log
transferType w_common . Type
baseGasFees string
2024-03-12 09:15:30 +00:00
multiTransactionID w_common . MultiTransactionIDType
2023-06-20 02:50:49 +00:00
receiptStatus * uint64
receiptType * uint8
txHash * common . Hash
logIndex * uint
receiptBlockHash * common . Hash
cumulativeGasUsed * uint64
contractAddress * common . Address
gasUsed * uint64
transactionIndex * uint
txType * uint8
txProtected * bool
txGas * uint64
txGasPrice * big . Int
txGasTipCap * big . Int
txGasFeeCap * big . Int
txValue * big . Int
txNonce * uint64
txSize * uint64
tokenAddress * common . Address
tokenID * big . Int
txFrom * common . Address
txTo * common . Address
}
func updateOrInsertTransfersDBFields ( creator statementCreator , transfers [ ] transferDBFields ) error {
insert , err := creator . Prepare ( ` INSERT OR REPLACE INTO transfers
( network_id , hash , blk_hash , blk_number , timestamp , address , tx , sender , receipt , log , type , loaded , base_gas_fee , multi_transaction_id ,
status , receipt_type , tx_hash , log_index , block_hash , cumulative_gas_used , contract_address , gas_used , tx_index ,
tx_type , protected , gas_limit , gas_price_clamped64 , gas_tip_cap_clamped64 , gas_fee_cap_clamped64 , amount_padded128hex , account_nonce , size , token_address , token_id , tx_from_address , tx_to_address )
VALUES
( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , 1 , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ` )
if err != nil {
return err
}
for _ , t := range transfers {
txGasPrice := sqlite . BigIntToClampedInt64 ( t . txGasPrice )
txGasTipCap := sqlite . BigIntToClampedInt64 ( t . txGasTipCap )
txGasFeeCap := sqlite . BigIntToClampedInt64 ( t . txGasFeeCap )
txValue := sqlite . BigIntToPadded128BitsStr ( t . txValue )
_ , err = insert . Exec ( t . chainID , t . id , t . blockHash , ( * bigint . SQLBigInt ) ( t . blockNumber ) , t . timestamp , t . address , & JSONBlob { t . transaction } , t . sender , & JSONBlob { t . receipt } , & JSONBlob { t . log } , t . transferType , t . baseGasFees , t . multiTransactionID ,
t . receiptStatus , t . receiptType , t . txHash , t . logIndex , t . receiptBlockHash , t . cumulativeGasUsed , t . contractAddress , t . gasUsed , t . transactionIndex ,
t . txType , t . txProtected , t . txGas , txGasPrice , txGasTipCap , txGasFeeCap , txValue , t . txNonce , t . txSize , t . tokenAddress , ( * bigint . SQLBigIntBytes ) ( t . tokenID ) , t . txFrom , t . txTo )
2021-09-09 14:28:54 +00:00
if err != nil {
2023-06-20 02:50:49 +00:00
log . Error ( "can't save transfer" , "b-hash" , t . blockHash , "b-n" , t . blockNumber , "a" , t . address , "h" , t . id )
2021-09-09 14:28:54 +00:00
return err
}
2024-02-28 10:44:47 +00:00
}
2024-01-24 13:00:08 +00:00
2024-02-28 10:44:47 +00:00
for _ , t := range transfers {
2024-01-24 13:00:08 +00:00
err = removeGasOnlyEthTransfer ( creator , t )
if err != nil {
log . Error ( "can't remove gas only eth transfer" , "b-hash" , t . blockHash , "b-n" , t . blockNumber , "a" , t . address , "h" , t . id , "err" , err )
// no return err, since it's not critical
}
}
return nil
}
func removeGasOnlyEthTransfer ( creator statementCreator , t transferDBFields ) error {
2024-02-28 10:44:47 +00:00
if t . transferType == w_common . EthTransfer {
countQuery , err := creator . Prepare ( ` SELECT COUNT(*) FROM transfers WHERE tx_hash = ? ` )
2024-01-24 13:00:08 +00:00
if err != nil {
return err
}
2024-02-28 10:44:47 +00:00
defer countQuery . Close ( )
2024-01-24 13:00:08 +00:00
2024-02-28 10:44:47 +00:00
var count int
err = countQuery . QueryRow ( t . txHash ) . Scan ( & count )
2024-01-24 13:00:08 +00:00
if err != nil {
return err
}
2024-02-28 10:44:47 +00:00
// If there's only one (or none), return without deleting
if count <= 1 {
log . Debug ( "Only one or no transfer found with the same tx_hash, skipping deletion." )
return nil
}
}
query , err := creator . Prepare ( ` DELETE FROM transfers WHERE tx_hash = ? AND address = ? AND network_id = ? AND account_nonce = ? AND type = 'eth' AND amount_padded128hex = '00000000000000000000000000000000' ` )
if err != nil {
return err
}
defer query . Close ( )
res , err := query . Exec ( t . txHash , t . address , t . chainID , t . txNonce )
if err != nil {
return err
}
count , err := res . RowsAffected ( )
if err != nil {
return err
2021-09-09 14:28:54 +00:00
}
2024-05-23 10:01:55 +00:00
log . Debug ( "removeGasOnlyEthTransfer rows deleted" , "count" , count )
2021-09-09 14:28:54 +00:00
return nil
}
2023-11-27 10:08:17 +00:00
// markBlocksAsLoaded(chainID, tx, address, blockNumbers)
// In case block contains both ETH and token transfers, it will be marked as loaded on ETH transfer processing.
// This is not a problem since for token transfers we have preloaded transactions and blocks 'loaded' flag is needed
// for ETH transfers only.
2021-09-09 14:28:54 +00:00
func markBlocksAsLoaded ( chainID uint64 , creator statementCreator , address common . Address , blocks [ ] * big . Int ) error {
update , err := creator . Prepare ( "UPDATE blocks SET loaded=? WHERE address=? AND blk_number=? AND network_id=?" )
if err != nil {
return err
}
for _ , block := range blocks {
_ , err := update . Exec ( true , address , ( * bigint . SQLBigInt ) ( block ) , chainID )
if err != nil {
return err
}
}
return nil
}
2023-09-06 19:15:07 +00:00
// GetOwnedMultiTransactionID returns sql.ErrNoRows if no transaction is found for the given identity
2024-04-04 20:02:07 +00:00
func GetOwnedMultiTransactionID ( tx * sql . Tx , chainID w_common . ChainID , hash common . Hash , address common . Address ) ( mTID int64 , err error ) {
row := tx . QueryRow ( ` SELECT COALESCE(multi_transaction_id, 0) FROM transfers WHERE network_id = ? AND tx_hash = ? AND address = ? ` , chainID , hash , address )
2023-09-06 19:15:07 +00:00
err = row . Scan ( & mTID )
if err != nil {
return 0 , err
}
return mTID , nil
}
2023-11-28 14:23:03 +00:00
2024-01-08 05:21:50 +00:00
func ( db * Database ) GetLatestCollectibleTransfer ( address common . Address , id thirdparty . CollectibleUniqueID ) ( * Transfer , error ) {
query := newTransfersQuery ( ) .
FilterAddress ( address ) .
FilterNetwork ( uint64 ( id . ContractID . ChainID ) ) .
FilterTokenAddress ( id . ContractID . Address ) .
FilterTokenID ( id . TokenID . Int ) .
FilterLoaded ( 1 ) .
SortByTimestamp ( false ) .
Limit ( 1 )
rows , err := db . client . Query ( query . String ( ) , query . Args ( ) ... )
if err != nil {
return nil , err
}
defer rows . Close ( )
transfers , err := query . TransferScan ( rows )
if err == sql . ErrNoRows || len ( transfers ) == 0 {
return nil , nil
} else if err != nil {
return nil , err
}
return & transfers [ 0 ] , nil
}
2023-11-28 14:23:03 +00:00
// Delete blocks for address and chainID
// Transfers will be deleted by cascade
func deleteBlocks ( creator statementCreator , address common . Address ) error {
delete , err := creator . Prepare ( "DELETE FROM blocks WHERE address = ?" )
if err != nil {
return err
}
_ , err = delete . Exec ( address )
return err
}
func getAddresses ( creator statementCreator ) ( rst [ ] common . Address , err error ) {
stmt , err := creator . Prepare ( ` SELECT address FROM transfers UNION SELECT address FROM blocks UNION
SELECT address FROM blocks_ranges_sequential UNION SELECT address FROM blocks_ranges ` )
if err != nil {
return
}
rows , err := stmt . Query ( )
if err != nil {
return nil , err
}
defer rows . Close ( )
address := common . Address { }
for rows . Next ( ) {
err = rows . Scan ( & address )
if err != nil {
return nil , err
}
rst = append ( rst , address )
}
return rst , nil
}