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.completion.usePlaceholders": false,
|
||||||
"ui.diagnostic.analyses": {
|
"ui.diagnostic.analyses": {
|
||||||
// https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md
|
// https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md
|
||||||
// "fieldalignment": false,
|
"fieldalignment": false,
|
||||||
"nilness": true,
|
"nilness": true,
|
||||||
"shadow": false,
|
"shadow": false,
|
||||||
"unusedparams": true,
|
"unusedparams": true,
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
package httphandler
|
package httphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/gaze-network/indexer-network/common/errs"
|
"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/indexer-network/modules/runes/runes"
|
||||||
"github.com/gaze-network/uint128"
|
"github.com/gaze-network/uint128"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type getBalancesByAddressRequest struct {
|
type getBalancesRequest struct {
|
||||||
Wallet string `params:"wallet"`
|
Wallet string `params:"wallet"`
|
||||||
Id string `query:"id"`
|
Id string `query:"id"`
|
||||||
BlockHeight uint64 `query:"blockHeight"`
|
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
|
var errList []error
|
||||||
if r.Wallet == "" {
|
if r.Wallet == "" {
|
||||||
errList = append(errList, errors.New("'wallet' is required"))
|
errList = append(errList, errors.New("'wallet' is required"))
|
||||||
@@ -25,6 +31,12 @@ func (r getBalancesByAddressRequest) Validate() error {
|
|||||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
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")
|
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,15 +48,15 @@ type balance struct {
|
|||||||
Decimals uint8 `json:"decimals"`
|
Decimals uint8 `json:"decimals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getBalancesByAddressResult struct {
|
type getBalancesResult struct {
|
||||||
List []balance `json:"list"`
|
List []balance `json:"list"`
|
||||||
BlockHeight uint64 `json:"blockHeight"`
|
BlockHeight uint64 `json:"blockHeight"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getBalancesByAddressResponse = HttpResponse[getBalancesByAddressResult]
|
type getBalancesResponse = HttpResponse[getBalancesResult]
|
||||||
|
|
||||||
func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
func (h *HttpHandler) GetBalances(ctx *fiber.Ctx) (err error) {
|
||||||
var req getBalancesByAddressRequest
|
var req getBalancesRequest
|
||||||
if err := ctx.ParamsParser(&req); err != nil {
|
if err := ctx.ParamsParser(&req); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -54,6 +66,9 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
|||||||
if err := req.Validate(); err != nil {
|
if err := req.Validate(); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
if req.Limit == 0 {
|
||||||
|
req.Limit = getBalancesDefaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
pkScript, ok := resolvePkScript(h.network, req.Wallet)
|
pkScript, ok := resolvePkScript(h.network, req.Wallet)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -64,49 +79,52 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
|||||||
if blockHeight == 0 {
|
if blockHeight == 0 {
|
||||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("latest block not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetLatestBlock")
|
return errors.Wrap(err, "error during GetLatestBlock")
|
||||||
}
|
}
|
||||||
blockHeight = uint64(blockHeader.Height)
|
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 err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("balances not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||||
}
|
}
|
||||||
|
|
||||||
runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id)
|
runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id)
|
||||||
if ok {
|
if ok {
|
||||||
// filter out balances that don't match the requested rune id
|
// filter out balances that don't match the requested rune id
|
||||||
for key := range balances {
|
balances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
|
||||||
if key != runeId {
|
return b.RuneId == runeId
|
||||||
delete(balances, key)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), balanceRuneIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceList := make([]balance, 0, len(balances))
|
balanceList := make([]balance, 0, len(balances))
|
||||||
for id, b := range balances {
|
for _, b := range balances {
|
||||||
runeEntry := runeEntries[id]
|
runeEntry := runeEntries[b.RuneId]
|
||||||
balanceList = append(balanceList, balance{
|
balanceList = append(balanceList, balance{
|
||||||
Amount: b.Amount,
|
Amount: b.Amount,
|
||||||
Id: id,
|
Id: b.RuneId,
|
||||||
Name: runeEntry.SpacedRune,
|
Name: runeEntry.SpacedRune,
|
||||||
Symbol: string(runeEntry.Symbol),
|
Symbol: string(runeEntry.Symbol),
|
||||||
Decimals: runeEntry.Divisibility,
|
Decimals: runeEntry.Divisibility,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
slices.SortFunc(balanceList, func(i, j balance) int {
|
|
||||||
return j.Amount.Cmp(i.Amount)
|
|
||||||
})
|
|
||||||
|
|
||||||
resp := getBalancesByAddressResponse{
|
resp := getBalancesResponse{
|
||||||
Result: &getBalancesByAddressResult{
|
Result: &getBalancesResult{
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
List: balanceList,
|
List: balanceList,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package httphandler
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/gaze-network/indexer-network/common/errs"
|
"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/gofiber/fiber/v2"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -16,33 +17,49 @@ type getBalanceQuery struct {
|
|||||||
Wallet string `json:"wallet"`
|
Wallet string `json:"wallet"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
BlockHeight uint64 `json:"blockHeight"`
|
BlockHeight uint64 `json:"blockHeight"`
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getBalancesByAddressBatchRequest struct {
|
type getBalancesBatchRequest struct {
|
||||||
Queries []getBalanceQuery `json:"queries"`
|
Queries []getBalanceQuery `json:"queries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r getBalancesByAddressBatchRequest) Validate() error {
|
const getBalancesBatchMaxQueries = 100
|
||||||
|
|
||||||
|
func (r getBalancesBatchRequest) Validate() error {
|
||||||
var errList []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 == "" {
|
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) {
|
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")
|
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||||
}
|
}
|
||||||
|
|
||||||
type getBalancesByAddressBatchResult struct {
|
type getBalancesBatchResult struct {
|
||||||
List []*getBalancesByAddressResult `json:"list"`
|
List []*getBalancesResult `json:"list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getBalancesByAddressBatchResponse = HttpResponse[getBalancesByAddressBatchResult]
|
type getBalancesBatchResponse = HttpResponse[getBalancesBatchResult]
|
||||||
|
|
||||||
func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
func (h *HttpHandler) GetBalancesBatch(ctx *fiber.Ctx) (err error) {
|
||||||
var req getBalancesByAddressBatchRequest
|
var req getBalancesBatchRequest
|
||||||
if err := ctx.BodyParser(&req); err != nil {
|
if err := ctx.BodyParser(&req); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -53,11 +70,14 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
|||||||
var latestBlockHeight uint64
|
var latestBlockHeight uint64
|
||||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("latest block not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetLatestBlock")
|
return errors.Wrap(err, "error during GetLatestBlock")
|
||||||
}
|
}
|
||||||
latestBlockHeight = uint64(blockHeader.Height)
|
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)
|
pkScript, ok := resolvePkScript(h.network, query.Wallet)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errs.NewPublicError(fmt.Sprintf("unable to resolve pkscript from \"queries[%d].wallet\"", queryIndex))
|
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
|
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 err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return nil, errs.NewPublicError("balances not found")
|
||||||
|
}
|
||||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||||
}
|
}
|
||||||
|
|
||||||
runeId, ok := h.resolveRuneId(ctx, query.Id)
|
runeId, ok := h.resolveRuneId(ctx, query.Id)
|
||||||
if ok {
|
if ok {
|
||||||
// filter out balances that don't match the requested rune id
|
// filter out balances that don't match the requested rune id
|
||||||
for key := range balances {
|
balances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
|
||||||
if key != runeId {
|
return b.RuneId == runeId
|
||||||
delete(balances, key)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx, balanceRuneIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return nil, errs.NewPublicError("rune not found")
|
||||||
|
}
|
||||||
return nil, errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
return nil, errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceList := make([]balance, 0, len(balances))
|
balanceList := make([]balance, 0, len(balances))
|
||||||
for id, b := range balances {
|
for _, b := range balances {
|
||||||
runeEntry := runeEntries[id]
|
runeEntry := runeEntries[b.RuneId]
|
||||||
balanceList = append(balanceList, balance{
|
balanceList = append(balanceList, balance{
|
||||||
Amount: b.Amount,
|
Amount: b.Amount,
|
||||||
Id: id,
|
Id: b.RuneId,
|
||||||
Name: runeEntry.SpacedRune,
|
Name: runeEntry.SpacedRune,
|
||||||
Symbol: string(runeEntry.Symbol),
|
Symbol: string(runeEntry.Symbol),
|
||||||
Decimals: runeEntry.Divisibility,
|
Decimals: runeEntry.Divisibility,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
slices.SortFunc(balanceList, func(i, j balance) int {
|
|
||||||
return j.Amount.Cmp(i.Amount)
|
|
||||||
})
|
|
||||||
|
|
||||||
result := getBalancesByAddressResult{
|
result := getBalancesResult{
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
List: balanceList,
|
List: balanceList,
|
||||||
}
|
}
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]*getBalancesByAddressResult, len(req.Queries))
|
results := make([]*getBalancesResult, len(req.Queries))
|
||||||
eg, ectx := errgroup.WithContext(ctx.UserContext())
|
eg, ectx := errgroup.WithContext(ctx.UserContext())
|
||||||
for i, query := range req.Queries {
|
for i, query := range req.Queries {
|
||||||
i := i
|
i := i
|
||||||
@@ -129,8 +156,8 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := getBalancesByAddressBatchResponse{
|
resp := getBalancesBatchResponse{
|
||||||
Result: &getBalancesByAddressBatchResult{
|
Result: &getBalancesBatchResult{
|
||||||
List: results,
|
List: results,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package httphandler
|
package httphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/gaze-network/indexer-network/common/errs"
|
"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/indexer-network/modules/runes/runes"
|
||||||
"github.com/gaze-network/uint128"
|
"github.com/gaze-network/uint128"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -14,13 +17,26 @@ import (
|
|||||||
type getHoldersRequest struct {
|
type getHoldersRequest struct {
|
||||||
Id string `params:"id"`
|
Id string `params:"id"`
|
||||||
BlockHeight uint64 `query:"blockHeight"`
|
BlockHeight uint64 `query:"blockHeight"`
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
getHoldersMaxLimit = 1000
|
||||||
|
getHoldersDefaultLimit = 100
|
||||||
|
)
|
||||||
|
|
||||||
func (r getHoldersRequest) Validate() error {
|
func (r getHoldersRequest) Validate() error {
|
||||||
var errList []error
|
var errList []error
|
||||||
if !isRuneIdOrRuneName(r.Id) {
|
if !isRuneIdOrRuneName(r.Id) {
|
||||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
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")
|
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)
|
blockHeight = uint64(blockHeader.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Limit == 0 {
|
||||||
|
req.Limit = getHoldersDefaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
var runeId runes.RuneId
|
var runeId runes.RuneId
|
||||||
if req.Id != "" {
|
if req.Id != "" {
|
||||||
var ok bool
|
var ok bool
|
||||||
@@ -75,10 +95,13 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
|||||||
if errors.Is(err, errs.NotFound) {
|
if errors.Is(err, errs.NotFound) {
|
||||||
return errs.NewPublicError("rune not found")
|
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 err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("balances not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetBalancesByRuneId")
|
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{
|
resp := getHoldersResponse{
|
||||||
Result: &getHoldersResult{
|
Result: &getHoldersResult{
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
|
|||||||
if blockHeight == 0 {
|
if blockHeight == 0 {
|
||||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("latest block not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetLatestBlock")
|
return errors.Wrap(err, "error during GetLatestBlock")
|
||||||
}
|
}
|
||||||
blockHeight = uint64(blockHeader.Height)
|
blockHeight = uint64(blockHeader.Height)
|
||||||
@@ -104,8 +107,11 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
|
|||||||
}
|
}
|
||||||
return errors.Wrap(err, "error during GetTokenInfoByHeight")
|
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 err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("rune not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetBalancesByRuneId")
|
return errors.Wrap(err, "error during GetBalancesByRuneId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package httphandler
|
package httphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -15,13 +16,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type getTransactionsRequest struct {
|
type getTransactionsRequest struct {
|
||||||
Wallet string `query:"wallet"`
|
Wallet string `query:"wallet"`
|
||||||
Id string `query:"id"`
|
Id string `query:"id"`
|
||||||
|
FromBlock int64 `query:"fromBlock"`
|
||||||
FromBlock int64 `query:"fromBlock"`
|
ToBlock int64 `query:"toBlock"`
|
||||||
ToBlock int64 `query:"toBlock"`
|
Limit int32 `query:"limit"`
|
||||||
|
Offset int32 `query:"offset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTransactionsMaxLimit = 3000
|
||||||
|
getTransactionsDefaultLimit = 100
|
||||||
|
)
|
||||||
|
|
||||||
func (r getTransactionsRequest) Validate() error {
|
func (r getTransactionsRequest) Validate() error {
|
||||||
var errList []error
|
var errList []error
|
||||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||||
@@ -33,6 +40,12 @@ func (r getTransactionsRequest) Validate() error {
|
|||||||
if r.ToBlock < -1 {
|
if r.ToBlock < -1 {
|
||||||
errList = append(errList, errors.Errorf("invalid toBlock range"))
|
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")
|
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\"")
|
return errs.NewPublicError("unable to resolve rune id from \"id\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if req.Limit == 0 {
|
||||||
|
req.Limit = getTransactionsDefaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
// default to latest block
|
// default to latest block
|
||||||
if req.ToBlock == 0 {
|
if req.ToBlock == 0 {
|
||||||
@@ -143,6 +159,9 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
|||||||
if req.FromBlock == -1 || req.ToBlock == -1 {
|
if req.FromBlock == -1 || req.ToBlock == -1 {
|
||||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("latest block not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetLatestBlock")
|
return errors.Wrap(err, "error during GetLatestBlock")
|
||||||
}
|
}
|
||||||
if req.FromBlock == -1 {
|
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))
|
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 err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("transactions not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetRuneTransactions")
|
return errors.Wrap(err, "error during GetRuneTransactions")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +203,9 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
|||||||
allRuneIds = lo.Uniq(allRuneIds)
|
allRuneIds = lo.Uniq(allRuneIds)
|
||||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), allRuneIds)
|
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), allRuneIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("rune entries not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,12 +304,12 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
|||||||
}
|
}
|
||||||
txList = append(txList, respTx)
|
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 {
|
slices.SortFunc(txList, func(t1, t2 transaction) int {
|
||||||
if t1.BlockHeight != t2.BlockHeight {
|
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{
|
resp := getTransactionsResponse{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package httphandler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/gaze-network/indexer-network/common/errs"
|
"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/internal/entity"
|
||||||
@@ -12,13 +11,20 @@ import (
|
|||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type getUTXOsByAddressRequest struct {
|
type getUTXOsRequest struct {
|
||||||
Wallet string `params:"wallet"`
|
Wallet string `params:"wallet"`
|
||||||
Id string `query:"id"`
|
Id string `query:"id"`
|
||||||
BlockHeight uint64 `query:"blockHeight"`
|
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
|
var errList []error
|
||||||
if r.Wallet == "" {
|
if r.Wallet == "" {
|
||||||
errList = append(errList, errors.New("'wallet' is required"))
|
errList = append(errList, errors.New("'wallet' is required"))
|
||||||
@@ -26,6 +32,12 @@ func (r getUTXOsByAddressRequest) Validate() error {
|
|||||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
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")
|
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,21 +53,21 @@ type utxoExtend struct {
|
|||||||
Runes []runeBalance `json:"runes"`
|
Runes []runeBalance `json:"runes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type utxo struct {
|
type utxoItem struct {
|
||||||
TxHash chainhash.Hash `json:"txHash"`
|
TxHash chainhash.Hash `json:"txHash"`
|
||||||
OutputIndex uint32 `json:"outputIndex"`
|
OutputIndex uint32 `json:"outputIndex"`
|
||||||
Extend utxoExtend `json:"extend"`
|
Extend utxoExtend `json:"extend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getUTXOsByAddressResult struct {
|
type getUTXOsResult struct {
|
||||||
List []utxo `json:"list"`
|
List []utxoItem `json:"list"`
|
||||||
BlockHeight uint64 `json:"blockHeight"`
|
BlockHeight uint64 `json:"blockHeight"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getUTXOsByAddressResponse = HttpResponse[getUTXOsByAddressResult]
|
type getUTXOsResponse = HttpResponse[getUTXOsResult]
|
||||||
|
|
||||||
func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
|
func (h *HttpHandler) GetUTXOs(ctx *fiber.Ctx) (err error) {
|
||||||
var req getUTXOsByAddressRequest
|
var req getUTXOsRequest
|
||||||
if err := ctx.ParamsParser(&req); err != nil {
|
if err := ctx.ParamsParser(&req); err != nil {
|
||||||
return errors.WithStack(err)
|
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\"")
|
return errs.NewPublicError("unable to resolve pkscript from \"wallet\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Limit == 0 {
|
||||||
|
req.Limit = getUTXOsDefaultLimit
|
||||||
|
}
|
||||||
|
|
||||||
blockHeight := req.BlockHeight
|
blockHeight := req.BlockHeight
|
||||||
if blockHeight == 0 {
|
if blockHeight == 0 {
|
||||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("latest block not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetLatestBlock")
|
return errors.Wrap(err, "error during GetLatestBlock")
|
||||||
}
|
}
|
||||||
blockHeight = uint64(blockHeader.Height)
|
blockHeight = uint64(blockHeader.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
outPointBalances, err := h.usecase.GetUnspentOutPointBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight)
|
var utxos []*entity.RunesUTXO
|
||||||
if err != nil {
|
if runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id); ok {
|
||||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
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 {
|
runeIds := make(map[runes.RuneId]struct{}, 0)
|
||||||
return outPointBalance.RuneId
|
for _, utxo := range utxos {
|
||||||
})
|
for _, balance := range utxo.RuneBalances {
|
||||||
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), outPointBalanceRuneIds)
|
runeIds[balance.RuneId] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runeIdsList := lo.Keys(runeIds)
|
||||||
|
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), runeIdsList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, errs.NotFound) {
|
||||||
|
return errs.NewPublicError("rune entries not found")
|
||||||
|
}
|
||||||
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
groupedBalances := lo.GroupBy(outPointBalances, func(outPointBalance *entity.OutPointBalance) wire.OutPoint {
|
utxoRespList := make([]utxoItem, 0, len(utxos))
|
||||||
return outPointBalance.OutPoint
|
for _, utxo := range utxos {
|
||||||
})
|
runeBalances := make([]runeBalance, 0, len(utxo.RuneBalances))
|
||||||
|
for _, balance := range utxo.RuneBalances {
|
||||||
utxoList := make([]utxo, 0, len(groupedBalances))
|
|
||||||
for outPoint, balances := range groupedBalances {
|
|
||||||
runeBalances := make([]runeBalance, 0, len(balances))
|
|
||||||
for _, balance := range balances {
|
|
||||||
runeEntry := runeEntries[balance.RuneId]
|
runeEntry := runeEntries[balance.RuneId]
|
||||||
runeBalances = append(runeBalances, runeBalance{
|
runeBalances = append(runeBalances, runeBalance{
|
||||||
RuneId: balance.RuneId,
|
RuneId: balance.RuneId,
|
||||||
@@ -111,34 +147,19 @@ func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
utxoList = append(utxoList, utxo{
|
utxoRespList = append(utxoRespList, utxoItem{
|
||||||
TxHash: outPoint.Hash,
|
TxHash: utxo.OutPoint.Hash,
|
||||||
OutputIndex: outPoint.Index,
|
OutputIndex: utxo.OutPoint.Index,
|
||||||
Extend: utxoExtend{
|
Extend: utxoExtend{
|
||||||
Runes: runeBalances,
|
Runes: runeBalances,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter by req.Id if exists
|
resp := getUTXOsResponse{
|
||||||
{
|
Result: &getUTXOsResult{
|
||||||
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{
|
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
List: utxoList,
|
List: utxoRespList,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
func (h *HttpHandler) Mount(router fiber.Router) error {
|
func (h *HttpHandler) Mount(router fiber.Router) error {
|
||||||
r := router.Group("/v2/runes")
|
r := router.Group("/v2/runes")
|
||||||
|
|
||||||
r.Post("/balances/wallet/batch", h.GetBalancesByAddressBatch)
|
r.Post("/balances/wallet/batch", h.GetBalancesBatch)
|
||||||
r.Get("/balances/wallet/:wallet", h.GetBalancesByAddress)
|
r.Get("/balances/wallet/:wallet", h.GetBalances)
|
||||||
r.Get("/transactions", h.GetTransactions)
|
r.Get("/transactions", h.GetTransactions)
|
||||||
r.Get("/holders/:id", h.GetHolders)
|
r.Get("/holders/:id", h.GetHolders)
|
||||||
r.Get("/info/:id", h.GetTokenInfo)
|
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)
|
r.Get("/block", h.GetCurrentBlock)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,5 +118,7 @@ CREATE TABLE IF NOT EXISTS "runes_balances" (
|
|||||||
"amount" DECIMAL NOT NULL,
|
"amount" DECIMAL NOT NULL,
|
||||||
PRIMARY KEY ("pkscript", "rune_id", "block_height")
|
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;
|
COMMIT;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
WITH balances AS (
|
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 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
|
-- name: GetBalancesByRuneId :many
|
||||||
WITH balances AS (
|
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 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
|
-- 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;
|
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
|
-- name: GetOutPointBalancesAtOutPoint :many
|
||||||
SELECT * FROM runes_outpoint_balances WHERE tx_hash = $1 AND tx_idx = $2;
|
SELECT * FROM runes_outpoint_balances WHERE tx_hash = $1 AND tx_idx = $2;
|
||||||
|
|
||||||
-- name: GetUnspentOutPointBalancesByPkScript :many
|
-- name: GetRunesUTXOsByPkScript :many
|
||||||
SELECT * FROM runes_outpoint_balances WHERE pkscript = @pkScript AND block_height <= @block_height AND (spent_height IS NULL OR spent_height > @block_height);
|
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
|
-- name: GetRuneEntriesByRuneIds :many
|
||||||
WITH states AS (
|
WITH states AS (
|
||||||
@@ -57,7 +77,7 @@ SELECT * FROM runes_transactions
|
|||||||
) AND (
|
) AND (
|
||||||
@from_block <= runes_transactions.block_height AND runes_transactions.block_height <= @to_block
|
@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
|
-- name: CountRuneEntries :one
|
||||||
SELECT COUNT(*) FROM runes_entries;
|
SELECT COUNT(*) FROM runes_entries;
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ type RunesReaderDataGateway interface {
|
|||||||
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
|
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
|
||||||
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, 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 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)
|
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 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)
|
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.
|
// 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)
|
CountRuneEntries(ctx context.Context) (uint64, error)
|
||||||
|
|
||||||
// GetBalancesByPkScript returns the balances for the given pkScript at the given blockHeight.
|
// 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.
|
// GetBalancesByRuneId returns the balances for the given runeId at the given blockHeight.
|
||||||
// Cannot use []byte as map key, so we're returning as slice.
|
// 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.
|
// 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)
|
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 (
|
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 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 {
|
type GetBalancesByPkScriptParams struct {
|
||||||
Pkscript string
|
Pkscript string
|
||||||
BlockHeight int32
|
BlockHeight int32
|
||||||
|
Limit int32
|
||||||
|
Offset int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBalancesByPkScriptRow struct {
|
type GetBalancesByPkScriptRow struct {
|
||||||
@@ -312,7 +314,12 @@ type GetBalancesByPkScriptRow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetBalancesByPkScript(ctx context.Context, arg GetBalancesByPkScriptParams) ([]GetBalancesByPkScriptRow, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -340,12 +347,14 @@ const getBalancesByRuneId = `-- name: GetBalancesByRuneId :many
|
|||||||
WITH balances AS (
|
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 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 {
|
type GetBalancesByRuneIdParams struct {
|
||||||
RuneID string
|
RuneID string
|
||||||
BlockHeight int32
|
BlockHeight int32
|
||||||
|
Limit int32
|
||||||
|
Offset int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBalancesByRuneIdRow struct {
|
type GetBalancesByRuneIdRow struct {
|
||||||
@@ -356,7 +365,12 @@ type GetBalancesByRuneIdRow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetBalancesByRuneId(ctx context.Context, arg GetBalancesByRuneIdParams) ([]GetBalancesByRuneIdRow, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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
|
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||||
WHERE (
|
WHERE (
|
||||||
$1::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
$3::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
|
|
||||||
OR runes_transactions.outputs @> $4::JSONB
|
OR runes_transactions.outputs @> $4::JSONB
|
||||||
OR runes_transactions.inputs @> $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)
|
|
||||||
) AND (
|
) 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 {
|
type GetRuneTransactionsParams struct {
|
||||||
|
Limit int32
|
||||||
|
Offset int32
|
||||||
FilterPkScript bool
|
FilterPkScript bool
|
||||||
PkScriptParam []byte
|
PkScriptParam []byte
|
||||||
FilterRuneID bool
|
FilterRuneID bool
|
||||||
@@ -698,6 +714,8 @@ type GetRuneTransactionsRow struct {
|
|||||||
|
|
||||||
func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactionsParams) ([]GetRuneTransactionsRow, error) {
|
func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactionsParams) ([]GetRuneTransactionsRow, error) {
|
||||||
rows, err := q.db.Query(ctx, getRuneTransactions,
|
rows, err := q.db.Query(ctx, getRuneTransactions,
|
||||||
|
arg.Limit,
|
||||||
|
arg.Offset,
|
||||||
arg.FilterPkScript,
|
arg.FilterPkScript,
|
||||||
arg.PkScriptParam,
|
arg.PkScriptParam,
|
||||||
arg.FilterRuneID,
|
arg.FilterRuneID,
|
||||||
@@ -757,32 +775,114 @@ func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactio
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUnspentOutPointBalancesByPkScript = `-- name: GetUnspentOutPointBalancesByPkScript :many
|
const getRunesUTXOsByPkScript = `-- name: GetRunesUTXOsByPkScript :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)
|
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
|
Pkscript string
|
||||||
BlockHeight int32
|
BlockHeight int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUnspentOutPointBalancesByPkScript(ctx context.Context, arg GetUnspentOutPointBalancesByPkScriptParams) ([]RunesOutpointBalance, error) {
|
type GetRunesUTXOsByPkScriptRow struct {
|
||||||
rows, err := q.db.Query(ctx, getUnspentOutPointBalancesByPkScript, arg.Pkscript, arg.BlockHeight)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []RunesOutpointBalance
|
var items []GetRunesUTXOsByPkScriptRow
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i RunesOutpointBalance
|
var i GetRunesUTXOsByPkScriptRow
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.RuneID,
|
|
||||||
&i.Pkscript,
|
|
||||||
&i.TxHash,
|
&i.TxHash,
|
||||||
&i.TxIdx,
|
&i.TxIdx,
|
||||||
&i.Amount,
|
&i.Pkscript,
|
||||||
&i.BlockHeight,
|
&i.RuneIds,
|
||||||
&i.SpentHeight,
|
&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 {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -638,6 +638,72 @@ func mapIndexedBlockTypeToParams(src entity.IndexedBlock) (gen.CreateIndexedBloc
|
|||||||
}, nil
|
}, 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) {
|
func mapOutPointBalanceModelToType(src gen.RunesOutpointBalance) (entity.OutPointBalance, error) {
|
||||||
runeId, err := runes.NewRuneIdFromString(src.RuneID)
|
runeId, err := runes.NewRuneIdFromString(src.RuneID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
@@ -62,7 +63,18 @@ func (r *Repository) GetIndexedBlockByHeight(ctx context.Context, height int64)
|
|||||||
return indexedBlock, nil
|
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)))
|
pkScriptParam := []byte(fmt.Sprintf(`[{"pkScript":"%s"}]`, hex.EncodeToString(pkScript)))
|
||||||
runeIdParam := []byte(fmt.Sprintf(`[{"runeId":"%s"}]`, runeId.String()))
|
runeIdParam := []byte(fmt.Sprintf(`[{"runeId":"%s"}]`, runeId.String()))
|
||||||
rows, err := r.queries.GetRuneTransactions(ctx, gen.GetRuneTransactionsParams{
|
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),
|
FromBlock: int32(fromBlock),
|
||||||
ToBlock: int32(toBlock),
|
ToBlock: int32(toBlock),
|
||||||
|
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error during query")
|
return nil, errors.Wrap(err, "error during query")
|
||||||
@@ -125,22 +140,59 @@ func (r *Repository) GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wi
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error) {
|
func (r *Repository) GetRunesUTXOsByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64, limit int32, offset int32) ([]*entity.RunesUTXO, error) {
|
||||||
balances, err := r.queries.GetUnspentOutPointBalancesByPkScript(ctx, gen.GetUnspentOutPointBalancesByPkScriptParams{
|
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),
|
Pkscript: hex.EncodeToString(pkScript),
|
||||||
BlockHeight: int32(blockHeight),
|
BlockHeight: int32(blockHeight),
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error during query")
|
return nil, errors.Wrap(err, "error during query")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]*entity.OutPointBalance, 0, len(balances))
|
result := make([]*entity.RunesUTXO, 0, len(rows))
|
||||||
for _, balanceModel := range balances {
|
for _, row := range rows {
|
||||||
balance, err := mapOutPointBalanceModelToType(balanceModel)
|
utxo, err := mapRunesUTXOModelToType(row)
|
||||||
if err != nil {
|
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
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -245,30 +297,46 @@ func (r *Repository) CountRuneEntries(ctx context.Context) (uint64, error) {
|
|||||||
return uint64(count), nil
|
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{
|
balances, err := r.queries.GetBalancesByPkScript(ctx, gen.GetBalancesByPkScriptParams{
|
||||||
Pkscript: hex.EncodeToString(pkScript),
|
Pkscript: hex.EncodeToString(pkScript),
|
||||||
BlockHeight: int32(blockHeight),
|
BlockHeight: int32(blockHeight),
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error during query")
|
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 {
|
for _, balanceModel := range balances {
|
||||||
balance, err := mapBalanceModelToType(gen.RunesBalance(balanceModel))
|
balance, err := mapBalanceModelToType(gen.RunesBalance(balanceModel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse balance model")
|
return nil, errors.Wrap(err, "failed to parse balance model")
|
||||||
}
|
}
|
||||||
result[balance.RuneId] = balance
|
result = append(result, balance)
|
||||||
}
|
}
|
||||||
return result, nil
|
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{
|
balances, err := r.queries.GetBalancesByRuneId(ctx, gen.GetBalancesByRuneIdParams{
|
||||||
RuneID: runeId.String(),
|
RuneID: runeId.String(),
|
||||||
BlockHeight: int32(blockHeight),
|
BlockHeight: int32(blockHeight),
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error during query")
|
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) {
|
func NewRuneFromString(value string) (Rune, error) {
|
||||||
n := uint128.From64(0)
|
n := uint128.From64(0)
|
||||||
for i, char := range value {
|
for i, char := range value {
|
||||||
|
// skip spacers
|
||||||
|
if char == '.' || char == '•' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
n = n.Add(uint128.From64(1))
|
n = n.Add(uint128.From64(1))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ import (
|
|||||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
"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) {
|
// Use limit = -1 as no limit.
|
||||||
balances, err := u.runesDg.GetBalancesByPkScript(ctx, pkScript, blockHeight)
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||||
}
|
}
|
||||||
return balances, nil
|
return balances, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Usecase) GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64) ([]*entity.Balance, error) {
|
// Use limit = -1 as no limit.
|
||||||
balances, err := u.runesDg.GetBalancesByRuneId(ctx, runeId, blockHeight)
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get rune holders by rune id")
|
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"
|
"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) {
|
// Use limit = -1 as no limit.
|
||||||
txs, err := u.runesDg.GetRuneTransactions(ctx, pkScript, runeId, fromBlock, toBlock)
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error during GetTransactionsByHeight")
|
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