mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 08:34:28 +08:00
feat: add get utxo by tx hash and output idx for Runes (#42)
* feat: add handler * feat: add get transaction * feat: add get utxos output * refactor: function parameter * feat: add check utxo not found * feat: add sats to get utxo output api * feat: add utxo sats entity * feat: add get utxos output batch * feat: handle error * fix: context * fix: sqlc queries * fix: remove unused code * fix: comment * fix: check utxo not found error * refactor: add some space * fix: comment * fix: use public field
This commit is contained in:
@@ -285,3 +285,12 @@ func (d *BitcoinNodeDatasource) GetBlockHeader(ctx context.Context, height int64
|
||||
|
||||
return types.ParseMsgBlockHeader(*block, height), nil
|
||||
}
|
||||
|
||||
func (d *BitcoinNodeDatasource) GetRawTransactionByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, error) {
|
||||
transaction, err := d.btcclient.GetRawTransaction(&txHash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get raw transaction")
|
||||
}
|
||||
|
||||
return transaction.MsgTx(), nil
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ type utxoExtend struct {
|
||||
type utxoItem struct {
|
||||
TxHash chainhash.Hash `json:"txHash"`
|
||||
OutputIndex uint32 `json:"outputIndex"`
|
||||
Sats int64 `json:"sats"`
|
||||
Extend utxoExtend `json:"extend"`
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ func (h *HttpHandler) GetUTXOs(ctx *fiber.Ctx) (err error) {
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
var utxos []*entity.RunesUTXO
|
||||
var utxos []*entity.RunesUTXOWithSats
|
||||
if runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id); ok {
|
||||
utxos, err = h.usecase.GetRunesUTXOsByRuneIdAndPkScript(ctx.UserContext(), runeId, pkScript, blockHeight, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
@@ -150,6 +151,7 @@ func (h *HttpHandler) GetUTXOs(ctx *fiber.Ctx) (err error) {
|
||||
utxoRespList = append(utxoRespList, utxoItem{
|
||||
TxHash: utxo.OutPoint.Hash,
|
||||
OutputIndex: utxo.OutPoint.Index,
|
||||
Sats: utxo.Sats,
|
||||
Extend: utxoExtend{
|
||||
Runes: runeBalances,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"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/gaze-network/indexer-network/modules/runes/usecase"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type getUTXOsOutputByLocationRequest struct {
|
||||
TxHash string `params:"txHash"`
|
||||
OutputIndex int32 `query:"outputIndex"`
|
||||
}
|
||||
|
||||
func (r getUTXOsOutputByLocationRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.TxHash == "" {
|
||||
errList = append(errList, errors.New("'txHash' is required"))
|
||||
}
|
||||
if r.OutputIndex < 0 {
|
||||
errList = append(errList, errors.New("'outputIndex' must be non-negative"))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type getUTXOsOutputByTxIdResponse = HttpResponse[utxoItem]
|
||||
|
||||
func (h *HttpHandler) GetUTXOsOutputByLocation(ctx *fiber.Ctx) (err error) {
|
||||
var req getUTXOsOutputByLocationRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
txHash, err := chainhash.NewHashFromStr(req.TxHash)
|
||||
if err != nil {
|
||||
return errs.WithPublicMessage(err, "unable to resolve txHash")
|
||||
}
|
||||
|
||||
utxo, err := h.usecase.GetUTXOsOutputByLocation(ctx.UserContext(), *txHash, uint32(req.OutputIndex))
|
||||
if err != nil {
|
||||
if errors.Is(err, usecase.ErrUTXONotFound) {
|
||||
return errs.NewPublicError("utxo not found")
|
||||
}
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
runeIds := make(map[runes.RuneId]struct{}, 0)
|
||||
for _, balance := range utxo.RuneBalances {
|
||||
runeIds[balance.RuneId] = struct{}{}
|
||||
}
|
||||
runeIdsList := lo.Keys(runeIds)
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), runeIdsList)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("rune entries not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||
}
|
||||
|
||||
runeBalances := make([]runeBalance, 0, len(utxo.RuneBalances))
|
||||
for _, balance := range utxo.RuneBalances {
|
||||
runeEntry := runeEntries[balance.RuneId]
|
||||
runeBalances = append(runeBalances, runeBalance{
|
||||
RuneId: balance.RuneId,
|
||||
Rune: runeEntry.SpacedRune,
|
||||
Symbol: string(runeEntry.Symbol),
|
||||
Amount: balance.Amount,
|
||||
Divisibility: runeEntry.Divisibility,
|
||||
})
|
||||
}
|
||||
|
||||
resp := getUTXOsOutputByTxIdResponse{
|
||||
Result: &utxoItem{
|
||||
TxHash: utxo.OutPoint.Hash,
|
||||
OutputIndex: utxo.OutPoint.Index,
|
||||
Sats: utxo.Sats,
|
||||
Extend: utxoExtend{
|
||||
Runes: runeBalances,
|
||||
},
|
||||
},
|
||||
}
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/gaze-network/indexer-network/modules/runes/usecase"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type getUTXOsOutputByLocationQuery struct {
|
||||
TxHash string `json:"txHash"`
|
||||
OutputIndex int32 `json:"outputIndex"`
|
||||
}
|
||||
|
||||
type getUTXOsOutputByLocationBatchRequest struct {
|
||||
Queries []getUTXOsOutputByLocationQuery `json:"queries"`
|
||||
}
|
||||
|
||||
const getUTXOsOutputByLocationBatchMaxQueries = 100
|
||||
|
||||
func (r getUTXOsOutputByLocationBatchRequest) Validate() error {
|
||||
var errList []error
|
||||
if len(r.Queries) == 0 {
|
||||
errList = append(errList, errors.New("at least one query is required"))
|
||||
}
|
||||
if len(r.Queries) > getUTXOsOutputByLocationBatchMaxQueries {
|
||||
errList = append(errList, errors.Errorf("cannot exceed %d queries", getUTXOsOutputByLocationBatchMaxQueries))
|
||||
}
|
||||
for i, query := range r.Queries {
|
||||
if query.TxHash == "" {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'txHash' is required", i))
|
||||
}
|
||||
if query.OutputIndex < 0 {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'outputIndex' must be non-negative", i))
|
||||
}
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type getUTXOsOutputByLocationBatchResult struct {
|
||||
List []*utxoItem `json:"list"`
|
||||
}
|
||||
|
||||
type getUTXOsOutputByLocationBatchResponse = HttpResponse[getUTXOsOutputByLocationBatchResult]
|
||||
|
||||
func (h *HttpHandler) GetUTXOsOutputByLocationBatch(ctx *fiber.Ctx) (err error) {
|
||||
var req getUTXOsOutputByLocationBatchRequest
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
processQuery := func(ctx context.Context, query getUTXOsOutputByLocationQuery, queryIndex int) (*utxoItem, error) {
|
||||
txHash, err := chainhash.NewHashFromStr(query.TxHash)
|
||||
if err != nil {
|
||||
return nil, errs.WithPublicMessage(err, fmt.Sprintf("unable to parse txHash from \"queries[%d].txHash\"", queryIndex))
|
||||
}
|
||||
|
||||
utxo, err := h.usecase.GetUTXOsOutputByLocation(ctx, *txHash, uint32(query.OutputIndex))
|
||||
if err != nil {
|
||||
if errors.Is(err, usecase.ErrUTXONotFound) {
|
||||
return nil, errs.NewPublicError(fmt.Sprintf("utxo not found for queries[%d]", queryIndex))
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
runeIds := make(map[runes.RuneId]struct{}, 0)
|
||||
for _, balance := range utxo.RuneBalances {
|
||||
runeIds[balance.RuneId] = struct{}{}
|
||||
}
|
||||
runeIdsList := lo.Keys(runeIds)
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx, runeIdsList)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return nil, errs.NewPublicError(fmt.Sprintf("rune entries not found for queries[%d]", queryIndex))
|
||||
}
|
||||
return nil, errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||
}
|
||||
|
||||
runeBalances := make([]runeBalance, 0, len(utxo.RuneBalances))
|
||||
for _, balance := range utxo.RuneBalances {
|
||||
runeEntry := runeEntries[balance.RuneId]
|
||||
runeBalances = append(runeBalances, runeBalance{
|
||||
RuneId: balance.RuneId,
|
||||
Rune: runeEntry.SpacedRune,
|
||||
Symbol: string(runeEntry.Symbol),
|
||||
Amount: balance.Amount,
|
||||
Divisibility: runeEntry.Divisibility,
|
||||
})
|
||||
}
|
||||
|
||||
return &utxoItem{
|
||||
TxHash: utxo.OutPoint.Hash,
|
||||
OutputIndex: utxo.OutPoint.Index,
|
||||
Sats: utxo.Sats,
|
||||
Extend: utxoExtend{
|
||||
Runes: runeBalances,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
results := make([]*utxoItem, len(req.Queries))
|
||||
eg, ectx := errgroup.WithContext(ctx.UserContext())
|
||||
for i, query := range req.Queries {
|
||||
i := i
|
||||
query := query
|
||||
eg.Go(func() error {
|
||||
result, err := processQuery(ectx, query, i)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error during processQuery for query %d", i)
|
||||
}
|
||||
results[i] = result
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resp := getUTXOsOutputByLocationBatchResponse{
|
||||
Result: &getUTXOsOutputByLocationBatchResult{
|
||||
List: results,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -13,6 +13,8 @@ func (h *HttpHandler) Mount(router fiber.Router) error {
|
||||
r.Get("/holders/:id", h.GetHolders)
|
||||
r.Get("/info/:id", h.GetTokenInfo)
|
||||
r.Get("/utxos/wallet/:wallet", h.GetUTXOs)
|
||||
r.Post("/utxos/output/batch", h.GetUTXOsOutputByLocationBatch)
|
||||
r.Get("/utxos/output/:txHash", h.GetUTXOsOutputByLocation)
|
||||
r.Get("/block", h.GetCurrentBlock)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -79,6 +79,11 @@ SELECT * FROM runes_transactions
|
||||
)
|
||||
ORDER BY runes_transactions.block_height DESC, runes_transactions.index DESC LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: GetRuneTransaction :one
|
||||
SELECT * FROM runes_transactions
|
||||
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||
WHERE hash = $1 LIMIT 1;
|
||||
|
||||
-- name: CountRuneEntries :one
|
||||
SELECT COUNT(*) FROM runes_entries;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package datagateway
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
@@ -28,6 +29,7 @@ type RunesReaderDataGateway interface {
|
||||
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error)
|
||||
// GetRuneTransactions returns the runes transactions, filterable by pkScript, runeId and height. If pkScript, runeId or height is zero value, that filter is ignored.
|
||||
GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64, limit int32, offset int32) ([]*entity.RuneTransaction, error)
|
||||
GetRuneTransaction(ctx context.Context, txHash chainhash.Hash) (*entity.RuneTransaction, error)
|
||||
|
||||
GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error)
|
||||
GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, runeId runes.RuneId, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error)
|
||||
|
||||
@@ -16,3 +16,8 @@ type RunesUTXO struct {
|
||||
OutPoint wire.OutPoint
|
||||
RuneBalances []RunesUTXOBalance
|
||||
}
|
||||
|
||||
type RunesUTXOWithSats struct {
|
||||
RunesUTXO
|
||||
Sats int64
|
||||
}
|
||||
|
||||
@@ -645,6 +645,83 @@ func (q *Queries) GetRuneIdFromRune(ctx context.Context, rune string) (string, e
|
||||
return rune_id, err
|
||||
}
|
||||
|
||||
const getRuneTransaction = `-- name: GetRuneTransaction :one
|
||||
SELECT hash, runes_transactions.block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched, tx_hash, runes_runestones.block_height, etching, etching_divisibility, etching_premine, etching_rune, etching_spacers, etching_symbol, etching_terms, etching_terms_amount, etching_terms_cap, etching_terms_height_start, etching_terms_height_end, etching_terms_offset_start, etching_terms_offset_end, etching_turbo, edicts, mint, pointer, cenotaph, flaws FROM runes_transactions
|
||||
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||
WHERE hash = $1 LIMIT 1
|
||||
`
|
||||
|
||||
type GetRuneTransactionRow struct {
|
||||
Hash string
|
||||
BlockHeight int32
|
||||
Index int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Inputs []byte
|
||||
Outputs []byte
|
||||
Mints []byte
|
||||
Burns []byte
|
||||
RuneEtched bool
|
||||
TxHash pgtype.Text
|
||||
BlockHeight_2 pgtype.Int4
|
||||
Etching pgtype.Bool
|
||||
EtchingDivisibility pgtype.Int2
|
||||
EtchingPremine pgtype.Numeric
|
||||
EtchingRune pgtype.Text
|
||||
EtchingSpacers pgtype.Int4
|
||||
EtchingSymbol pgtype.Int4
|
||||
EtchingTerms pgtype.Bool
|
||||
EtchingTermsAmount pgtype.Numeric
|
||||
EtchingTermsCap pgtype.Numeric
|
||||
EtchingTermsHeightStart pgtype.Int4
|
||||
EtchingTermsHeightEnd pgtype.Int4
|
||||
EtchingTermsOffsetStart pgtype.Int4
|
||||
EtchingTermsOffsetEnd pgtype.Int4
|
||||
EtchingTurbo pgtype.Bool
|
||||
Edicts []byte
|
||||
Mint pgtype.Text
|
||||
Pointer pgtype.Int4
|
||||
Cenotaph pgtype.Bool
|
||||
Flaws pgtype.Int4
|
||||
}
|
||||
|
||||
func (q *Queries) GetRuneTransaction(ctx context.Context, hash string) (GetRuneTransactionRow, error) {
|
||||
row := q.db.QueryRow(ctx, getRuneTransaction, hash)
|
||||
var i GetRuneTransactionRow
|
||||
err := row.Scan(
|
||||
&i.Hash,
|
||||
&i.BlockHeight,
|
||||
&i.Index,
|
||||
&i.Timestamp,
|
||||
&i.Inputs,
|
||||
&i.Outputs,
|
||||
&i.Mints,
|
||||
&i.Burns,
|
||||
&i.RuneEtched,
|
||||
&i.TxHash,
|
||||
&i.BlockHeight_2,
|
||||
&i.Etching,
|
||||
&i.EtchingDivisibility,
|
||||
&i.EtchingPremine,
|
||||
&i.EtchingRune,
|
||||
&i.EtchingSpacers,
|
||||
&i.EtchingSymbol,
|
||||
&i.EtchingTerms,
|
||||
&i.EtchingTermsAmount,
|
||||
&i.EtchingTermsCap,
|
||||
&i.EtchingTermsHeightStart,
|
||||
&i.EtchingTermsHeightEnd,
|
||||
&i.EtchingTermsOffsetStart,
|
||||
&i.EtchingTermsOffsetEnd,
|
||||
&i.EtchingTurbo,
|
||||
&i.Edicts,
|
||||
&i.Mint,
|
||||
&i.Pointer,
|
||||
&i.Cenotaph,
|
||||
&i.Flaws,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getRuneTransactions = `-- name: GetRuneTransactions :many
|
||||
SELECT hash, runes_transactions.block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched, tx_hash, runes_runestones.block_height, etching, etching_divisibility, etching_premine, etching_rune, etching_spacers, etching_symbol, etching_terms, etching_terms_amount, etching_terms_cap, etching_terms_height_start, etching_terms_height_end, etching_terms_offset_start, etching_terms_offset_end, etching_turbo, edicts, mint, pointer, cenotaph, flaws FROM runes_transactions
|
||||
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||
|
||||
@@ -120,6 +120,33 @@ func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, r
|
||||
return runeTxs, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
runeTxModel, runestoneModel, err := extractModelRuneTxAndRunestone(gen.GetRuneTransactionsRow(row))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to extract rune transaction and runestone from row")
|
||||
}
|
||||
|
||||
runeTx, err := mapRuneTransactionModelToType(runeTxModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse rune transaction model")
|
||||
}
|
||||
|
||||
if runestoneModel != nil {
|
||||
runestone, err := mapRunestoneModelToType(*runestoneModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse runestone model")
|
||||
}
|
||||
runeTx.Runestone = &runestone
|
||||
}
|
||||
|
||||
return &runeTx, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error) {
|
||||
balances, err := r.queries.GetOutPointBalancesAtOutPoint(ctx, gen.GetOutPointBalancesAtOutPointParams{
|
||||
TxHash: outPoint.Hash.String(),
|
||||
|
||||
5
modules/runes/usecase/errs.go
Normal file
5
modules/runes/usecase/errs.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package usecase
|
||||
|
||||
import "github.com/cockroachdb/errors"
|
||||
|
||||
var ErrUTXONotFound = errors.New("utxo not found")
|
||||
@@ -2,24 +2,118 @@ package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetRunesUTXOsByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error) {
|
||||
func (u *Usecase) GetRunesUTXOsByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXOWithSats, error) {
|
||||
balances, err := u.runesDg.GetRunesUTXOsByPkScript(ctx, pkScript, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
return balances, nil
|
||||
|
||||
result := make([]*entity.RunesUTXOWithSats, 0, len(balances))
|
||||
for _, balance := range balances {
|
||||
tx, err := u.bitcoinClient.GetRawTransactionByTxHash(ctx, balance.OutPoint.Hash)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "No such mempool or blockchain transaction.") {
|
||||
return nil, errors.WithStack(ErrUTXONotFound)
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result = append(result, &entity.RunesUTXOWithSats{
|
||||
RunesUTXO: entity.RunesUTXO{
|
||||
PkScript: balance.PkScript,
|
||||
OutPoint: balance.OutPoint,
|
||||
RuneBalances: balance.RuneBalances,
|
||||
},
|
||||
Sats: tx.TxOut[balance.OutPoint.Index].Value,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, runeId runes.RuneId, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error) {
|
||||
func (u *Usecase) GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, runeId runes.RuneId, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXOWithSats, error) {
|
||||
balances, err := u.runesDg.GetRunesUTXOsByRuneIdAndPkScript(ctx, runeId, pkScript, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
return balances, nil
|
||||
|
||||
result := make([]*entity.RunesUTXOWithSats, 0, len(balances))
|
||||
for _, balance := range balances {
|
||||
tx, err := u.bitcoinClient.GetRawTransactionByTxHash(ctx, balance.OutPoint.Hash)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "No such mempool or blockchain transaction.") {
|
||||
return nil, errors.WithStack(ErrUTXONotFound)
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result = append(result, &entity.RunesUTXOWithSats{
|
||||
RunesUTXO: entity.RunesUTXO{
|
||||
PkScript: balance.PkScript,
|
||||
OutPoint: balance.OutPoint,
|
||||
RuneBalances: balance.RuneBalances,
|
||||
},
|
||||
Sats: tx.TxOut[balance.OutPoint.Index].Value,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetUTXOsOutputByLocation(ctx context.Context, txHash chainhash.Hash, outputIdx uint32) (*entity.RunesUTXOWithSats, error) {
|
||||
tx, err := u.bitcoinClient.GetRawTransactionByTxHash(ctx, txHash)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "No such mempool or blockchain transaction.") {
|
||||
return nil, errors.WithStack(ErrUTXONotFound)
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// If the output index is out of range, return an error
|
||||
if len(tx.TxOut) <= int(outputIdx) {
|
||||
return nil, errors.WithStack(ErrUTXONotFound)
|
||||
}
|
||||
|
||||
rune := &entity.RunesUTXOWithSats{
|
||||
RunesUTXO: entity.RunesUTXO{
|
||||
PkScript: tx.TxOut[0].PkScript,
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: txHash,
|
||||
Index: outputIdx,
|
||||
},
|
||||
},
|
||||
Sats: tx.TxOut[outputIdx].Value,
|
||||
}
|
||||
|
||||
transaction, err := u.runesDg.GetRuneTransaction(ctx, txHash)
|
||||
// If Bitcoin transaction is not found in the database, return the PkScript and OutPoint
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return rune, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
runeBalance := make([]entity.RunesUTXOBalance, 0, len(transaction.Outputs))
|
||||
for _, output := range transaction.Outputs {
|
||||
if output.Index == outputIdx {
|
||||
runeBalance = append(runeBalance, entity.RunesUTXOBalance{
|
||||
RuneId: output.RuneId,
|
||||
Amount: output.Amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rune.RuneBalances = runeBalance
|
||||
return rune, nil
|
||||
}
|
||||
|
||||
@@ -9,4 +9,6 @@ import (
|
||||
|
||||
type Contract interface {
|
||||
GetRawTransactionAndHeightByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, int64, error)
|
||||
|
||||
GetRawTransactionByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user