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 }