feat: explorer API, wallet, CCIP scripts, and config refresh
- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
This commit is contained in:
@@ -2,10 +2,13 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -122,7 +125,7 @@ func (s *Server) handleEtherscanAPI(w http.ResponseWriter, r *http.Request) {
|
||||
var timestamp time.Time
|
||||
var transactionCount int
|
||||
var gasUsed, gasLimit int64
|
||||
var transactions []string
|
||||
var transactions interface{}
|
||||
|
||||
query := `
|
||||
SELECT hash, parent_hash, timestamp, miner, transaction_count, gas_used, gas_limit
|
||||
@@ -142,40 +145,28 @@ func (s *Server) handleEtherscanAPI(w http.ResponseWriter, r *http.Request) {
|
||||
break
|
||||
}
|
||||
|
||||
// If boolean is true, get full transaction objects
|
||||
if boolean {
|
||||
txQuery := `
|
||||
SELECT hash FROM transactions
|
||||
WHERE chain_id = $1 AND block_number = $2
|
||||
ORDER BY transaction_index
|
||||
`
|
||||
rows, err := s.db.Query(ctx, txQuery, s.chainID, blockNumber)
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var txHash string
|
||||
if err := rows.Scan(&txHash); err == nil {
|
||||
transactions = append(transactions, txHash)
|
||||
}
|
||||
txObjects, err := s.loadEtherscanBlockTransactions(ctx, blockNumber)
|
||||
if err != nil {
|
||||
response = EtherscanResponse{
|
||||
Status: "0",
|
||||
Message: "Error",
|
||||
Result: nil,
|
||||
}
|
||||
break
|
||||
}
|
||||
transactions = txObjects
|
||||
} else {
|
||||
// Just get transaction hashes
|
||||
txQuery := `
|
||||
SELECT hash FROM transactions
|
||||
WHERE chain_id = $1 AND block_number = $2
|
||||
ORDER BY transaction_index
|
||||
`
|
||||
rows, err := s.db.Query(ctx, txQuery, s.chainID, blockNumber)
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var txHash string
|
||||
if err := rows.Scan(&txHash); err == nil {
|
||||
transactions = append(transactions, txHash)
|
||||
}
|
||||
txHashes, err := s.loadEtherscanBlockTransactionHashes(ctx, blockNumber)
|
||||
if err != nil {
|
||||
response = EtherscanResponse{
|
||||
Status: "0",
|
||||
Message: "Error",
|
||||
Result: nil,
|
||||
}
|
||||
break
|
||||
}
|
||||
transactions = txHashes
|
||||
}
|
||||
|
||||
blockResult := map[string]interface{}{
|
||||
@@ -216,3 +207,92 @@ func (s *Server) handleEtherscanAPI(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func (s *Server) loadEtherscanBlockTransactionHashes(ctx context.Context, blockNumber int64) ([]string, error) {
|
||||
rows, err := s.db.Query(ctx, `
|
||||
SELECT hash
|
||||
FROM transactions
|
||||
WHERE chain_id = $1 AND block_number = $2
|
||||
ORDER BY transaction_index
|
||||
`, s.chainID, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
hashes := make([]string, 0)
|
||||
for rows.Next() {
|
||||
var txHash string
|
||||
if err := rows.Scan(&txHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashes = append(hashes, txHash)
|
||||
}
|
||||
|
||||
return hashes, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Server) loadEtherscanBlockTransactions(ctx context.Context, blockNumber int64) ([]map[string]interface{}, error) {
|
||||
rows, err := s.db.Query(ctx, `
|
||||
SELECT hash, block_hash, transaction_index, from_address, to_address, value::text,
|
||||
COALESCE(gas_price, 0), gas_limit, nonce, COALESCE(input_data, '')
|
||||
FROM transactions
|
||||
WHERE chain_id = $1 AND block_number = $2
|
||||
ORDER BY transaction_index
|
||||
`, s.chainID, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
transactions := make([]map[string]interface{}, 0)
|
||||
for rows.Next() {
|
||||
var hash, blockHash, fromAddress, value, inputData string
|
||||
var toAddress sql.NullString
|
||||
var transactionIndex int
|
||||
var gasPrice, gasLimit, nonce int64
|
||||
if err := rows.Scan(&hash, &blockHash, &transactionIndex, &fromAddress, &toAddress, &value, &gasPrice, &gasLimit, &nonce, &inputData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := map[string]interface{}{
|
||||
"hash": hash,
|
||||
"blockHash": blockHash,
|
||||
"blockNumber": fmt.Sprintf("0x%x", blockNumber),
|
||||
"transactionIndex": fmt.Sprintf("0x%x", transactionIndex),
|
||||
"from": fromAddress,
|
||||
"value": decimalStringToHex(value),
|
||||
"gasPrice": fmt.Sprintf("0x%x", gasPrice),
|
||||
"gas": fmt.Sprintf("0x%x", gasLimit),
|
||||
"nonce": fmt.Sprintf("0x%x", nonce),
|
||||
"input": normalizeHexInput(inputData),
|
||||
}
|
||||
if toAddress.Valid && toAddress.String != "" {
|
||||
tx["to"] = toAddress.String
|
||||
} else {
|
||||
tx["to"] = nil
|
||||
}
|
||||
|
||||
transactions = append(transactions, tx)
|
||||
}
|
||||
|
||||
return transactions, rows.Err()
|
||||
}
|
||||
|
||||
func decimalStringToHex(value string) string {
|
||||
parsed, ok := new(big.Int).SetString(value, 10)
|
||||
if !ok {
|
||||
return "0x0"
|
||||
}
|
||||
return "0x" + parsed.Text(16)
|
||||
}
|
||||
|
||||
func normalizeHexInput(input string) string {
|
||||
trimmed := strings.TrimSpace(input)
|
||||
if trimmed == "" {
|
||||
return "0x"
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "0x") {
|
||||
return trimmed
|
||||
}
|
||||
return "0x" + trimmed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user