mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-03-27 22:43:42 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d51e52b83 | ||
|
|
618220d0cb | ||
|
|
6004744721 | ||
|
|
90ed7bc350 | ||
|
|
7a0fe84e40 | ||
|
|
f1d4651042 |
171
modules/runes/api/httphandler/get_transaction_by_hash.go
Normal file
171
modules/runes/api/httphandler/get_transaction_by_hash.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type getTransactionByHashRequest struct {
|
||||
Hash string `params:"hash"`
|
||||
}
|
||||
|
||||
func (r getTransactionByHashRequest) Validate() error {
|
||||
var errList []error
|
||||
if len(r.Hash) == 0 {
|
||||
errList = append(errList, errs.NewPublicError("hash is required"))
|
||||
}
|
||||
if len(r.Hash) > chainhash.MaxHashStringSize {
|
||||
errList = append(errList, errs.NewPublicError(fmt.Sprintf("hash length must be less than or equal to %d bytes", chainhash.MaxHashStringSize)))
|
||||
}
|
||||
if len(errList) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type getTransactionByHashResponse = HttpResponse[transaction]
|
||||
|
||||
func (h *HttpHandler) GetTransactionByHash(ctx *fiber.Ctx) (err error) {
|
||||
var req getTransactionByHashRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
hash, err := chainhash.NewHashFromStr(req.Hash)
|
||||
if err != nil {
|
||||
return errs.NewPublicError("invalid transaction hash")
|
||||
}
|
||||
|
||||
tx, err := h.usecase.GetRuneTransaction(ctx.UserContext(), *hash)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "transaction not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetRuneTransaction")
|
||||
}
|
||||
|
||||
allRuneIds := make(map[runes.RuneId]struct{})
|
||||
for id := range tx.Mints {
|
||||
allRuneIds[id] = struct{}{}
|
||||
}
|
||||
for id := range tx.Burns {
|
||||
allRuneIds[id] = struct{}{}
|
||||
}
|
||||
for _, input := range tx.Inputs {
|
||||
allRuneIds[input.RuneId] = struct{}{}
|
||||
}
|
||||
for _, output := range tx.Outputs {
|
||||
allRuneIds[output.RuneId] = struct{}{}
|
||||
}
|
||||
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), lo.Keys(allRuneIds))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||
}
|
||||
|
||||
respTx := &transaction{
|
||||
TxHash: tx.Hash,
|
||||
BlockHeight: tx.BlockHeight,
|
||||
Index: tx.Index,
|
||||
Timestamp: tx.Timestamp.Unix(),
|
||||
Inputs: make([]txInputOutput, 0, len(tx.Inputs)),
|
||||
Outputs: make([]txInputOutput, 0, len(tx.Outputs)),
|
||||
Mints: make(map[string]amountWithDecimal, len(tx.Mints)),
|
||||
Burns: make(map[string]amountWithDecimal, len(tx.Burns)),
|
||||
Extend: runeTransactionExtend{
|
||||
RuneEtched: tx.RuneEtched,
|
||||
Runestone: nil,
|
||||
},
|
||||
}
|
||||
for _, input := range tx.Inputs {
|
||||
address := addressFromPkScript(input.PkScript, h.network)
|
||||
respTx.Inputs = append(respTx.Inputs, txInputOutput{
|
||||
PkScript: hex.EncodeToString(input.PkScript),
|
||||
Address: address,
|
||||
Id: input.RuneId,
|
||||
Amount: input.Amount,
|
||||
Decimals: runeEntries[input.RuneId].Divisibility,
|
||||
Index: input.Index,
|
||||
})
|
||||
}
|
||||
for _, output := range tx.Outputs {
|
||||
address := addressFromPkScript(output.PkScript, h.network)
|
||||
respTx.Outputs = append(respTx.Outputs, txInputOutput{
|
||||
PkScript: hex.EncodeToString(output.PkScript),
|
||||
Address: address,
|
||||
Id: output.RuneId,
|
||||
Amount: output.Amount,
|
||||
Decimals: runeEntries[output.RuneId].Divisibility,
|
||||
Index: output.Index,
|
||||
})
|
||||
}
|
||||
for id, amount := range tx.Mints {
|
||||
respTx.Mints[id.String()] = amountWithDecimal{
|
||||
Amount: amount,
|
||||
Decimals: runeEntries[id].Divisibility,
|
||||
}
|
||||
}
|
||||
for id, amount := range tx.Burns {
|
||||
respTx.Burns[id.String()] = amountWithDecimal{
|
||||
Amount: amount,
|
||||
Decimals: runeEntries[id].Divisibility,
|
||||
}
|
||||
}
|
||||
if tx.Runestone != nil {
|
||||
var e *etching
|
||||
if tx.Runestone.Etching != nil {
|
||||
var symbol *string
|
||||
if tx.Runestone.Etching.Symbol != nil {
|
||||
symbol = lo.ToPtr(string(*tx.Runestone.Etching.Symbol))
|
||||
}
|
||||
var t *terms
|
||||
if tx.Runestone.Etching.Terms != nil {
|
||||
t = &terms{
|
||||
Amount: tx.Runestone.Etching.Terms.Amount,
|
||||
Cap: tx.Runestone.Etching.Terms.Cap,
|
||||
HeightStart: tx.Runestone.Etching.Terms.HeightStart,
|
||||
HeightEnd: tx.Runestone.Etching.Terms.HeightEnd,
|
||||
OffsetStart: tx.Runestone.Etching.Terms.OffsetStart,
|
||||
OffsetEnd: tx.Runestone.Etching.Terms.OffsetEnd,
|
||||
}
|
||||
}
|
||||
e = &etching{
|
||||
Divisibility: tx.Runestone.Etching.Divisibility,
|
||||
Premine: tx.Runestone.Etching.Premine,
|
||||
Rune: tx.Runestone.Etching.Rune,
|
||||
Spacers: tx.Runestone.Etching.Spacers,
|
||||
Symbol: symbol,
|
||||
Terms: t,
|
||||
Turbo: tx.Runestone.Etching.Turbo,
|
||||
}
|
||||
}
|
||||
respTx.Extend.Runestone = &runestone{
|
||||
Cenotaph: tx.Runestone.Cenotaph,
|
||||
Flaws: lo.Ternary(tx.Runestone.Cenotaph, tx.Runestone.Flaws.CollectAsString(), nil),
|
||||
Etching: e,
|
||||
Edicts: lo.Map(tx.Runestone.Edicts, func(ed runes.Edict, _ int) edict {
|
||||
return edict{
|
||||
Id: ed.Id,
|
||||
Amount: ed.Amount,
|
||||
Output: ed.Output,
|
||||
}
|
||||
}),
|
||||
Mint: tx.Runestone.Mint,
|
||||
Pointer: tx.Runestone.Pointer,
|
||||
}
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(getTransactionByHashResponse{
|
||||
Result: respTx,
|
||||
}))
|
||||
}
|
||||
@@ -191,23 +191,22 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
return errors.Wrap(err, "error during GetRuneTransactions")
|
||||
}
|
||||
|
||||
var allRuneIds []runes.RuneId
|
||||
allRuneIds := make(map[runes.RuneId]struct{})
|
||||
for _, tx := range txs {
|
||||
for id := range tx.Mints {
|
||||
allRuneIds = append(allRuneIds, id)
|
||||
allRuneIds[id] = struct{}{}
|
||||
}
|
||||
for id := range tx.Burns {
|
||||
allRuneIds = append(allRuneIds, id)
|
||||
allRuneIds[id] = struct{}{}
|
||||
}
|
||||
for _, input := range tx.Inputs {
|
||||
allRuneIds = append(allRuneIds, input.RuneId)
|
||||
allRuneIds[input.RuneId] = struct{}{}
|
||||
}
|
||||
for _, output := range tx.Outputs {
|
||||
allRuneIds = append(allRuneIds, output.RuneId)
|
||||
allRuneIds[output.RuneId] = struct{}{}
|
||||
}
|
||||
}
|
||||
allRuneIds = lo.Uniq(allRuneIds)
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), allRuneIds)
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), lo.Keys(allRuneIds))
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("rune entries not found")
|
||||
|
||||
@@ -10,6 +10,7 @@ func (h *HttpHandler) Mount(router fiber.Router) error {
|
||||
r.Post("/balances/wallet/batch", h.GetBalancesBatch)
|
||||
r.Get("/balances/wallet/:wallet", h.GetBalances)
|
||||
r.Get("/transactions", h.GetTransactions)
|
||||
r.Get("/transactions/hash/:hash", h.GetTransactionByHash)
|
||||
r.Get("/holders/:id", h.GetHolders)
|
||||
r.Get("/info/:id", h.GetTokenInfo)
|
||||
r.Get("/utxos/wallet/:wallet", h.GetUTXOs)
|
||||
|
||||
@@ -86,6 +86,8 @@ SELECT * FROM runes_entries
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE (
|
||||
runes_entries.terms = TRUE AND
|
||||
COALESCE(runes_entries.terms_amount, 0) != 0 AND
|
||||
COALESCE(runes_entries.terms_cap, 0) != 0 AND
|
||||
states.mints < runes_entries.terms_cap AND
|
||||
(
|
||||
runes_entries.terms_height_start IS NULL OR runes_entries.terms_height_start <= @height::integer
|
||||
@@ -99,9 +101,10 @@ SELECT * FROM runes_entries
|
||||
|
||||
) AND (
|
||||
@search::text = '' OR
|
||||
runes_entries.rune ILIKE @search::text || '%'
|
||||
runes_entries.rune ILIKE '%' || @search::text || '%'
|
||||
)
|
||||
ORDER BY (states.mints / runes_entries.terms_cap::float) DESC
|
||||
ORDER BY (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(states.mints, 0)) /
|
||||
(COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(runes_entries.terms_cap, 0))::float DESC
|
||||
LIMIT @_limit OFFSET @_offset;
|
||||
|
||||
-- name: GetRuneIdFromRune :one
|
||||
|
||||
@@ -437,6 +437,8 @@ SELECT runes_entries.rune_id, number, rune, spacers, premine, symbol, divisibili
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE (
|
||||
runes_entries.terms = TRUE AND
|
||||
COALESCE(runes_entries.terms_amount, 0) != 0 AND
|
||||
COALESCE(runes_entries.terms_cap, 0) != 0 AND
|
||||
states.mints < runes_entries.terms_cap AND
|
||||
(
|
||||
runes_entries.terms_height_start IS NULL OR runes_entries.terms_height_start <= $1::integer
|
||||
@@ -450,9 +452,10 @@ SELECT runes_entries.rune_id, number, rune, spacers, premine, symbol, divisibili
|
||||
|
||||
) AND (
|
||||
$2::text = '' OR
|
||||
runes_entries.rune ILIKE $2::text || '%'
|
||||
runes_entries.rune ILIKE '%' || $2::text || '%'
|
||||
)
|
||||
ORDER BY (states.mints / runes_entries.terms_cap::float) DESC
|
||||
ORDER BY (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(states.mints, 0)) /
|
||||
(COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(runes_entries.terms_cap, 0))::float DESC
|
||||
LIMIT $4 OFFSET $3
|
||||
`
|
||||
|
||||
|
||||
@@ -122,8 +122,11 @@ func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, r
|
||||
|
||||
func (r *Repository) GetRuneTransaction(ctx context.Context, txHash chainhash.Hash) (*entity.RuneTransaction, error) {
|
||||
row, err := r.queries.GetRuneTransaction(ctx, txHash.String())
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errors.WithStack(errs.NotFound)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errors.WithStack(errs.NotFound)
|
||||
}
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
runeTxModel, runestoneModel, err := extractModelRuneTxAndRunestone(gen.GetRuneTransactionsRow(row))
|
||||
|
||||
@@ -3,6 +3,7 @@ package usecase
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
@@ -16,3 +17,11 @@ func (u *Usecase) GetRuneTransactions(ctx context.Context, pkScript []byte, rune
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetRuneTransaction(ctx context.Context, hash chainhash.Hash) (*entity.RuneTransaction, error) {
|
||||
tx, err := u.runesDg.GetRuneTransaction(ctx, hash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetRuneTransaction")
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
var DefaultClient = fasthttp.Client{
|
||||
MaxConnsPerHost: 10240, // default is 512
|
||||
MaxConnWaitTimeout: 5 * time.Second, // default is no wating
|
||||
ReadBufferSize: 4 * 1024,
|
||||
WriteBufferSize: 4 * 1024,
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// Enable debug mode
|
||||
Debug bool
|
||||
@@ -143,7 +150,7 @@ func (h *Client) request(ctx context.Context, reqOptions RequestOptions) (*HttpR
|
||||
fasthttp.ReleaseRequest(req)
|
||||
}()
|
||||
|
||||
if err := fasthttp.Do(req, resp); err != nil {
|
||||
if err := DefaultClient.Do(req, resp); err != nil {
|
||||
return nil, errors.Wrapf(err, "url: %s", url)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user