mirror of
https://github.com/luxfi/indexer.git
synced 2026-09-24 00:25:37 +00:00
Lux Partners Limited is not the entity behind this work. Every place the old name stood — copyright lines, site footers, brand config, legal and compliance text — now names the company that actually holds it. lux.fund is the exception and is not renamed to match: that site belongs to Lux Assets Management LLC, a different company, and it says so. Two kinds of line were left for a person rather than rewritten. A dated news item recording that Lux Partners Limited was established in the Isle of Man is a historical fact, and renaming it would falsify the record. And a sentence asserting that the company is domiciled in the Isle of Man is a claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle of Man company, so swapping the name there would assert something untrue. Co-authored-by: Hanzo Dev <[email protected]>
776 lines
23 KiB
Go
776 lines
23 KiB
Go
// Copyright (c) 2025 Lux Industries Inc
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// RPCHandler handles Etherscan-compatible API requests
|
|
type RPCHandler struct {
|
|
repo *Repository
|
|
}
|
|
|
|
// NewRPCHandler creates a new RPC handler
|
|
func NewRPCHandler(repo *Repository) *RPCHandler {
|
|
return &RPCHandler{repo: repo}
|
|
}
|
|
|
|
// RPCResponse is the Etherscan-style API response
|
|
type RPCResponse struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Result interface{} `json:"result"`
|
|
}
|
|
|
|
// Handle processes RPC API requests
|
|
func (h *RPCHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
module := r.URL.Query().Get("module")
|
|
action := r.URL.Query().Get("action")
|
|
|
|
var resp RPCResponse
|
|
|
|
switch module {
|
|
case "account":
|
|
resp = h.handleAccount(r.Context(), action, r)
|
|
case "contract":
|
|
resp = h.handleContract(r.Context(), action, r)
|
|
case "transaction":
|
|
resp = h.handleTransaction(r.Context(), action, r)
|
|
case "block":
|
|
resp = h.handleBlock(r.Context(), action, r)
|
|
case "logs":
|
|
resp = h.handleLogs(r.Context(), action, r)
|
|
case "token":
|
|
resp = h.handleToken(r.Context(), action, r)
|
|
case "stats":
|
|
resp = h.handleStats(r.Context(), action, r)
|
|
case "proxy":
|
|
resp = h.handleProxy(r.Context(), action, r)
|
|
default:
|
|
resp = RPCResponse{
|
|
Status: "0",
|
|
Message: "NOTOK",
|
|
Result: "Invalid module",
|
|
}
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(resp)
|
|
}
|
|
|
|
func (h *RPCHandler) handleAccount(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "balance":
|
|
return h.getBalance(ctx, r)
|
|
case "balancemulti":
|
|
return h.getBalanceMulti(ctx, r)
|
|
case "txlist":
|
|
return h.getTxList(ctx, r)
|
|
case "txlistinternal":
|
|
return h.getInternalTxList(ctx, r)
|
|
case "tokentx":
|
|
return h.getTokenTx(ctx, r)
|
|
case "tokennfttx":
|
|
return h.getTokenNftTx(ctx, r)
|
|
case "token1155tx":
|
|
return h.getToken1155Tx(ctx, r)
|
|
case "getminedblocks":
|
|
return h.getMinedBlocks(ctx, r)
|
|
case "tokenbalance":
|
|
return h.getTokenBalance(ctx, r)
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|
|
|
|
func (h *RPCHandler) getBalance(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
if address == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address"}
|
|
}
|
|
|
|
balance, err := h.repo.GetAddressBalance(ctx, address)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: err.Error()}
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: balance}
|
|
}
|
|
|
|
func (h *RPCHandler) getBalanceMulti(ctx context.Context, r *http.Request) RPCResponse {
|
|
addresses := r.URL.Query().Get("address")
|
|
if addresses == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address"}
|
|
}
|
|
|
|
addrList := strings.Split(addresses, ",")
|
|
type BalanceResult struct {
|
|
Account string `json:"account"`
|
|
Balance string `json:"balance"`
|
|
}
|
|
|
|
var results []BalanceResult
|
|
for _, addr := range addrList {
|
|
addr = strings.TrimSpace(addr)
|
|
balance, _ := h.repo.GetAddressBalance(ctx, addr)
|
|
results = append(results, BalanceResult{Account: addr, Balance: balance})
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: results}
|
|
}
|
|
|
|
func (h *RPCHandler) getTxList(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
if address == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address"}
|
|
}
|
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
if offset <= 0 {
|
|
offset = 10
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
result, err := h.repo.GetAddressTransactions(ctx, address, page-1, offset)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: err.Error()}
|
|
}
|
|
|
|
// Convert to Etherscan format
|
|
txs := result.Items.([]Transaction)
|
|
var etherscanTxs []map[string]interface{}
|
|
|
|
for _, tx := range txs {
|
|
etx := map[string]interface{}{
|
|
"hash": tx.Hash,
|
|
"blockNumber": fmt.Sprintf("%d", *tx.BlockNumber),
|
|
"timeStamp": fmt.Sprintf("%d", tx.Timestamp.Unix()),
|
|
"from": tx.From.Hash,
|
|
"to": "",
|
|
"value": tx.Value,
|
|
"gas": fmt.Sprintf("%d", tx.Gas),
|
|
"gasPrice": tx.GasPrice,
|
|
"gasUsed": fmt.Sprintf("%d", *tx.GasUsed),
|
|
"nonce": fmt.Sprintf("%d", tx.Nonce),
|
|
"input": tx.Input,
|
|
"transactionIndex": fmt.Sprintf("%d", *tx.TransactionIndex),
|
|
"isError": "0",
|
|
"txreceipt_status": "1",
|
|
"contractAddress": "",
|
|
"confirmations": fmt.Sprintf("%d", tx.Confirmations),
|
|
}
|
|
if tx.To != nil {
|
|
etx["to"] = tx.To.Hash
|
|
}
|
|
if tx.Status == "error" {
|
|
etx["isError"] = "1"
|
|
etx["txreceipt_status"] = "0"
|
|
}
|
|
if tx.CreatedContract != nil {
|
|
etx["contractAddress"] = tx.CreatedContract.Hash
|
|
}
|
|
etherscanTxs = append(etherscanTxs, etx)
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: etherscanTxs}
|
|
}
|
|
|
|
func (h *RPCHandler) getInternalTxList(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
txhash := r.URL.Query().Get("txhash")
|
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
if offset <= 0 {
|
|
offset = 10
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
var result *PaginatedResponse
|
|
var err error
|
|
|
|
if txhash != "" {
|
|
result, err = h.repo.GetInternalTransactions(ctx, txhash, page-1, offset)
|
|
} else if address != "" {
|
|
// For address-based internal tx query
|
|
result = &PaginatedResponse{Items: []InternalTransaction{}}
|
|
} else {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address or txhash"}
|
|
}
|
|
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: err.Error()}
|
|
}
|
|
|
|
// Convert to Etherscan format
|
|
itxs := result.Items.([]InternalTransaction)
|
|
var etherscanTxs []map[string]interface{}
|
|
|
|
for _, itx := range itxs {
|
|
etx := map[string]interface{}{
|
|
"hash": itx.TxHash,
|
|
"blockNumber": fmt.Sprintf("%d", itx.BlockNumber),
|
|
"from": itx.From.Hash,
|
|
"to": "",
|
|
"value": itx.Value,
|
|
"gas": fmt.Sprintf("%d", itx.Gas),
|
|
"gasUsed": fmt.Sprintf("%d", *itx.GasUsed),
|
|
"input": itx.Input,
|
|
"type": itx.CallType,
|
|
"isError": "0",
|
|
"errCode": "",
|
|
}
|
|
if itx.To != nil {
|
|
etx["to"] = itx.To.Hash
|
|
}
|
|
if itx.Success != nil && !*itx.Success {
|
|
etx["isError"] = "1"
|
|
etx["errCode"] = itx.Error
|
|
}
|
|
etherscanTxs = append(etherscanTxs, etx)
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: etherscanTxs}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenTx(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
contractAddress := r.URL.Query().Get("contractaddress")
|
|
|
|
if address == "" && contractAddress == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address"}
|
|
}
|
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
if offset <= 0 {
|
|
offset = 10
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
targetAddr := address
|
|
if targetAddr == "" {
|
|
targetAddr = contractAddress
|
|
}
|
|
|
|
result, err := h.repo.GetAddressTokenTransfers(ctx, targetAddr, page-1, offset)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: err.Error()}
|
|
}
|
|
|
|
// Convert to Etherscan format
|
|
transfers := result.Items.([]TokenTransfer)
|
|
var etherscanTxs []map[string]interface{}
|
|
|
|
for _, t := range transfers {
|
|
// Filter by contractAddress if provided
|
|
if contractAddress != "" && !strings.EqualFold(t.Token.Address, contractAddress) {
|
|
continue
|
|
}
|
|
|
|
etx := map[string]interface{}{
|
|
"hash": t.TxHash,
|
|
"blockNumber": fmt.Sprintf("%d", t.BlockNumber),
|
|
"timeStamp": fmt.Sprintf("%d", t.Timestamp.Unix()),
|
|
"from": t.From.Hash,
|
|
"to": t.To.Hash,
|
|
"value": t.Total.Value,
|
|
"contractAddress": t.Token.Address,
|
|
"tokenName": t.Token.Name,
|
|
"tokenSymbol": t.Token.Symbol,
|
|
"tokenDecimal": fmt.Sprintf("%d", *t.Token.Decimals),
|
|
"transactionIndex": fmt.Sprintf("%d", t.LogIndex),
|
|
"gas": "0",
|
|
"gasPrice": "0",
|
|
"gasUsed": "0",
|
|
}
|
|
etherscanTxs = append(etherscanTxs, etx)
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: etherscanTxs}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenNftTx(ctx context.Context, r *http.Request) RPCResponse {
|
|
// Similar to getTokenTx but for ERC-721
|
|
return h.getTokenTx(ctx, r)
|
|
}
|
|
|
|
func (h *RPCHandler) getToken1155Tx(ctx context.Context, r *http.Request) RPCResponse {
|
|
// Similar to getTokenTx but for ERC-1155
|
|
return h.getTokenTx(ctx, r)
|
|
}
|
|
|
|
func (h *RPCHandler) getMinedBlocks(ctx context.Context, r *http.Request) RPCResponse {
|
|
// Return empty for now - would need miner tracking
|
|
return RPCResponse{Status: "1", Message: "OK", Result: []interface{}{}}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenBalance(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
contractAddress := r.URL.Query().Get("contractaddress")
|
|
|
|
if address == "" || contractAddress == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address or contractaddress"}
|
|
}
|
|
|
|
// Query token balance from database
|
|
// For now return 0 - would need token balance tracking
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0"}
|
|
}
|
|
|
|
func (h *RPCHandler) handleContract(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "getabi":
|
|
return h.getABI(ctx, r)
|
|
case "getsourcecode":
|
|
return h.getSourceCode(ctx, r)
|
|
case "getcontractcreation":
|
|
return h.getContractCreation(ctx, r)
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|
|
|
|
func (h *RPCHandler) getABI(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
if address == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address"}
|
|
}
|
|
|
|
contract, err := h.repo.GetSmartContract(ctx, address)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK-Contract source code not verified", Result: ""}
|
|
}
|
|
|
|
if !contract.IsVerified {
|
|
return RPCResponse{Status: "0", Message: "NOTOK-Contract source code not verified", Result: ""}
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: string(contract.ABI)}
|
|
}
|
|
|
|
func (h *RPCHandler) getSourceCode(ctx context.Context, r *http.Request) RPCResponse {
|
|
address := r.URL.Query().Get("address")
|
|
if address == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing address"}
|
|
}
|
|
|
|
contract, err := h.repo.GetSmartContract(ctx, address)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Contract not found"}
|
|
}
|
|
|
|
result := []map[string]interface{}{
|
|
{
|
|
"SourceCode": contract.SourceCode,
|
|
"ABI": string(contract.ABI),
|
|
"ContractName": contract.Name,
|
|
"CompilerVersion": contract.CompilerVersion,
|
|
"OptimizationUsed": "0",
|
|
"Runs": contract.OptimizationRuns,
|
|
"ConstructorArguments": contract.ConstructorArgs,
|
|
"EVMVersion": contract.EVMVersion,
|
|
"Library": "",
|
|
"LicenseType": contract.LicenseType,
|
|
"Proxy": "0",
|
|
"Implementation": "",
|
|
"SwarmSource": "",
|
|
},
|
|
}
|
|
|
|
if contract.OptimizationEnabled {
|
|
result[0]["OptimizationUsed"] = "1"
|
|
}
|
|
if contract.IsProxy {
|
|
result[0]["Proxy"] = "1"
|
|
if len(contract.Implementations) > 0 {
|
|
result[0]["Implementation"] = contract.Implementations[0].Address
|
|
}
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: result}
|
|
}
|
|
|
|
func (h *RPCHandler) getContractCreation(ctx context.Context, r *http.Request) RPCResponse {
|
|
addresses := r.URL.Query().Get("contractaddresses")
|
|
if addresses == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing contractaddresses"}
|
|
}
|
|
|
|
addrList := strings.Split(addresses, ",")
|
|
var results []map[string]interface{}
|
|
|
|
for _, addr := range addrList {
|
|
addr = strings.TrimSpace(addr)
|
|
address, err := h.repo.GetAddress(ctx, addr)
|
|
if err != nil || !address.IsContract {
|
|
continue
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"contractAddress": addr,
|
|
"contractCreator": "",
|
|
"txHash": "",
|
|
}
|
|
if address.Creator != nil {
|
|
result["contractCreator"] = address.Creator.Hash
|
|
}
|
|
if address.CreationTxHash != "" {
|
|
result["txHash"] = address.CreationTxHash
|
|
}
|
|
results = append(results, result)
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: results}
|
|
}
|
|
|
|
func (h *RPCHandler) handleTransaction(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "getstatus":
|
|
return h.getTxStatus(ctx, r)
|
|
case "gettxreceiptstatus":
|
|
return h.getTxReceiptStatus(ctx, r)
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|
|
|
|
func (h *RPCHandler) getTxStatus(ctx context.Context, r *http.Request) RPCResponse {
|
|
txhash := r.URL.Query().Get("txhash")
|
|
if txhash == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing txhash"}
|
|
}
|
|
|
|
tx, err := h.repo.GetTransactionByHash(ctx, txhash)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Transaction not found"}
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"isError": "0",
|
|
"errDescription": "",
|
|
}
|
|
|
|
if tx.Status == "error" {
|
|
result["isError"] = "1"
|
|
result["errDescription"] = tx.RevertReason
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: result}
|
|
}
|
|
|
|
func (h *RPCHandler) getTxReceiptStatus(ctx context.Context, r *http.Request) RPCResponse {
|
|
txhash := r.URL.Query().Get("txhash")
|
|
if txhash == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing txhash"}
|
|
}
|
|
|
|
tx, err := h.repo.GetTransactionByHash(ctx, txhash)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Transaction not found"}
|
|
}
|
|
|
|
status := "1"
|
|
if tx.Status == "error" {
|
|
status = "0"
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: map[string]string{"status": status}}
|
|
}
|
|
|
|
func (h *RPCHandler) handleBlock(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "getblockreward":
|
|
return h.getBlockReward(ctx, r)
|
|
case "getblockcountdown":
|
|
return h.getBlockCountdown(ctx, r)
|
|
case "getblocknobytime":
|
|
return h.getBlockByTime(ctx, r)
|
|
case "dailyavgblocksize":
|
|
return h.getDailyBlockSize(ctx, r)
|
|
case "dailyblkcount":
|
|
return h.getDailyBlockCount(ctx, r)
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|
|
|
|
func (h *RPCHandler) getBlockReward(ctx context.Context, r *http.Request) RPCResponse {
|
|
blockno := r.URL.Query().Get("blockno")
|
|
if blockno == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing blockno"}
|
|
}
|
|
|
|
height, err := strconv.ParseUint(blockno, 10, 64)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid blockno"}
|
|
}
|
|
|
|
block, err := h.repo.GetBlockByNumber(ctx, height)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Block not found"}
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"blockNumber": fmt.Sprintf("%d", block.Height),
|
|
"timeStamp": fmt.Sprintf("%d", block.Timestamp.Unix()),
|
|
"blockMiner": "",
|
|
"blockReward": "0",
|
|
"uncles": []interface{}{},
|
|
"uncleInclusionReward": "0",
|
|
}
|
|
|
|
if block.Miner != nil {
|
|
result["blockMiner"] = block.Miner.Hash
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: result}
|
|
}
|
|
|
|
func (h *RPCHandler) getBlockCountdown(ctx context.Context, r *http.Request) RPCResponse {
|
|
// Return countdown to target block
|
|
return RPCResponse{Status: "1", Message: "OK", Result: map[string]interface{}{
|
|
"CurrentBlock": "0",
|
|
"CountdownBlock": "0",
|
|
"RemainingBlock": "0",
|
|
"EstimateTimeInSec": "0",
|
|
}}
|
|
}
|
|
|
|
func (h *RPCHandler) getBlockByTime(ctx context.Context, r *http.Request) RPCResponse {
|
|
// Get block by timestamp
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0"}
|
|
}
|
|
|
|
func (h *RPCHandler) getDailyBlockSize(ctx context.Context, r *http.Request) RPCResponse {
|
|
return RPCResponse{Status: "1", Message: "OK", Result: []interface{}{}}
|
|
}
|
|
|
|
func (h *RPCHandler) getDailyBlockCount(ctx context.Context, r *http.Request) RPCResponse {
|
|
return RPCResponse{Status: "1", Message: "OK", Result: []interface{}{}}
|
|
}
|
|
|
|
func (h *RPCHandler) handleLogs(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
if action != "getLogs" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
|
|
address := r.URL.Query().Get("address")
|
|
fromBlock, _ := strconv.ParseUint(r.URL.Query().Get("fromBlock"), 10, 64)
|
|
toBlock, _ := strconv.ParseUint(r.URL.Query().Get("toBlock"), 10, 64)
|
|
|
|
topic0 := r.URL.Query().Get("topic0")
|
|
topic1 := r.URL.Query().Get("topic1")
|
|
topic2 := r.URL.Query().Get("topic2")
|
|
topic3 := r.URL.Query().Get("topic3")
|
|
|
|
topics := []string{topic0, topic1, topic2, topic3}
|
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
if offset <= 0 {
|
|
offset = 1000
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
result, err := h.repo.GetLogs(ctx, address, fromBlock, toBlock, topics, page-1, offset)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: err.Error()}
|
|
}
|
|
|
|
// Convert to Etherscan format
|
|
logs := result.Items.([]Log)
|
|
var etherscanLogs []map[string]interface{}
|
|
|
|
for _, log := range logs {
|
|
elog := map[string]interface{}{
|
|
"address": log.Address.Hash,
|
|
"topics": log.Topics,
|
|
"data": log.Data,
|
|
"blockNumber": fmt.Sprintf("0x%x", log.BlockNumber),
|
|
"transactionHash": log.TxHash,
|
|
"transactionIndex": fmt.Sprintf("0x%x", 0),
|
|
"blockHash": log.BlockHash,
|
|
"logIndex": fmt.Sprintf("0x%x", log.Index),
|
|
"removed": false,
|
|
}
|
|
etherscanLogs = append(etherscanLogs, elog)
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: etherscanLogs}
|
|
}
|
|
|
|
func (h *RPCHandler) handleToken(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "tokeninfo":
|
|
return h.getTokenInfo(ctx, r)
|
|
case "tokenholderlist":
|
|
return h.getTokenHolderList(ctx, r)
|
|
case "tokensupply":
|
|
return h.getTokenSupply(ctx, r)
|
|
case "tokensupplyhistory":
|
|
return h.getTokenSupplyHistory(ctx, r)
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenInfo(ctx context.Context, r *http.Request) RPCResponse {
|
|
contractAddress := r.URL.Query().Get("contractaddress")
|
|
if contractAddress == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing contractaddress"}
|
|
}
|
|
|
|
token, err := h.repo.GetToken(ctx, contractAddress)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Token not found"}
|
|
}
|
|
|
|
result := []map[string]interface{}{
|
|
{
|
|
"contractAddress": token.Address,
|
|
"tokenName": token.Name,
|
|
"symbol": token.Symbol,
|
|
"divisor": fmt.Sprintf("%d", *token.Decimals),
|
|
"tokenType": token.Type,
|
|
"totalSupply": token.TotalSupply,
|
|
"description": "",
|
|
"website": "",
|
|
"email": "",
|
|
"blog": "",
|
|
"reddit": "",
|
|
"slack": "",
|
|
"facebook": "",
|
|
"twitter": "",
|
|
"bitcointalk": "",
|
|
"github": "",
|
|
"telegram": "",
|
|
"wechat": "",
|
|
"linkedin": "",
|
|
"discord": "",
|
|
"whitepaper": "",
|
|
},
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: result}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenHolderList(ctx context.Context, r *http.Request) RPCResponse {
|
|
contractAddress := r.URL.Query().Get("contractaddress")
|
|
if contractAddress == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing contractaddress"}
|
|
}
|
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
if offset <= 0 {
|
|
offset = 10
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
|
|
// Would need to query token_balances table
|
|
return RPCResponse{Status: "1", Message: "OK", Result: []interface{}{}}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenSupply(ctx context.Context, r *http.Request) RPCResponse {
|
|
contractAddress := r.URL.Query().Get("contractaddress")
|
|
if contractAddress == "" {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Missing contractaddress"}
|
|
}
|
|
|
|
token, err := h.repo.GetToken(ctx, contractAddress)
|
|
if err != nil {
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Token not found"}
|
|
}
|
|
|
|
return RPCResponse{Status: "1", Message: "OK", Result: token.TotalSupply}
|
|
}
|
|
|
|
func (h *RPCHandler) getTokenSupplyHistory(ctx context.Context, r *http.Request) RPCResponse {
|
|
return RPCResponse{Status: "1", Message: "OK", Result: []interface{}{}}
|
|
}
|
|
|
|
func (h *RPCHandler) handleStats(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "ethsupply":
|
|
return h.getEthSupply(ctx, r)
|
|
case "ethprice":
|
|
return h.getEthPrice(ctx, r)
|
|
case "chainsize":
|
|
return h.getChainSize(ctx, r)
|
|
case "nodecount":
|
|
return h.getNodeCount(ctx, r)
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|
|
|
|
func (h *RPCHandler) getEthSupply(ctx context.Context, r *http.Request) RPCResponse {
|
|
// Return total supply
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0"}
|
|
}
|
|
|
|
func (h *RPCHandler) getEthPrice(ctx context.Context, r *http.Request) RPCResponse {
|
|
return RPCResponse{Status: "1", Message: "OK", Result: map[string]interface{}{
|
|
"ethbtc": "0",
|
|
"ethbtc_timestamp": "0",
|
|
"ethusd": "0",
|
|
"ethusd_timestamp": "0",
|
|
}}
|
|
}
|
|
|
|
func (h *RPCHandler) getChainSize(ctx context.Context, r *http.Request) RPCResponse {
|
|
return RPCResponse{Status: "1", Message: "OK", Result: []interface{}{}}
|
|
}
|
|
|
|
func (h *RPCHandler) getNodeCount(ctx context.Context, r *http.Request) RPCResponse {
|
|
return RPCResponse{Status: "1", Message: "OK", Result: map[string]interface{}{
|
|
"UTCDate": "",
|
|
"TotalNodeCount": 0,
|
|
}}
|
|
}
|
|
|
|
func (h *RPCHandler) handleProxy(ctx context.Context, action string, r *http.Request) RPCResponse {
|
|
switch action {
|
|
case "eth_blockNumber":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0x0"}
|
|
case "eth_getBlockByNumber":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: nil}
|
|
case "eth_getTransactionByHash":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: nil}
|
|
case "eth_getTransactionReceipt":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: nil}
|
|
case "eth_call":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0x"}
|
|
case "eth_getCode":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0x"}
|
|
case "eth_getStorageAt":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0x"}
|
|
case "eth_gasPrice":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0x0"}
|
|
case "eth_estimateGas":
|
|
return RPCResponse{Status: "1", Message: "OK", Result: "0x0"}
|
|
default:
|
|
return RPCResponse{Status: "0", Message: "NOTOK", Result: "Invalid action"}
|
|
}
|
|
}
|