github.com/status-im/status-go@v1.1.0/services/wallet/activity/details.go (about)

     1  package activity
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/hex"
     7  	"errors"
     8  	"math/big"
     9  
    10  	// used for embedding the sql query in the binary
    11  	_ "embed"
    12  
    13  	eth "github.com/ethereum/go-ethereum/common"
    14  	"github.com/ethereum/go-ethereum/common/hexutil"
    15  	"github.com/ethereum/go-ethereum/core/types"
    16  	"github.com/status-im/status-go/services/wallet/common"
    17  	"github.com/status-im/status-go/sqlite"
    18  )
    19  
    20  type ProtocolType = int
    21  
    22  const (
    23  	ProtocolHop ProtocolType = iota + 1
    24  	ProtocolUniswap
    25  )
    26  
    27  type EntryChainDetails struct {
    28  	ChainID     int64        `json:"chainId"`
    29  	BlockNumber int64        `json:"blockNumber"`
    30  	Hash        eth.Hash     `json:"hash"`
    31  	Contract    *eth.Address `json:"contractAddress,omitempty"`
    32  }
    33  
    34  type EntryDetails struct {
    35  	ID           string              `json:"id"`
    36  	MultiTxID    int                 `json:"multiTxId"`
    37  	Nonce        uint64              `json:"nonce"`
    38  	ChainDetails []EntryChainDetails `json:"chainDetails"`
    39  	Input        string              `json:"input"`
    40  	ProtocolType *ProtocolType       `json:"protocolType,omitempty"`
    41  	MaxFeePerGas *hexutil.Big        `json:"maxFeePerGas"`
    42  	GasLimit     uint64              `json:"gasLimit"`
    43  	TotalFees    *hexutil.Big        `json:"totalFees,omitempty"`
    44  }
    45  
    46  //go:embed multiTxDetails.sql
    47  var queryMultiTxDetailsString string
    48  
    49  //go:embed txDetails.sql
    50  var queryTxDetailsString string
    51  
    52  func protocolTypeFromDBType(dbType string) (protocolType *ProtocolType) {
    53  	protocolType = new(ProtocolType)
    54  	switch common.Type(dbType) {
    55  	case common.UniswapV2Swap:
    56  		fallthrough
    57  	case common.UniswapV3Swap:
    58  		*protocolType = ProtocolUniswap
    59  	case common.HopBridgeFrom:
    60  		fallthrough
    61  	case common.HopBridgeTo:
    62  		*protocolType = ProtocolHop
    63  	default:
    64  		return nil
    65  	}
    66  	return protocolType
    67  }
    68  
    69  func getMultiTxDetails(ctx context.Context, db *sql.DB, multiTxID int) (*EntryDetails, error) {
    70  	if multiTxID <= 0 {
    71  		return nil, errors.New("invalid tx id")
    72  	}
    73  
    74  	rows, err := db.QueryContext(ctx, queryMultiTxDetailsString, multiTxID, multiTxID)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	defer rows.Close()
    79  
    80  	var maxFeePerGas *hexutil.Big
    81  	var input string
    82  	var protocolType *ProtocolType
    83  	var nonce, gasLimit uint64
    84  	var totalFees *hexutil.Big
    85  	var chainDetailsList []EntryChainDetails
    86  	for rows.Next() {
    87  		var contractTypeDB sql.NullString
    88  		var chainIDDB, nonceDB, blockNumber sql.NullInt64
    89  		var transferHashDB, contractAddressDB sql.RawBytes
    90  		var baseGasFees *string
    91  		var baseGasFeesDB sql.NullString
    92  		tx := &types.Transaction{}
    93  		nullableTx := sqlite.JSONBlob{Data: tx}
    94  		err := rows.Scan(&transferHashDB, &blockNumber, &chainIDDB, &contractTypeDB, &nonceDB, &contractAddressDB, &nullableTx, &baseGasFeesDB)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  
    99  		var chainID int64
   100  		if chainIDDB.Valid {
   101  			chainID = chainIDDB.Int64
   102  		}
   103  		chainDetails := getChainDetails(chainID, &chainDetailsList)
   104  
   105  		if baseGasFeesDB.Valid {
   106  			baseGasFees = common.NewAndSet(baseGasFeesDB.String)
   107  		}
   108  
   109  		if len(transferHashDB) > 0 {
   110  			chainDetails.Hash = eth.BytesToHash(transferHashDB)
   111  		}
   112  		if contractTypeDB.Valid && protocolType == nil {
   113  			protocolType = protocolTypeFromDBType(contractTypeDB.String)
   114  		}
   115  
   116  		if blockNumber.Valid {
   117  			chainDetails.BlockNumber = blockNumber.Int64
   118  		}
   119  		if nonceDB.Valid {
   120  			nonce = uint64(nonceDB.Int64)
   121  		}
   122  
   123  		if len(contractAddressDB) > 0 && chainDetails.Contract == nil {
   124  			chainDetails.Contract = new(eth.Address)
   125  			*chainDetails.Contract = eth.BytesToAddress(contractAddressDB)
   126  		}
   127  
   128  		if nullableTx.Valid {
   129  			input = "0x" + hex.EncodeToString(tx.Data())
   130  			maxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
   131  			gasLimit = tx.Gas()
   132  			if baseGasFees != nil {
   133  				baseGasFees, _ := new(big.Int).SetString(*baseGasFees, 0)
   134  				totalFees = (*hexutil.Big)(getTotalFees(tx, baseGasFees))
   135  			}
   136  		}
   137  	}
   138  	if err = rows.Err(); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	if maxFeePerGas == nil {
   143  		maxFeePerGas = (*hexutil.Big)(big.NewInt(0))
   144  	}
   145  
   146  	if len(input) == 0 {
   147  		input = "0x"
   148  	}
   149  
   150  	return &EntryDetails{
   151  		MultiTxID:    multiTxID,
   152  		Nonce:        nonce,
   153  		ProtocolType: protocolType,
   154  		Input:        input,
   155  		MaxFeePerGas: maxFeePerGas,
   156  		GasLimit:     gasLimit,
   157  		ChainDetails: chainDetailsList,
   158  		TotalFees:    totalFees,
   159  	}, nil
   160  }
   161  
   162  func getTxDetails(ctx context.Context, db *sql.DB, id string) (*EntryDetails, error) {
   163  	if len(id) == 0 {
   164  		return nil, errors.New("invalid tx id")
   165  	}
   166  	rows, err := db.QueryContext(ctx, queryTxDetailsString, eth.HexToHash(id))
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	defer rows.Close()
   171  
   172  	if !rows.Next() {
   173  		return nil, errors.New("Entry not found")
   174  	}
   175  
   176  	tx := &types.Transaction{}
   177  	nullableTx := sqlite.JSONBlob{Data: tx}
   178  	var transferHashDB, contractAddressDB sql.RawBytes
   179  	var chainIDDB, nonceDB, blockNumberDB sql.NullInt64
   180  	var baseGasFees string
   181  	err = rows.Scan(&transferHashDB, &blockNumberDB, &chainIDDB, &nonceDB, &nullableTx, &contractAddressDB, &baseGasFees)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	details := &EntryDetails{
   187  		ID: id,
   188  	}
   189  
   190  	var chainID int64
   191  	if chainIDDB.Valid {
   192  		chainID = chainIDDB.Int64
   193  	}
   194  	chainDetails := getChainDetails(chainID, &details.ChainDetails)
   195  
   196  	if blockNumberDB.Valid {
   197  		chainDetails.BlockNumber = blockNumberDB.Int64
   198  	}
   199  
   200  	if nonceDB.Valid {
   201  		details.Nonce = uint64(nonceDB.Int64)
   202  	}
   203  
   204  	if len(transferHashDB) > 0 {
   205  		chainDetails.Hash = eth.BytesToHash(transferHashDB)
   206  	}
   207  
   208  	if len(contractAddressDB) > 0 {
   209  		chainDetails.Contract = new(eth.Address)
   210  		*chainDetails.Contract = eth.BytesToAddress(contractAddressDB)
   211  	}
   212  
   213  	if nullableTx.Valid {
   214  		details.Input = "0x" + hex.EncodeToString(tx.Data())
   215  		details.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
   216  		details.GasLimit = tx.Gas()
   217  		baseGasFees, _ := new(big.Int).SetString(baseGasFees, 0)
   218  		details.TotalFees = (*hexutil.Big)(getTotalFees(tx, baseGasFees))
   219  	}
   220  
   221  	return details, nil
   222  }
   223  
   224  func getTotalFees(tx *types.Transaction, baseFee *big.Int) *big.Int {
   225  	if tx.Type() == types.DynamicFeeTxType {
   226  		// EIP-1559 transaction
   227  		if baseFee == nil {
   228  			return nil
   229  		}
   230  		tip := tx.GasTipCap()
   231  		maxFee := tx.GasFeeCap()
   232  		gasUsed := big.NewInt(int64(tx.Gas()))
   233  
   234  		totalGasUsed := new(big.Int).Add(tip, baseFee)
   235  		if totalGasUsed.Cmp(maxFee) > 0 {
   236  			totalGasUsed.Set(maxFee)
   237  		}
   238  
   239  		return new(big.Int).Mul(totalGasUsed, gasUsed)
   240  	}
   241  
   242  	// Legacy transaction
   243  	gasPrice := tx.GasPrice()
   244  	gasUsed := big.NewInt(int64(tx.Gas()))
   245  
   246  	return new(big.Int).Mul(gasPrice, gasUsed)
   247  }
   248  
   249  func getChainDetails(chainID int64, data *[]EntryChainDetails) *EntryChainDetails {
   250  	for i, entry := range *data {
   251  		if entry.ChainID == chainID {
   252  			return &(*data)[i]
   253  		}
   254  	}
   255  	*data = append(*data, EntryChainDetails{
   256  		ChainID: chainID,
   257  	})
   258  	return &(*data)[len(*data)-1]
   259  }