2023-08-24 12:23:40 +00:00
|
|
|
package activity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
2023-08-29 17:17:50 +00:00
|
|
|
"math/big"
|
2023-08-24 12:23:40 +00:00
|
|
|
|
|
|
|
eth "github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/status-im/status-go/services/wallet/common"
|
|
|
|
"github.com/status-im/status-go/sqlite"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ProtocolType = int
|
|
|
|
|
|
|
|
const (
|
|
|
|
ProtocolHop ProtocolType = iota + 1
|
|
|
|
ProtocolUniswap
|
|
|
|
)
|
|
|
|
|
2023-11-30 11:37:32 +00:00
|
|
|
type EntryChainDetails struct {
|
|
|
|
ChainID int64 `json:"chainId"`
|
|
|
|
BlockNumber int64 `json:"blockNumber"`
|
|
|
|
Hash eth.Hash `json:"hash"`
|
|
|
|
Contract *eth.Address `json:"contractAddress,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-08-24 12:23:40 +00:00
|
|
|
type EntryDetails struct {
|
2023-11-30 11:37:32 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
MultiTxID int `json:"multiTxId"`
|
|
|
|
Nonce uint64 `json:"nonce"`
|
|
|
|
ChainDetails []EntryChainDetails `json:"chainDetails"`
|
|
|
|
Input string `json:"input"`
|
|
|
|
ProtocolType *ProtocolType `json:"protocolType,omitempty"`
|
|
|
|
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
|
|
|
|
GasLimit uint64 `json:"gasLimit"`
|
|
|
|
TotalFees *hexutil.Big `json:"totalFees,omitempty"`
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func protocolTypeFromDBType(dbType string) (protocolType *ProtocolType) {
|
|
|
|
protocolType = new(ProtocolType)
|
|
|
|
switch common.Type(dbType) {
|
|
|
|
case common.UniswapV2Swap:
|
|
|
|
fallthrough
|
|
|
|
case common.UniswapV3Swap:
|
|
|
|
*protocolType = ProtocolUniswap
|
|
|
|
case common.HopBridgeFrom:
|
|
|
|
fallthrough
|
|
|
|
case common.HopBridgeTo:
|
|
|
|
*protocolType = ProtocolHop
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return protocolType
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMultiTxDetails(ctx context.Context, db *sql.DB, multiTxID int) (*EntryDetails, error) {
|
|
|
|
if multiTxID <= 0 {
|
|
|
|
return nil, errors.New("invalid tx id")
|
|
|
|
}
|
2023-11-30 11:37:32 +00:00
|
|
|
|
|
|
|
// Extracting tx only when values are not null to prevent errors during the scan.
|
2023-08-24 12:23:40 +00:00
|
|
|
rows, err := db.QueryContext(ctx, `
|
2023-08-29 17:17:50 +00:00
|
|
|
SELECT
|
|
|
|
tx_hash,
|
2023-08-24 12:23:40 +00:00
|
|
|
blk_number,
|
2023-11-30 11:37:32 +00:00
|
|
|
network_id,
|
2023-08-24 12:23:40 +00:00
|
|
|
type,
|
|
|
|
account_nonce,
|
2023-11-30 11:37:32 +00:00
|
|
|
contract_address,
|
|
|
|
CASE
|
|
|
|
WHEN json_extract(tx, '$.gas') = '0x0' THEN NULL
|
|
|
|
ELSE transfers.tx
|
|
|
|
END as tx,
|
|
|
|
base_gas_fee
|
2023-08-29 17:17:50 +00:00
|
|
|
FROM
|
|
|
|
transfers
|
|
|
|
WHERE
|
2023-08-24 12:23:40 +00:00
|
|
|
multi_transaction_id = ?;`, multiTxID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
var maxFeePerGas *hexutil.Big
|
|
|
|
var input string
|
|
|
|
var protocolType *ProtocolType
|
2023-11-30 11:37:32 +00:00
|
|
|
var nonce, gasLimit uint64
|
|
|
|
var totalFees *hexutil.Big
|
|
|
|
var chainDetailsList []EntryChainDetails
|
2023-08-24 12:23:40 +00:00
|
|
|
for rows.Next() {
|
|
|
|
var contractTypeDB sql.NullString
|
2023-11-30 11:37:32 +00:00
|
|
|
var chainIDDB, nonceDB, blockNumber sql.NullInt64
|
2023-08-24 12:23:40 +00:00
|
|
|
var transferHashDB, contractAddressDB sql.RawBytes
|
2023-11-30 11:37:32 +00:00
|
|
|
var baseGasFees string
|
2023-08-24 12:23:40 +00:00
|
|
|
tx := &types.Transaction{}
|
|
|
|
nullableTx := sqlite.JSONBlob{Data: tx}
|
2023-11-30 11:37:32 +00:00
|
|
|
err := rows.Scan(&transferHashDB, &blockNumber, &chainIDDB, &contractTypeDB, &nonceDB, &contractAddressDB, &nullableTx, &baseGasFees)
|
2023-08-24 12:23:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-30 11:37:32 +00:00
|
|
|
|
|
|
|
var chainID int64
|
|
|
|
if chainIDDB.Valid {
|
|
|
|
chainID = chainIDDB.Int64
|
|
|
|
}
|
|
|
|
chainDetails := getChainDetails(chainID, &chainDetailsList)
|
|
|
|
|
2023-08-24 12:23:40 +00:00
|
|
|
if len(transferHashDB) > 0 {
|
2023-11-30 11:37:32 +00:00
|
|
|
chainDetails.Hash = eth.BytesToHash(transferHashDB)
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
if contractTypeDB.Valid && protocolType == nil {
|
|
|
|
protocolType = protocolTypeFromDBType(contractTypeDB.String)
|
|
|
|
}
|
|
|
|
|
2023-11-30 11:37:32 +00:00
|
|
|
if blockNumber.Valid {
|
|
|
|
chainDetails.BlockNumber = blockNumber.Int64
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
2023-11-30 11:37:32 +00:00
|
|
|
if nonceDB.Valid {
|
|
|
|
nonce = uint64(nonceDB.Int64)
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
2023-11-30 11:37:32 +00:00
|
|
|
|
|
|
|
if len(contractAddressDB) > 0 && chainDetails.Contract == nil {
|
|
|
|
chainDetails.Contract = new(eth.Address)
|
|
|
|
*chainDetails.Contract = eth.BytesToAddress(contractAddressDB)
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
2023-11-30 11:37:32 +00:00
|
|
|
if nullableTx.Valid {
|
|
|
|
input = "0x" + hex.EncodeToString(tx.Data())
|
|
|
|
maxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
|
|
|
|
gasLimit = tx.Gas()
|
|
|
|
baseGasFees, _ := new(big.Int).SetString(baseGasFees, 0)
|
|
|
|
totalFees = (*hexutil.Big)(getTotalFees(tx, baseGasFees))
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-30 11:37:32 +00:00
|
|
|
|
|
|
|
if maxFeePerGas == nil {
|
|
|
|
maxFeePerGas = (*hexutil.Big)(big.NewInt(0))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(input) == 0 {
|
|
|
|
input = "0x"
|
|
|
|
}
|
|
|
|
|
2023-08-24 12:23:40 +00:00
|
|
|
return &EntryDetails{
|
|
|
|
MultiTxID: multiTxID,
|
|
|
|
Nonce: nonce,
|
|
|
|
ProtocolType: protocolType,
|
|
|
|
Input: input,
|
|
|
|
MaxFeePerGas: maxFeePerGas,
|
|
|
|
GasLimit: gasLimit,
|
2023-11-30 11:37:32 +00:00
|
|
|
ChainDetails: chainDetailsList,
|
|
|
|
TotalFees: totalFees,
|
2023-08-24 12:23:40 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTxDetails(ctx context.Context, db *sql.DB, id string) (*EntryDetails, error) {
|
|
|
|
if len(id) == 0 {
|
|
|
|
return nil, errors.New("invalid tx id")
|
|
|
|
}
|
|
|
|
rows, err := db.QueryContext(ctx, `
|
2023-08-29 17:17:50 +00:00
|
|
|
SELECT
|
2023-08-24 12:23:40 +00:00
|
|
|
tx_hash,
|
|
|
|
blk_number,
|
2023-11-30 11:37:32 +00:00
|
|
|
network_id,
|
2023-08-24 12:23:40 +00:00
|
|
|
account_nonce,
|
|
|
|
tx,
|
2023-08-29 17:17:50 +00:00
|
|
|
contract_address,
|
|
|
|
base_gas_fee
|
|
|
|
FROM
|
|
|
|
transfers
|
|
|
|
WHERE
|
2023-08-24 12:23:40 +00:00
|
|
|
hash = ?;`, eth.HexToHash(id))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
if !rows.Next() {
|
|
|
|
return nil, errors.New("Entry not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
tx := &types.Transaction{}
|
|
|
|
nullableTx := sqlite.JSONBlob{Data: tx}
|
|
|
|
var transferHashDB, contractAddressDB sql.RawBytes
|
2023-11-30 11:37:32 +00:00
|
|
|
var chainIDDB, nonceDB, blockNumberDB sql.NullInt64
|
2023-08-29 17:17:50 +00:00
|
|
|
var baseGasFees string
|
2023-11-30 11:37:32 +00:00
|
|
|
err = rows.Scan(&transferHashDB, &blockNumberDB, &chainIDDB, &nonceDB, &nullableTx, &contractAddressDB, &baseGasFees)
|
2023-08-24 12:23:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
details := &EntryDetails{
|
2023-11-30 11:37:32 +00:00
|
|
|
ID: id,
|
|
|
|
}
|
|
|
|
|
|
|
|
var chainID int64
|
|
|
|
if chainIDDB.Valid {
|
|
|
|
chainID = chainIDDB.Int64
|
|
|
|
}
|
|
|
|
chainDetails := getChainDetails(chainID, &details.ChainDetails)
|
|
|
|
|
|
|
|
if blockNumberDB.Valid {
|
|
|
|
chainDetails.BlockNumber = blockNumberDB.Int64
|
|
|
|
}
|
|
|
|
|
|
|
|
if nonceDB.Valid {
|
|
|
|
details.Nonce = uint64(nonceDB.Int64)
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(transferHashDB) > 0 {
|
2023-11-30 11:37:32 +00:00
|
|
|
chainDetails.Hash = eth.BytesToHash(transferHashDB)
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(contractAddressDB) > 0 {
|
2023-11-30 11:37:32 +00:00
|
|
|
chainDetails.Contract = new(eth.Address)
|
|
|
|
*chainDetails.Contract = eth.BytesToAddress(contractAddressDB)
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if nullableTx.Valid {
|
|
|
|
details.Input = "0x" + hex.EncodeToString(tx.Data())
|
|
|
|
details.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
|
2023-11-30 11:37:32 +00:00
|
|
|
details.GasLimit = tx.Gas()
|
2023-08-29 17:17:50 +00:00
|
|
|
baseGasFees, _ := new(big.Int).SetString(baseGasFees, 0)
|
|
|
|
details.TotalFees = (*hexutil.Big)(getTotalFees(tx, baseGasFees))
|
2023-08-24 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return details, nil
|
|
|
|
}
|
2023-08-29 17:17:50 +00:00
|
|
|
|
|
|
|
func getTotalFees(tx *types.Transaction, baseFee *big.Int) *big.Int {
|
|
|
|
if tx.Type() == types.DynamicFeeTxType {
|
|
|
|
// EIP-1559 transaction
|
|
|
|
if baseFee == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
tip := tx.GasTipCap()
|
|
|
|
maxFee := tx.GasFeeCap()
|
|
|
|
gasUsed := big.NewInt(int64(tx.Gas()))
|
|
|
|
|
|
|
|
totalGasUsed := new(big.Int).Add(tip, baseFee)
|
|
|
|
if totalGasUsed.Cmp(maxFee) > 0 {
|
|
|
|
totalGasUsed.Set(maxFee)
|
|
|
|
}
|
|
|
|
|
|
|
|
return new(big.Int).Mul(totalGasUsed, gasUsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Legacy transaction
|
|
|
|
gasPrice := tx.GasPrice()
|
|
|
|
gasUsed := big.NewInt(int64(tx.Gas()))
|
|
|
|
|
|
|
|
return new(big.Int).Mul(gasPrice, gasUsed)
|
|
|
|
}
|
2023-11-30 11:37:32 +00:00
|
|
|
|
|
|
|
func getChainDetails(chainID int64, data *[]EntryChainDetails) *EntryChainDetails {
|
|
|
|
for i, entry := range *data {
|
|
|
|
if entry.ChainID == chainID {
|
|
|
|
return &(*data)[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*data = append(*data, EntryChainDetails{
|
|
|
|
ChainID: chainID,
|
|
|
|
})
|
|
|
|
return &(*data)[len(*data)-1]
|
|
|
|
}
|