mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 08:34:28 +08:00
feat: add Runes API pagination (#36)
* feat: implement pagination on get balance, get holders * feat: paginate get transactions * fix: remove debug * feat: implement pagination in get utxos * feat: sort response in get holders * feat: cap batch query * feat: add default limits to all endpoints * chore: rename endpoint funcs * fix: parse rune name spacers * chore: use compare.Cmp * feat: handle not found errors on all usecase
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -39,7 +39,7 @@
|
||||
"ui.completion.usePlaceholders": false,
|
||||
"ui.diagnostic.analyses": {
|
||||
// https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md
|
||||
// "fieldalignment": false,
|
||||
"fieldalignment": false,
|
||||
"nilness": true,
|
||||
"shadow": false,
|
||||
"unusedparams": true,
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"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"
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type getBalancesByAddressRequest struct {
|
||||
type getBalancesRequest struct {
|
||||
Wallet string `params:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Limit int32 `query:"limit"`
|
||||
Offset int32 `query:"offset"`
|
||||
}
|
||||
|
||||
func (r getBalancesByAddressRequest) Validate() error {
|
||||
const (
|
||||
getBalancesMaxLimit = 5000
|
||||
getBalancesDefaultLimit = 100
|
||||
)
|
||||
|
||||
func (r getBalancesRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.Wallet == "" {
|
||||
errList = append(errList, errors.New("'wallet' is required"))
|
||||
@@ -25,6 +31,12 @@ func (r getBalancesByAddressRequest) Validate() error {
|
||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
||||
}
|
||||
if r.Limit < 0 {
|
||||
errList = append(errList, errors.New("'limit' must be non-negative"))
|
||||
}
|
||||
if r.Limit > getBalancesMaxLimit {
|
||||
errList = append(errList, errors.Errorf("'limit' cannot exceed %d", getBalancesMaxLimit))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
@@ -36,15 +48,15 @@ type balance struct {
|
||||
Decimals uint8 `json:"decimals"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressResult struct {
|
||||
type getBalancesResult struct {
|
||||
List []balance `json:"list"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressResponse = HttpResponse[getBalancesByAddressResult]
|
||||
type getBalancesResponse = HttpResponse[getBalancesResult]
|
||||
|
||||
func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
||||
var req getBalancesByAddressRequest
|
||||
func (h *HttpHandler) GetBalances(ctx *fiber.Ctx) (err error) {
|
||||
var req getBalancesRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -54,6 +66,9 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if req.Limit == 0 {
|
||||
req.Limit = getBalancesDefaultLimit
|
||||
}
|
||||
|
||||
pkScript, ok := resolvePkScript(h.network, req.Wallet)
|
||||
if !ok {
|
||||
@@ -64,49 +79,52 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("latest block not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight)
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("balances not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
|
||||
runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id)
|
||||
if ok {
|
||||
// filter out balances that don't match the requested rune id
|
||||
for key := range balances {
|
||||
if key != runeId {
|
||||
delete(balances, key)
|
||||
}
|
||||
}
|
||||
balances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
|
||||
return b.RuneId == runeId
|
||||
})
|
||||
}
|
||||
|
||||
balanceRuneIds := lo.Keys(balances)
|
||||
balanceRuneIds := lo.Map(balances, func(b *entity.Balance, _ int) runes.RuneId {
|
||||
return b.RuneId
|
||||
})
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), balanceRuneIds)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||
}
|
||||
|
||||
balanceList := make([]balance, 0, len(balances))
|
||||
for id, b := range balances {
|
||||
runeEntry := runeEntries[id]
|
||||
for _, b := range balances {
|
||||
runeEntry := runeEntries[b.RuneId]
|
||||
balanceList = append(balanceList, balance{
|
||||
Amount: b.Amount,
|
||||
Id: id,
|
||||
Id: b.RuneId,
|
||||
Name: runeEntry.SpacedRune,
|
||||
Symbol: string(runeEntry.Symbol),
|
||||
Decimals: runeEntry.Divisibility,
|
||||
})
|
||||
}
|
||||
slices.SortFunc(balanceList, func(i, j balance) int {
|
||||
return j.Amount.Cmp(i.Amount)
|
||||
})
|
||||
|
||||
resp := getBalancesByAddressResponse{
|
||||
Result: &getBalancesByAddressResult{
|
||||
resp := getBalancesResponse{
|
||||
Result: &getBalancesResult{
|
||||
BlockHeight: blockHeight,
|
||||
List: balanceList,
|
||||
},
|
||||
|
||||
@@ -3,10 +3,11 @@ package httphandler
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"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"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -16,33 +17,49 @@ type getBalanceQuery struct {
|
||||
Wallet string `json:"wallet"`
|
||||
Id string `json:"id"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressBatchRequest struct {
|
||||
type getBalancesBatchRequest struct {
|
||||
Queries []getBalanceQuery `json:"queries"`
|
||||
}
|
||||
|
||||
func (r getBalancesByAddressBatchRequest) Validate() error {
|
||||
const getBalancesBatchMaxQueries = 100
|
||||
|
||||
func (r getBalancesBatchRequest) Validate() error {
|
||||
var errList []error
|
||||
for _, query := range r.Queries {
|
||||
if len(r.Queries) == 0 {
|
||||
errList = append(errList, errors.New("at least one query is required"))
|
||||
}
|
||||
if len(r.Queries) > getBalancesBatchMaxQueries {
|
||||
errList = append(errList, errors.Errorf("cannot exceed %d queries", getBalancesBatchMaxQueries))
|
||||
}
|
||||
for i, query := range r.Queries {
|
||||
if query.Wallet == "" {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'wallet' is required"))
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'wallet' is required", i))
|
||||
}
|
||||
if query.Id != "" && !isRuneIdOrRuneName(query.Id) {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'id' is not valid rune id or rune name"))
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'id' is not valid rune id or rune name", i))
|
||||
}
|
||||
if query.Limit < 0 {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'limit' must be non-negative", i))
|
||||
}
|
||||
if query.Limit > getBalancesMaxLimit {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'limit' cannot exceed %d", i, getBalancesMaxLimit))
|
||||
}
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type getBalancesByAddressBatchResult struct {
|
||||
List []*getBalancesByAddressResult `json:"list"`
|
||||
type getBalancesBatchResult struct {
|
||||
List []*getBalancesResult `json:"list"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressBatchResponse = HttpResponse[getBalancesByAddressBatchResult]
|
||||
type getBalancesBatchResponse = HttpResponse[getBalancesBatchResult]
|
||||
|
||||
func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
||||
var req getBalancesByAddressBatchRequest
|
||||
func (h *HttpHandler) GetBalancesBatch(ctx *fiber.Ctx) (err error) {
|
||||
var req getBalancesBatchRequest
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -53,11 +70,14 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
||||
var latestBlockHeight uint64
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("latest block not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
latestBlockHeight = uint64(blockHeader.Height)
|
||||
|
||||
processQuery := func(ctx context.Context, query getBalanceQuery, queryIndex int) (*getBalancesByAddressResult, error) {
|
||||
processQuery := func(ctx context.Context, query getBalanceQuery, queryIndex int) (*getBalancesResult, error) {
|
||||
pkScript, ok := resolvePkScript(h.network, query.Wallet)
|
||||
if !ok {
|
||||
return nil, errs.NewPublicError(fmt.Sprintf("unable to resolve pkscript from \"queries[%d].wallet\"", queryIndex))
|
||||
@@ -68,50 +88,57 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
||||
blockHeight = latestBlockHeight
|
||||
}
|
||||
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight)
|
||||
if query.Limit == 0 {
|
||||
query.Limit = getBalancesMaxLimit
|
||||
}
|
||||
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight, query.Limit, query.Offset)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return nil, errs.NewPublicError("balances not found")
|
||||
}
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
|
||||
runeId, ok := h.resolveRuneId(ctx, query.Id)
|
||||
if ok {
|
||||
// filter out balances that don't match the requested rune id
|
||||
for key := range balances {
|
||||
if key != runeId {
|
||||
delete(balances, key)
|
||||
}
|
||||
}
|
||||
balances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
|
||||
return b.RuneId == runeId
|
||||
})
|
||||
}
|
||||
|
||||
balanceRuneIds := lo.Keys(balances)
|
||||
balanceRuneIds := lo.Map(balances, func(b *entity.Balance, _ int) runes.RuneId {
|
||||
return b.RuneId
|
||||
})
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx, balanceRuneIds)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return nil, errs.NewPublicError("rune not found")
|
||||
}
|
||||
return nil, errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||
}
|
||||
|
||||
balanceList := make([]balance, 0, len(balances))
|
||||
for id, b := range balances {
|
||||
runeEntry := runeEntries[id]
|
||||
for _, b := range balances {
|
||||
runeEntry := runeEntries[b.RuneId]
|
||||
balanceList = append(balanceList, balance{
|
||||
Amount: b.Amount,
|
||||
Id: id,
|
||||
Id: b.RuneId,
|
||||
Name: runeEntry.SpacedRune,
|
||||
Symbol: string(runeEntry.Symbol),
|
||||
Decimals: runeEntry.Divisibility,
|
||||
})
|
||||
}
|
||||
slices.SortFunc(balanceList, func(i, j balance) int {
|
||||
return j.Amount.Cmp(i.Amount)
|
||||
})
|
||||
|
||||
result := getBalancesByAddressResult{
|
||||
result := getBalancesResult{
|
||||
BlockHeight: blockHeight,
|
||||
List: balanceList,
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
results := make([]*getBalancesByAddressResult, len(req.Queries))
|
||||
results := make([]*getBalancesResult, len(req.Queries))
|
||||
eg, ectx := errgroup.WithContext(ctx.UserContext())
|
||||
for i, query := range req.Queries {
|
||||
i := i
|
||||
@@ -129,8 +156,8 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resp := getBalancesByAddressBatchResponse{
|
||||
Result: &getBalancesByAddressBatchResult{
|
||||
resp := getBalancesBatchResponse{
|
||||
Result: &getBalancesBatchResult{
|
||||
List: results,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"slices"
|
||||
|
||||
"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"
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -14,13 +17,26 @@ import (
|
||||
type getHoldersRequest struct {
|
||||
Id string `params:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
const (
|
||||
getHoldersMaxLimit = 1000
|
||||
getHoldersDefaultLimit = 100
|
||||
)
|
||||
|
||||
func (r getHoldersRequest) Validate() error {
|
||||
var errList []error
|
||||
if !isRuneIdOrRuneName(r.Id) {
|
||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
||||
}
|
||||
if r.Limit < 0 {
|
||||
errList = append(errList, errors.New("'limit' must be non-negative"))
|
||||
}
|
||||
if r.Limit > getHoldersMaxLimit {
|
||||
errList = append(errList, errors.Errorf("'limit' cannot exceed %d", getHoldersMaxLimit))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
@@ -61,6 +77,10 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = getHoldersDefaultLimit
|
||||
}
|
||||
|
||||
var runeId runes.RuneId
|
||||
if req.Id != "" {
|
||||
var ok bool
|
||||
@@ -75,10 +95,13 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("rune not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetHoldersByHeight")
|
||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdAndHeight")
|
||||
}
|
||||
holdingBalances, err := h.usecase.GetBalancesByRuneId(ctx.UserContext(), runeId, blockHeight)
|
||||
holdingBalances, err := h.usecase.GetBalancesByRuneId(ctx.UserContext(), runeId, blockHeight, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("balances not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetBalancesByRuneId")
|
||||
}
|
||||
|
||||
@@ -104,6 +127,14 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
||||
})
|
||||
}
|
||||
|
||||
// sort by amount descending, then pk script ascending
|
||||
slices.SortFunc(holdingBalances, func(b1, b2 *entity.Balance) int {
|
||||
if b1.Amount.Cmp(b2.Amount) == 0 {
|
||||
return bytes.Compare(b1.PkScript, b2.PkScript)
|
||||
}
|
||||
return b2.Amount.Cmp(b1.Amount)
|
||||
})
|
||||
|
||||
resp := getHoldersResponse{
|
||||
Result: &getHoldersResult{
|
||||
BlockHeight: blockHeight,
|
||||
|
||||
@@ -83,6 +83,9 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("latest block not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
@@ -104,8 +107,11 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
return errors.Wrap(err, "error during GetTokenInfoByHeight")
|
||||
}
|
||||
holdingBalances, err := h.usecase.GetBalancesByRuneId(ctx.UserContext(), runeId, blockHeight)
|
||||
holdingBalances, err := h.usecase.GetBalancesByRuneId(ctx.UserContext(), runeId, blockHeight, -1, 0) // get all balances
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("rune not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetBalancesByRuneId")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
@@ -15,13 +16,19 @@ import (
|
||||
)
|
||||
|
||||
type getTransactionsRequest struct {
|
||||
Wallet string `query:"wallet"`
|
||||
Id string `query:"id"`
|
||||
|
||||
FromBlock int64 `query:"fromBlock"`
|
||||
ToBlock int64 `query:"toBlock"`
|
||||
Wallet string `query:"wallet"`
|
||||
Id string `query:"id"`
|
||||
FromBlock int64 `query:"fromBlock"`
|
||||
ToBlock int64 `query:"toBlock"`
|
||||
Limit int32 `query:"limit"`
|
||||
Offset int32 `query:"offset"`
|
||||
}
|
||||
|
||||
const (
|
||||
getTransactionsMaxLimit = 3000
|
||||
getTransactionsDefaultLimit = 100
|
||||
)
|
||||
|
||||
func (r getTransactionsRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||
@@ -33,6 +40,12 @@ func (r getTransactionsRequest) Validate() error {
|
||||
if r.ToBlock < -1 {
|
||||
errList = append(errList, errors.Errorf("invalid toBlock range"))
|
||||
}
|
||||
if r.Limit < 0 {
|
||||
errList = append(errList, errors.New("'limit' must be non-negative"))
|
||||
}
|
||||
if r.Limit > getTransactionsMaxLimit {
|
||||
errList = append(errList, errors.Errorf("'limit' cannot exceed %d", getTransactionsMaxLimit))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
@@ -133,6 +146,9 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
return errs.NewPublicError("unable to resolve rune id from \"id\"")
|
||||
}
|
||||
}
|
||||
if req.Limit == 0 {
|
||||
req.Limit = getTransactionsDefaultLimit
|
||||
}
|
||||
|
||||
// default to latest block
|
||||
if req.ToBlock == 0 {
|
||||
@@ -143,6 +159,9 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
if req.FromBlock == -1 || req.ToBlock == -1 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("latest block not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
if req.FromBlock == -1 {
|
||||
@@ -158,8 +177,11 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
return errs.NewPublicError(fmt.Sprintf("fromBlock must be less than or equal to toBlock, got fromBlock=%d, toBlock=%d", req.FromBlock, req.ToBlock))
|
||||
}
|
||||
|
||||
txs, err := h.usecase.GetRuneTransactions(ctx.UserContext(), pkScript, runeId, uint64(req.FromBlock), uint64(req.ToBlock))
|
||||
txs, err := h.usecase.GetRuneTransactions(ctx.UserContext(), pkScript, runeId, uint64(req.FromBlock), uint64(req.ToBlock), req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("transactions not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetRuneTransactions")
|
||||
}
|
||||
|
||||
@@ -181,6 +203,9 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
allRuneIds = lo.Uniq(allRuneIds)
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), allRuneIds)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("rune entries not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||
}
|
||||
|
||||
@@ -279,12 +304,12 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
txList = append(txList, respTx)
|
||||
}
|
||||
// sort by block height ASC, then index ASC
|
||||
// sort by block height DESC, then index DESC
|
||||
slices.SortFunc(txList, func(t1, t2 transaction) int {
|
||||
if t1.BlockHeight != t2.BlockHeight {
|
||||
return int(t1.BlockHeight - t2.BlockHeight)
|
||||
return cmp.Compare(t2.BlockHeight, t1.BlockHeight)
|
||||
}
|
||||
return int(t1.Index - t2.Index)
|
||||
return cmp.Compare(t2.Index, t1.Index)
|
||||
})
|
||||
|
||||
resp := getTransactionsResponse{
|
||||
|
||||
@@ -2,7 +2,6 @@ package httphandler
|
||||
|
||||
import (
|
||||
"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"
|
||||
@@ -12,13 +11,20 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type getUTXOsByAddressRequest struct {
|
||||
type getUTXOsRequest struct {
|
||||
Wallet string `params:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Limit int32 `query:"limit"`
|
||||
Offset int32 `query:"offset"`
|
||||
}
|
||||
|
||||
func (r getUTXOsByAddressRequest) Validate() error {
|
||||
const (
|
||||
getUTXOsMaxLimit = 3000
|
||||
getUTXOsDefaultLimit = 100
|
||||
)
|
||||
|
||||
func (r getUTXOsRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.Wallet == "" {
|
||||
errList = append(errList, errors.New("'wallet' is required"))
|
||||
@@ -26,6 +32,12 @@ func (r getUTXOsByAddressRequest) Validate() error {
|
||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
||||
}
|
||||
if r.Limit < 0 {
|
||||
errList = append(errList, errors.New("'limit' must be non-negative"))
|
||||
}
|
||||
if r.Limit > getUTXOsMaxLimit {
|
||||
errList = append(errList, errors.Errorf("'limit' cannot exceed %d", getUTXOsMaxLimit))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
@@ -41,21 +53,21 @@ type utxoExtend struct {
|
||||
Runes []runeBalance `json:"runes"`
|
||||
}
|
||||
|
||||
type utxo struct {
|
||||
type utxoItem struct {
|
||||
TxHash chainhash.Hash `json:"txHash"`
|
||||
OutputIndex uint32 `json:"outputIndex"`
|
||||
Extend utxoExtend `json:"extend"`
|
||||
}
|
||||
|
||||
type getUTXOsByAddressResult struct {
|
||||
List []utxo `json:"list"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
type getUTXOsResult struct {
|
||||
List []utxoItem `json:"list"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
}
|
||||
|
||||
type getUTXOsByAddressResponse = HttpResponse[getUTXOsByAddressResult]
|
||||
type getUTXOsResponse = HttpResponse[getUTXOsResult]
|
||||
|
||||
func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
|
||||
var req getUTXOsByAddressRequest
|
||||
func (h *HttpHandler) GetUTXOs(ctx *fiber.Ctx) (err error) {
|
||||
var req getUTXOsRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -71,36 +83,60 @@ func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
|
||||
return errs.NewPublicError("unable to resolve pkscript from \"wallet\"")
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = getUTXOsDefaultLimit
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("latest block not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
outPointBalances, err := h.usecase.GetUnspentOutPointBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
var utxos []*entity.RunesUTXO
|
||||
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 {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("utxos not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
} else {
|
||||
utxos, err = h.usecase.GetRunesUTXOsByPkScript(ctx.UserContext(), pkScript, blockHeight, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return errs.NewPublicError("utxos not found")
|
||||
}
|
||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
}
|
||||
|
||||
outPointBalanceRuneIds := lo.Map(outPointBalances, func(outPointBalance *entity.OutPointBalance, _ int) runes.RuneId {
|
||||
return outPointBalance.RuneId
|
||||
})
|
||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), outPointBalanceRuneIds)
|
||||
runeIds := make(map[runes.RuneId]struct{}, 0)
|
||||
for _, utxo := range utxos {
|
||||
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")
|
||||
}
|
||||
|
||||
groupedBalances := lo.GroupBy(outPointBalances, func(outPointBalance *entity.OutPointBalance) wire.OutPoint {
|
||||
return outPointBalance.OutPoint
|
||||
})
|
||||
|
||||
utxoList := make([]utxo, 0, len(groupedBalances))
|
||||
for outPoint, balances := range groupedBalances {
|
||||
runeBalances := make([]runeBalance, 0, len(balances))
|
||||
for _, balance := range balances {
|
||||
utxoRespList := make([]utxoItem, 0, len(utxos))
|
||||
for _, utxo := range utxos {
|
||||
runeBalances := make([]runeBalance, 0, len(utxo.RuneBalances))
|
||||
for _, balance := range utxo.RuneBalances {
|
||||
runeEntry := runeEntries[balance.RuneId]
|
||||
runeBalances = append(runeBalances, runeBalance{
|
||||
RuneId: balance.RuneId,
|
||||
@@ -111,34 +147,19 @@ func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
|
||||
})
|
||||
}
|
||||
|
||||
utxoList = append(utxoList, utxo{
|
||||
TxHash: outPoint.Hash,
|
||||
OutputIndex: outPoint.Index,
|
||||
utxoRespList = append(utxoRespList, utxoItem{
|
||||
TxHash: utxo.OutPoint.Hash,
|
||||
OutputIndex: utxo.OutPoint.Index,
|
||||
Extend: utxoExtend{
|
||||
Runes: runeBalances,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// filter by req.Id if exists
|
||||
{
|
||||
runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id)
|
||||
if ok {
|
||||
utxoList = lo.Filter(utxoList, func(u utxo, _ int) bool {
|
||||
for _, runeBalance := range u.Extend.Runes {
|
||||
if runeBalance.RuneId == runeId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resp := getUTXOsByAddressResponse{
|
||||
Result: &getUTXOsByAddressResult{
|
||||
resp := getUTXOsResponse{
|
||||
Result: &getUTXOsResult{
|
||||
BlockHeight: blockHeight,
|
||||
List: utxoList,
|
||||
List: utxoRespList,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
func (h *HttpHandler) Mount(router fiber.Router) error {
|
||||
r := router.Group("/v2/runes")
|
||||
|
||||
r.Post("/balances/wallet/batch", h.GetBalancesByAddressBatch)
|
||||
r.Get("/balances/wallet/:wallet", h.GetBalancesByAddress)
|
||||
r.Post("/balances/wallet/batch", h.GetBalancesBatch)
|
||||
r.Get("/balances/wallet/:wallet", h.GetBalances)
|
||||
r.Get("/transactions", h.GetTransactions)
|
||||
r.Get("/holders/:id", h.GetHolders)
|
||||
r.Get("/info/:id", h.GetTokenInfo)
|
||||
r.Get("/utxos/wallet/:wallet", h.GetUTXOsByAddress)
|
||||
r.Get("/utxos/wallet/:wallet", h.GetUTXOs)
|
||||
r.Get("/block", h.GetCurrentBlock)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,5 +118,7 @@ CREATE TABLE IF NOT EXISTS "runes_balances" (
|
||||
"amount" DECIMAL NOT NULL,
|
||||
PRIMARY KEY ("pkscript", "rune_id", "block_height")
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS runes_balances_rune_id_block_height_idx ON "runes_balances" USING BTREE ("rune_id", "block_height");
|
||||
CREATE INDEX IF NOT EXISTS runes_balances_pkscript_block_height_idx ON "runes_balances" USING BTREE ("pkscript", "block_height");
|
||||
|
||||
COMMIT;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (rune_id) * FROM runes_balances WHERE pkscript = $1 AND block_height <= $2 ORDER BY rune_id, block_height DESC
|
||||
)
|
||||
SELECT * FROM balances WHERE amount > 0;
|
||||
SELECT * FROM balances WHERE amount > 0 ORDER BY amount DESC, rune_id LIMIT $3 OFFSET $4;
|
||||
|
||||
-- name: GetBalancesByRuneId :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (pkscript) * FROM runes_balances WHERE rune_id = $1 AND block_height <= $2 ORDER BY pkscript, block_height DESC
|
||||
)
|
||||
SELECT * FROM balances WHERE amount > 0;
|
||||
SELECT * FROM balances WHERE amount > 0 ORDER BY amount DESC, pkscript LIMIT $3 OFFSET $4;
|
||||
|
||||
-- name: GetBalanceByPkScriptAndRuneId :one
|
||||
SELECT * FROM runes_balances WHERE pkscript = $1 AND rune_id = $2 AND block_height <= $3 ORDER BY block_height DESC LIMIT 1;
|
||||
@@ -16,8 +16,28 @@ SELECT * FROM runes_balances WHERE pkscript = $1 AND rune_id = $2 AND block_heig
|
||||
-- name: GetOutPointBalancesAtOutPoint :many
|
||||
SELECT * FROM runes_outpoint_balances WHERE tx_hash = $1 AND tx_idx = $2;
|
||||
|
||||
-- name: GetUnspentOutPointBalancesByPkScript :many
|
||||
SELECT * FROM runes_outpoint_balances WHERE pkscript = @pkScript AND block_height <= @block_height AND (spent_height IS NULL OR spent_height > @block_height);
|
||||
-- name: GetRunesUTXOsByPkScript :many
|
||||
SELECT tx_hash, tx_idx, max("pkscript") as pkscript, array_agg("rune_id") as rune_ids, array_agg("amount") as amounts
|
||||
FROM runes_outpoint_balances
|
||||
WHERE
|
||||
pkscript = @pkScript AND
|
||||
block_height <= @block_height AND
|
||||
(spent_height IS NULL OR spent_height > @block_height)
|
||||
GROUP BY tx_hash, tx_idx
|
||||
ORDER BY tx_hash, tx_idx
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: GetRunesUTXOsByRuneIdAndPkScript :many
|
||||
SELECT tx_hash, tx_idx, max("pkscript") as pkscript, array_agg("rune_id") as rune_ids, array_agg("amount") as amounts
|
||||
FROM runes_outpoint_balances
|
||||
WHERE
|
||||
pkscript = @pkScript AND
|
||||
block_height <= @block_height AND
|
||||
(spent_height IS NULL OR spent_height > @block_height)
|
||||
GROUP BY tx_hash, tx_idx
|
||||
HAVING array_agg("rune_id") @> @rune_ids::text[]
|
||||
ORDER BY tx_hash, tx_idx
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: GetRuneEntriesByRuneIds :many
|
||||
WITH states AS (
|
||||
@@ -57,7 +77,7 @@ SELECT * FROM runes_transactions
|
||||
) AND (
|
||||
@from_block <= runes_transactions.block_height AND runes_transactions.block_height <= @to_block
|
||||
)
|
||||
ORDER BY runes_transactions.block_height DESC LIMIT 10000;
|
||||
ORDER BY runes_transactions.block_height DESC, runes_transactions.index DESC LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: CountRuneEntries :one
|
||||
SELECT COUNT(*) FROM runes_entries;
|
||||
|
||||
@@ -27,10 +27,11 @@ type RunesReaderDataGateway interface {
|
||||
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
|
||||
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) ([]*entity.RuneTransaction, error)
|
||||
GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64, limit int32, offset int32) ([]*entity.RuneTransaction, error)
|
||||
|
||||
GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error)
|
||||
GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error)
|
||||
GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, runeId runes.RuneId, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error)
|
||||
GetRunesUTXOsByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error)
|
||||
// GetRuneIdFromRune returns the RuneId for the given rune. Returns errs.NotFound if the rune entry is not found.
|
||||
GetRuneIdFromRune(ctx context.Context, rune runes.Rune) (runes.RuneId, error)
|
||||
// GetRuneEntryByRuneId returns the RuneEntry for the given runeId. Returns errs.NotFound if the rune entry is not found.
|
||||
@@ -45,10 +46,12 @@ type RunesReaderDataGateway interface {
|
||||
CountRuneEntries(ctx context.Context) (uint64, error)
|
||||
|
||||
// GetBalancesByPkScript returns the balances for the given pkScript at the given blockHeight.
|
||||
GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) (map[runes.RuneId]*entity.Balance, error)
|
||||
// Use limit = -1 as no limit.
|
||||
GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.Balance, error)
|
||||
// GetBalancesByRuneId returns the balances for the given runeId at the given blockHeight.
|
||||
// Cannot use []byte as map key, so we're returning as slice.
|
||||
GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64) ([]*entity.Balance, error)
|
||||
// Use limit = -1 as no limit.
|
||||
GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64, limit int32, offset int32) ([]*entity.Balance, error)
|
||||
// GetBalancesByPkScriptAndRuneId returns the balance for the given pkScript and runeId at the given blockHeight.
|
||||
GetBalanceByPkScriptAndRuneId(ctx context.Context, pkScript []byte, runeId runes.RuneId, blockHeight uint64) (*entity.Balance, error)
|
||||
}
|
||||
|
||||
18
modules/runes/internal/entity/runes_utxo.go
Normal file
18
modules/runes/internal/entity/runes_utxo.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
"github.com/gaze-network/uint128"
|
||||
)
|
||||
|
||||
type RunesUTXOBalance struct {
|
||||
RuneId runes.RuneId
|
||||
Amount uint128.Uint128
|
||||
}
|
||||
|
||||
type RunesUTXO struct {
|
||||
PkScript []byte
|
||||
OutPoint wire.OutPoint
|
||||
RuneBalances []RunesUTXOBalance
|
||||
}
|
||||
@@ -296,12 +296,14 @@ const getBalancesByPkScript = `-- name: GetBalancesByPkScript :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (rune_id) pkscript, block_height, rune_id, amount FROM runes_balances WHERE pkscript = $1 AND block_height <= $2 ORDER BY rune_id, block_height DESC
|
||||
)
|
||||
SELECT pkscript, block_height, rune_id, amount FROM balances WHERE amount > 0
|
||||
SELECT pkscript, block_height, rune_id, amount FROM balances WHERE amount > 0 ORDER BY amount DESC, rune_id LIMIT $3 OFFSET $4
|
||||
`
|
||||
|
||||
type GetBalancesByPkScriptParams struct {
|
||||
Pkscript string
|
||||
BlockHeight int32
|
||||
Limit int32
|
||||
Offset int32
|
||||
}
|
||||
|
||||
type GetBalancesByPkScriptRow struct {
|
||||
@@ -312,7 +314,12 @@ type GetBalancesByPkScriptRow struct {
|
||||
}
|
||||
|
||||
func (q *Queries) GetBalancesByPkScript(ctx context.Context, arg GetBalancesByPkScriptParams) ([]GetBalancesByPkScriptRow, error) {
|
||||
rows, err := q.db.Query(ctx, getBalancesByPkScript, arg.Pkscript, arg.BlockHeight)
|
||||
rows, err := q.db.Query(ctx, getBalancesByPkScript,
|
||||
arg.Pkscript,
|
||||
arg.BlockHeight,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -340,12 +347,14 @@ const getBalancesByRuneId = `-- name: GetBalancesByRuneId :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (pkscript) pkscript, block_height, rune_id, amount FROM runes_balances WHERE rune_id = $1 AND block_height <= $2 ORDER BY pkscript, block_height DESC
|
||||
)
|
||||
SELECT pkscript, block_height, rune_id, amount FROM balances WHERE amount > 0
|
||||
SELECT pkscript, block_height, rune_id, amount FROM balances WHERE amount > 0 ORDER BY amount DESC, pkscript LIMIT $3 OFFSET $4
|
||||
`
|
||||
|
||||
type GetBalancesByRuneIdParams struct {
|
||||
RuneID string
|
||||
BlockHeight int32
|
||||
Limit int32
|
||||
Offset int32
|
||||
}
|
||||
|
||||
type GetBalancesByRuneIdRow struct {
|
||||
@@ -356,7 +365,12 @@ type GetBalancesByRuneIdRow struct {
|
||||
}
|
||||
|
||||
func (q *Queries) GetBalancesByRuneId(ctx context.Context, arg GetBalancesByRuneIdParams) ([]GetBalancesByRuneIdRow, error) {
|
||||
rows, err := q.db.Query(ctx, getBalancesByRuneId, arg.RuneID, arg.BlockHeight)
|
||||
rows, err := q.db.Query(ctx, getBalancesByRuneId,
|
||||
arg.RuneID,
|
||||
arg.BlockHeight,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -635,23 +649,25 @@ 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
|
||||
WHERE (
|
||||
$1::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
||||
OR runes_transactions.outputs @> $2::JSONB
|
||||
OR runes_transactions.inputs @> $2::JSONB
|
||||
) AND (
|
||||
$3::BOOLEAN = FALSE -- if @filter_rune_id is TRUE, apply rune_id filter
|
||||
$3::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
||||
OR runes_transactions.outputs @> $4::JSONB
|
||||
OR runes_transactions.inputs @> $4::JSONB
|
||||
OR runes_transactions.mints ? $5
|
||||
OR runes_transactions.burns ? $5
|
||||
OR (runes_transactions.rune_etched = TRUE AND runes_transactions.block_height = $6 AND runes_transactions.index = $7)
|
||||
OR runes_transactions.inputs @> $4::JSONB
|
||||
) AND (
|
||||
$8 <= runes_transactions.block_height AND runes_transactions.block_height <= $9
|
||||
$5::BOOLEAN = FALSE -- if @filter_rune_id is TRUE, apply rune_id filter
|
||||
OR runes_transactions.outputs @> $6::JSONB
|
||||
OR runes_transactions.inputs @> $6::JSONB
|
||||
OR runes_transactions.mints ? $7
|
||||
OR runes_transactions.burns ? $7
|
||||
OR (runes_transactions.rune_etched = TRUE AND runes_transactions.block_height = $8 AND runes_transactions.index = $9)
|
||||
) AND (
|
||||
$10 <= runes_transactions.block_height AND runes_transactions.block_height <= $11
|
||||
)
|
||||
ORDER BY runes_transactions.block_height DESC LIMIT 10000
|
||||
ORDER BY runes_transactions.block_height DESC, runes_transactions.index DESC LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type GetRuneTransactionsParams struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
FilterPkScript bool
|
||||
PkScriptParam []byte
|
||||
FilterRuneID bool
|
||||
@@ -698,6 +714,8 @@ type GetRuneTransactionsRow struct {
|
||||
|
||||
func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactionsParams) ([]GetRuneTransactionsRow, error) {
|
||||
rows, err := q.db.Query(ctx, getRuneTransactions,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.FilterPkScript,
|
||||
arg.PkScriptParam,
|
||||
arg.FilterRuneID,
|
||||
@@ -757,32 +775,114 @@ func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactio
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getUnspentOutPointBalancesByPkScript = `-- name: GetUnspentOutPointBalancesByPkScript :many
|
||||
SELECT rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height FROM runes_outpoint_balances WHERE pkscript = $1 AND block_height <= $2 AND (spent_height IS NULL OR spent_height > $2)
|
||||
const getRunesUTXOsByPkScript = `-- name: GetRunesUTXOsByPkScript :many
|
||||
SELECT tx_hash, tx_idx, max("pkscript") as pkscript, array_agg("rune_id") as rune_ids, array_agg("amount") as amounts
|
||||
FROM runes_outpoint_balances
|
||||
WHERE
|
||||
pkscript = $3 AND
|
||||
block_height <= $4 AND
|
||||
(spent_height IS NULL OR spent_height > $4)
|
||||
GROUP BY tx_hash, tx_idx
|
||||
ORDER BY tx_hash, tx_idx
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type GetUnspentOutPointBalancesByPkScriptParams struct {
|
||||
type GetRunesUTXOsByPkScriptParams struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
Pkscript string
|
||||
BlockHeight int32
|
||||
}
|
||||
|
||||
func (q *Queries) GetUnspentOutPointBalancesByPkScript(ctx context.Context, arg GetUnspentOutPointBalancesByPkScriptParams) ([]RunesOutpointBalance, error) {
|
||||
rows, err := q.db.Query(ctx, getUnspentOutPointBalancesByPkScript, arg.Pkscript, arg.BlockHeight)
|
||||
type GetRunesUTXOsByPkScriptRow struct {
|
||||
TxHash string
|
||||
TxIdx int32
|
||||
Pkscript interface{}
|
||||
RuneIds interface{}
|
||||
Amounts interface{}
|
||||
}
|
||||
|
||||
func (q *Queries) GetRunesUTXOsByPkScript(ctx context.Context, arg GetRunesUTXOsByPkScriptParams) ([]GetRunesUTXOsByPkScriptRow, error) {
|
||||
rows, err := q.db.Query(ctx, getRunesUTXOsByPkScript,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.Pkscript,
|
||||
arg.BlockHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []RunesOutpointBalance
|
||||
var items []GetRunesUTXOsByPkScriptRow
|
||||
for rows.Next() {
|
||||
var i RunesOutpointBalance
|
||||
var i GetRunesUTXOsByPkScriptRow
|
||||
if err := rows.Scan(
|
||||
&i.RuneID,
|
||||
&i.Pkscript,
|
||||
&i.TxHash,
|
||||
&i.TxIdx,
|
||||
&i.Amount,
|
||||
&i.BlockHeight,
|
||||
&i.SpentHeight,
|
||||
&i.Pkscript,
|
||||
&i.RuneIds,
|
||||
&i.Amounts,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRunesUTXOsByRuneIdAndPkScript = `-- name: GetRunesUTXOsByRuneIdAndPkScript :many
|
||||
SELECT tx_hash, tx_idx, max("pkscript") as pkscript, array_agg("rune_id") as rune_ids, array_agg("amount") as amounts
|
||||
FROM runes_outpoint_balances
|
||||
WHERE
|
||||
pkscript = $3 AND
|
||||
block_height <= $4 AND
|
||||
(spent_height IS NULL OR spent_height > $4)
|
||||
GROUP BY tx_hash, tx_idx
|
||||
HAVING array_agg("rune_id") @> $5::text[]
|
||||
ORDER BY tx_hash, tx_idx
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type GetRunesUTXOsByRuneIdAndPkScriptParams struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
Pkscript string
|
||||
BlockHeight int32
|
||||
RuneIds []string
|
||||
}
|
||||
|
||||
type GetRunesUTXOsByRuneIdAndPkScriptRow struct {
|
||||
TxHash string
|
||||
TxIdx int32
|
||||
Pkscript interface{}
|
||||
RuneIds interface{}
|
||||
Amounts interface{}
|
||||
}
|
||||
|
||||
func (q *Queries) GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, arg GetRunesUTXOsByRuneIdAndPkScriptParams) ([]GetRunesUTXOsByRuneIdAndPkScriptRow, error) {
|
||||
rows, err := q.db.Query(ctx, getRunesUTXOsByRuneIdAndPkScript,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.Pkscript,
|
||||
arg.BlockHeight,
|
||||
arg.RuneIds,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetRunesUTXOsByRuneIdAndPkScriptRow
|
||||
for rows.Next() {
|
||||
var i GetRunesUTXOsByRuneIdAndPkScriptRow
|
||||
if err := rows.Scan(
|
||||
&i.TxHash,
|
||||
&i.TxIdx,
|
||||
&i.Pkscript,
|
||||
&i.RuneIds,
|
||||
&i.Amounts,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -638,6 +638,72 @@ func mapIndexedBlockTypeToParams(src entity.IndexedBlock) (gen.CreateIndexedBloc
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapRunesUTXOModelToType(src gen.GetRunesUTXOsByPkScriptRow) (entity.RunesUTXO, error) {
|
||||
pkScriptRaw, ok := src.Pkscript.(string)
|
||||
if !ok {
|
||||
return entity.RunesUTXO{}, errors.New("pkscript from database is not string")
|
||||
}
|
||||
pkScript, err := hex.DecodeString(pkScriptRaw)
|
||||
if err != nil {
|
||||
return entity.RunesUTXO{}, errors.Wrap(err, "failed to parse pkscript")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.TxHash)
|
||||
if err != nil {
|
||||
return entity.RunesUTXO{}, errors.Wrap(err, "failed to parse tx hash")
|
||||
}
|
||||
runeIdsRaw, ok := src.RuneIds.([]interface{})
|
||||
if !ok {
|
||||
return entity.RunesUTXO{}, errors.New("src.RuneIds is not a slice")
|
||||
}
|
||||
runeIds := make([]string, 0, len(runeIdsRaw))
|
||||
for i, raw := range runeIdsRaw {
|
||||
runeId, ok := raw.(string)
|
||||
if !ok {
|
||||
return entity.RunesUTXO{}, errors.Errorf("src.RuneIds[%d] is not a string", i)
|
||||
}
|
||||
runeIds = append(runeIds, runeId)
|
||||
}
|
||||
amountsRaw, ok := src.Amounts.([]interface{})
|
||||
if !ok {
|
||||
return entity.RunesUTXO{}, errors.New("amounts from database is not a slice")
|
||||
}
|
||||
amounts := make([]pgtype.Numeric, 0, len(amountsRaw))
|
||||
for i, raw := range amountsRaw {
|
||||
amount, ok := raw.(pgtype.Numeric)
|
||||
if !ok {
|
||||
return entity.RunesUTXO{}, errors.Errorf("src.Amounts[%d] is not pgtype.Numeric", i)
|
||||
}
|
||||
amounts = append(amounts, amount)
|
||||
}
|
||||
if len(runeIds) != len(amounts) {
|
||||
return entity.RunesUTXO{}, errors.New("rune ids and amounts have different lengths")
|
||||
}
|
||||
|
||||
runesBalances := make([]entity.RunesUTXOBalance, 0, len(runeIds))
|
||||
for i := range runeIds {
|
||||
runeId, err := runes.NewRuneIdFromString(runeIds[i])
|
||||
if err != nil {
|
||||
return entity.RunesUTXO{}, errors.Wrap(err, "failed to parse rune id")
|
||||
}
|
||||
amount, err := uint128FromNumeric(amounts[i])
|
||||
if err != nil {
|
||||
return entity.RunesUTXO{}, errors.Wrap(err, "failed to parse amount")
|
||||
}
|
||||
runesBalances = append(runesBalances, entity.RunesUTXOBalance{
|
||||
RuneId: runeId,
|
||||
Amount: lo.FromPtr(amount),
|
||||
})
|
||||
}
|
||||
return entity.RunesUTXO{
|
||||
PkScript: pkScript,
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *txHash,
|
||||
Index: uint32(src.TxIdx),
|
||||
},
|
||||
RuneBalances: runesBalances,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapOutPointBalanceModelToType(src gen.RunesOutpointBalance) (entity.OutPointBalance, error) {
|
||||
runeId, err := runes.NewRuneIdFromString(src.RuneID)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@@ -62,7 +63,18 @@ func (r *Repository) GetIndexedBlockByHeight(ctx context.Context, height int64)
|
||||
return indexedBlock, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64) ([]*entity.RuneTransaction, error) {
|
||||
const maxRuneTransactionsLimit = 10000 // temporary limit to prevent large queries from overwhelming the database
|
||||
|
||||
func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64, limit int32, offset int32) ([]*entity.RuneTransaction, error) {
|
||||
if limit == -1 {
|
||||
limit = maxRuneTransactionsLimit
|
||||
}
|
||||
if limit < 0 {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "limit must be -1 or non-negative")
|
||||
}
|
||||
if limit > maxRuneTransactionsLimit {
|
||||
return nil, errors.Wrapf(errs.InvalidArgument, "limit cannot exceed %d", maxRuneTransactionsLimit)
|
||||
}
|
||||
pkScriptParam := []byte(fmt.Sprintf(`[{"pkScript":"%s"}]`, hex.EncodeToString(pkScript)))
|
||||
runeIdParam := []byte(fmt.Sprintf(`[{"runeId":"%s"}]`, runeId.String()))
|
||||
rows, err := r.queries.GetRuneTransactions(ctx, gen.GetRuneTransactionsParams{
|
||||
@@ -77,6 +89,9 @@ func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, r
|
||||
|
||||
FromBlock: int32(fromBlock),
|
||||
ToBlock: int32(toBlock),
|
||||
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
@@ -125,22 +140,59 @@ func (r *Repository) GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wi
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error) {
|
||||
balances, err := r.queries.GetUnspentOutPointBalancesByPkScript(ctx, gen.GetUnspentOutPointBalancesByPkScriptParams{
|
||||
func (r *Repository) GetRunesUTXOsByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error) {
|
||||
if limit == -1 {
|
||||
limit = math.MaxInt32
|
||||
}
|
||||
if limit < 0 {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "limit must be -1 or non-negative")
|
||||
}
|
||||
rows, err := r.queries.GetRunesUTXOsByPkScript(ctx, gen.GetRunesUTXOsByPkScriptParams{
|
||||
Pkscript: hex.EncodeToString(pkScript),
|
||||
BlockHeight: int32(blockHeight),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
result := make([]*entity.OutPointBalance, 0, len(balances))
|
||||
for _, balanceModel := range balances {
|
||||
balance, err := mapOutPointBalanceModelToType(balanceModel)
|
||||
result := make([]*entity.RunesUTXO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
utxo, err := mapRunesUTXOModelToType(row)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse balance model")
|
||||
return nil, errors.Wrap(err, "failed to parse row model")
|
||||
}
|
||||
result = append(result, &balance)
|
||||
result = append(result, &utxo)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, runeId runes.RuneId, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error) {
|
||||
if limit == -1 {
|
||||
limit = math.MaxInt32
|
||||
}
|
||||
if limit < 0 {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "limit must be -1 or non-negative")
|
||||
}
|
||||
rows, err := r.queries.GetRunesUTXOsByRuneIdAndPkScript(ctx, gen.GetRunesUTXOsByRuneIdAndPkScriptParams{
|
||||
Pkscript: hex.EncodeToString(pkScript),
|
||||
BlockHeight: int32(blockHeight),
|
||||
RuneIds: []string{runeId.String()},
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
result := make([]*entity.RunesUTXO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
utxo, err := mapRunesUTXOModelToType(gen.GetRunesUTXOsByPkScriptRow(row))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse row")
|
||||
}
|
||||
result = append(result, &utxo)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -245,30 +297,46 @@ func (r *Repository) CountRuneEntries(ctx context.Context) (uint64, error) {
|
||||
return uint64(count), nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) (map[runes.RuneId]*entity.Balance, error) {
|
||||
func (r *Repository) GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.Balance, error) {
|
||||
if limit == -1 {
|
||||
limit = math.MaxInt32
|
||||
}
|
||||
if limit < 0 {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "limit must be -1 or non-negative")
|
||||
}
|
||||
balances, err := r.queries.GetBalancesByPkScript(ctx, gen.GetBalancesByPkScriptParams{
|
||||
Pkscript: hex.EncodeToString(pkScript),
|
||||
BlockHeight: int32(blockHeight),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
result := make(map[runes.RuneId]*entity.Balance, len(balances))
|
||||
result := make([]*entity.Balance, 0, len(balances))
|
||||
for _, balanceModel := range balances {
|
||||
balance, err := mapBalanceModelToType(gen.RunesBalance(balanceModel))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse balance model")
|
||||
}
|
||||
result[balance.RuneId] = balance
|
||||
result = append(result, balance)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64) ([]*entity.Balance, error) {
|
||||
func (r *Repository) GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64, limit int32, offset int32) ([]*entity.Balance, error) {
|
||||
if limit == -1 {
|
||||
limit = math.MaxInt32
|
||||
}
|
||||
if limit < 0 {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "limit must be -1 or non-negative")
|
||||
}
|
||||
balances, err := r.queries.GetBalancesByRuneId(ctx, gen.GetBalancesByRuneIdParams{
|
||||
RuneID: runeId.String(),
|
||||
BlockHeight: int32(blockHeight),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
|
||||
@@ -29,6 +29,10 @@ var ErrInvalidBase26 = errors.New("invalid base-26 character: must be in the ran
|
||||
func NewRuneFromString(value string) (Rune, error) {
|
||||
n := uint128.From64(0)
|
||||
for i, char := range value {
|
||||
// skip spacers
|
||||
if char == '.' || char == '•' {
|
||||
continue
|
||||
}
|
||||
if i > 0 {
|
||||
n = n.Add(uint128.From64(1))
|
||||
}
|
||||
|
||||
@@ -8,16 +8,18 @@ import (
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) (map[runes.RuneId]*entity.Balance, error) {
|
||||
balances, err := u.runesDg.GetBalancesByPkScript(ctx, pkScript, blockHeight)
|
||||
// Use limit = -1 as no limit.
|
||||
func (u *Usecase) GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.Balance, error) {
|
||||
balances, err := u.runesDg.GetBalancesByPkScript(ctx, pkScript, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
return balances, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64) ([]*entity.Balance, error) {
|
||||
balances, err := u.runesDg.GetBalancesByRuneId(ctx, runeId, blockHeight)
|
||||
// Use limit = -1 as no limit.
|
||||
func (u *Usecase) GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64, limit int32, offset int32) ([]*entity.Balance, error) {
|
||||
balances, err := u.runesDg.GetBalancesByRuneId(ctx, runeId, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get rune holders by rune id")
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error) {
|
||||
balances, err := u.runesDg.GetUnspentOutPointBalancesByPkScript(ctx, pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
return balances, nil
|
||||
}
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64) ([]*entity.RuneTransaction, error) {
|
||||
txs, err := u.runesDg.GetRuneTransactions(ctx, pkScript, runeId, fromBlock, toBlock)
|
||||
// Use limit = -1 as no limit.
|
||||
func (u *Usecase) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64, limit int32, offset int32) ([]*entity.RuneTransaction, error) {
|
||||
txs, err := u.runesDg.GetRuneTransactions(ctx, pkScript, runeId, fromBlock, toBlock, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTransactionsByHeight")
|
||||
}
|
||||
|
||||
25
modules/runes/usecase/get_utxos.go
Normal file
25
modules/runes/usecase/get_utxos.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"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) {
|
||||
balances, err := u.runesDg.GetRunesUTXOsByPkScript(ctx, pkScript, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
return balances, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, runeId runes.RuneId, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, 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
|
||||
}
|
||||
Reference in New Issue
Block a user