mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 08:34:28 +08:00
feat(runes): add Get Tokens API (#38)
* 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 * feat(runes): add get token list api * fix(runes): use distinct to get token list * feat: remove unused code * fix: count holders distinct pkscript * feat: implement additional scopes * chore: comments * feat: implement search * refactor: switch to use paginationRequest * refactor: rename get token list to get tokens * fix: count total holders by rune ids * fix: rename file * fix: rename minting to ongoing * fix: get ongoing check rune is mintable * chore: disable gosec g115 * fix: pr --------- Co-authored-by: Gaze <gazenw@users.noreply.github.com>
This commit is contained in:
@@ -101,3 +101,6 @@ linters-settings:
|
||||
attr-only: true
|
||||
key-naming-case: snake
|
||||
args-on-sep-lines: true
|
||||
gosec:
|
||||
excludes:
|
||||
- G115
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: blocks.sql
|
||||
|
||||
package gen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package gen
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: events.sql
|
||||
|
||||
package gen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package gen
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: nodes.sql
|
||||
|
||||
package gen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: nodesales.sql
|
||||
|
||||
package gen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: test.sql
|
||||
|
||||
package gen
|
||||
|
||||
@@ -11,11 +11,10 @@ import (
|
||||
)
|
||||
|
||||
type getBalancesRequest struct {
|
||||
paginationRequest
|
||||
Wallet string `params:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Limit int32 `query:"limit"`
|
||||
Offset int32 `query:"offset"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -66,8 +65,8 @@ func (h *HttpHandler) GetBalances(ctx *fiber.Ctx) (err error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if req.Limit == 0 {
|
||||
req.Limit = getBalancesDefaultLimit
|
||||
if err := req.ParseDefault(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pkScript, ok := resolvePkScript(h.network, req.Wallet)
|
||||
|
||||
@@ -89,7 +89,7 @@ func (h *HttpHandler) GetBalancesBatch(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
|
||||
if query.Limit == 0 {
|
||||
query.Limit = getBalancesMaxLimit
|
||||
query.Limit = getBalancesDefaultLimit
|
||||
}
|
||||
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight, query.Limit, query.Offset)
|
||||
|
||||
@@ -15,15 +15,13 @@ import (
|
||||
)
|
||||
|
||||
type getHoldersRequest struct {
|
||||
paginationRequest
|
||||
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 {
|
||||
@@ -68,6 +66,9 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.ParseDefault(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
@@ -78,10 +79,6 @@ 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
|
||||
|
||||
@@ -57,9 +57,9 @@ type getTokenInfoResult struct {
|
||||
MintedAmount uint128.Uint128 `json:"mintedAmount"`
|
||||
BurnedAmount uint128.Uint128 `json:"burnedAmount"`
|
||||
Decimals uint8 `json:"decimals"`
|
||||
DeployedAt uint64 `json:"deployedAt"` // unix timestamp
|
||||
DeployedAt int64 `json:"deployedAt"` // unix timestamp
|
||||
DeployedAtHeight uint64 `json:"deployedAtHeight"`
|
||||
CompletedAt *uint64 `json:"completedAt"` // unix timestamp
|
||||
CompletedAt *int64 `json:"completedAt"` // unix timestamp
|
||||
CompletedAtHeight *uint64 `json:"completedAtHeight"`
|
||||
HoldersCount int `json:"holdersCount"`
|
||||
Extend tokenInfoExtend `json:"extend"`
|
||||
@@ -144,9 +144,9 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
|
||||
MintedAmount: mintedAmount,
|
||||
BurnedAmount: runeEntry.BurnedAmount,
|
||||
Decimals: runeEntry.Divisibility,
|
||||
DeployedAt: uint64(runeEntry.EtchedAt.Unix()),
|
||||
DeployedAt: runeEntry.EtchedAt.Unix(),
|
||||
DeployedAtHeight: runeEntry.EtchingBlock,
|
||||
CompletedAt: lo.Ternary(runeEntry.CompletedAt.IsZero(), nil, lo.ToPtr(uint64(runeEntry.CompletedAt.Unix()))),
|
||||
CompletedAt: lo.Ternary(runeEntry.CompletedAt.IsZero(), nil, lo.ToPtr(runeEntry.CompletedAt.Unix())),
|
||||
CompletedAtHeight: runeEntry.CompletedAtHeight,
|
||||
HoldersCount: len(holdingBalances),
|
||||
Extend: tokenInfoExtend{
|
||||
|
||||
172
modules/runes/api/httphandler/get_tokens.go
Normal file
172
modules/runes/api/httphandler/get_tokens.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
getTokensMaxLimit = 1000
|
||||
)
|
||||
|
||||
type GetTokensScope string
|
||||
|
||||
const (
|
||||
GetTokensScopeAll GetTokensScope = "all"
|
||||
GetTokensScopeOngoing GetTokensScope = "ongoing"
|
||||
)
|
||||
|
||||
func (s GetTokensScope) IsValid() bool {
|
||||
switch s {
|
||||
case GetTokensScopeAll, GetTokensScopeOngoing:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type getTokensRequest struct {
|
||||
paginationRequest
|
||||
Search string `query:"search"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Scope GetTokensScope `query:"scope"`
|
||||
}
|
||||
|
||||
func (req getTokensRequest) Validate() error {
|
||||
var errList []error
|
||||
if err := req.paginationRequest.Validate(); err != nil {
|
||||
errList = append(errList, err)
|
||||
}
|
||||
if req.Limit > getTokensMaxLimit {
|
||||
errList = append(errList, errors.Errorf("limit must be less than or equal to 1000"))
|
||||
}
|
||||
if req.Scope != "" && !req.Scope.IsValid() {
|
||||
errList = append(errList, errors.Errorf("invalid scope: %s", req.Scope))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
func (req *getTokensRequest) ParseDefault() error {
|
||||
if err := req.paginationRequest.ParseDefault(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if req.Scope == "" {
|
||||
req.Scope = GetTokensScopeAll
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type getTokensResult struct {
|
||||
List []getTokenInfoResult `json:"list"`
|
||||
}
|
||||
|
||||
type getTokensResponse = HttpResponse[getTokensResult]
|
||||
|
||||
func (h *HttpHandler) GetTokens(ctx *fiber.Ctx) (err error) {
|
||||
var req getTokensRequest
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.ParseDefault(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// remove spacers
|
||||
search := strings.Replace(strings.Replace(req.Search, "•", "", -1), ".", "", -1)
|
||||
|
||||
var entries []*runes.RuneEntry
|
||||
switch req.Scope {
|
||||
case GetTokensScopeAll:
|
||||
entries, err = h.usecase.GetRuneEntries(ctx.UserContext(), search, blockHeight, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetRuneEntryList")
|
||||
}
|
||||
case GetTokensScopeOngoing:
|
||||
entries, err = h.usecase.GetOngoingRuneEntries(ctx.UserContext(), search, blockHeight, req.Limit, req.Offset)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetRuneEntryList")
|
||||
}
|
||||
default:
|
||||
return errs.NewPublicError(fmt.Sprintf("invalid scope: %s", req.Scope))
|
||||
}
|
||||
|
||||
runeIds := lo.Map(entries, func(item *runes.RuneEntry, _ int) runes.RuneId { return item.RuneId })
|
||||
totalHolders, err := h.usecase.GetTotalHoldersByRuneIds(ctx.UserContext(), runeIds, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTotalHoldersByRuneIds")
|
||||
}
|
||||
|
||||
result := make([]getTokenInfoResult, 0, len(entries))
|
||||
for _, ent := range entries {
|
||||
totalSupply, err := ent.Supply()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot get total supply of rune")
|
||||
}
|
||||
mintedAmount, err := ent.MintedAmount()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot get minted amount of rune")
|
||||
}
|
||||
circulatingSupply := mintedAmount.Sub(ent.BurnedAmount)
|
||||
|
||||
terms := lo.FromPtr(ent.Terms)
|
||||
result = append(result, getTokenInfoResult{
|
||||
Id: ent.RuneId,
|
||||
Name: ent.SpacedRune,
|
||||
Symbol: string(ent.Symbol),
|
||||
TotalSupply: totalSupply,
|
||||
CirculatingSupply: circulatingSupply,
|
||||
MintedAmount: mintedAmount,
|
||||
BurnedAmount: ent.BurnedAmount,
|
||||
Decimals: ent.Divisibility,
|
||||
DeployedAt: ent.EtchedAt.Unix(),
|
||||
DeployedAtHeight: ent.EtchingBlock,
|
||||
CompletedAt: lo.Ternary(ent.CompletedAt.IsZero(), nil, lo.ToPtr(ent.CompletedAt.Unix())),
|
||||
CompletedAtHeight: ent.CompletedAtHeight,
|
||||
HoldersCount: int(totalHolders[ent.RuneId]),
|
||||
Extend: tokenInfoExtend{
|
||||
Entry: entry{
|
||||
Divisibility: ent.Divisibility,
|
||||
Premine: ent.Premine,
|
||||
Rune: ent.SpacedRune.Rune,
|
||||
Spacers: ent.SpacedRune.Spacers,
|
||||
Symbol: string(ent.Symbol),
|
||||
Terms: entryTerms{
|
||||
Amount: lo.FromPtr(terms.Amount),
|
||||
Cap: lo.FromPtr(terms.Cap),
|
||||
HeightStart: terms.HeightStart,
|
||||
HeightEnd: terms.HeightEnd,
|
||||
OffsetStart: terms.OffsetStart,
|
||||
OffsetEnd: terms.OffsetEnd,
|
||||
},
|
||||
Turbo: ent.Turbo,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(getTokensResponse{
|
||||
Result: &getTokensResult{
|
||||
List: result,
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -16,17 +16,15 @@ import (
|
||||
)
|
||||
|
||||
type getTransactionsRequest struct {
|
||||
paginationRequest
|
||||
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 {
|
||||
@@ -128,6 +126,9 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.ParseDefault(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var pkScript []byte
|
||||
if req.Wallet != "" {
|
||||
@@ -146,9 +147,6 @@ 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 {
|
||||
|
||||
@@ -12,16 +12,14 @@ import (
|
||||
)
|
||||
|
||||
type getUTXOsRequest struct {
|
||||
paginationRequest
|
||||
Wallet string `params:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Limit int32 `query:"limit"`
|
||||
Offset int32 `query:"offset"`
|
||||
}
|
||||
|
||||
const (
|
||||
getUTXOsMaxLimit = 3000
|
||||
getUTXOsDefaultLimit = 100
|
||||
)
|
||||
|
||||
func (r getUTXOsRequest) Validate() error {
|
||||
@@ -78,16 +76,15 @@ func (h *HttpHandler) GetUTXOs(ctx *fiber.Ctx) (err error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.ParseDefault(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pkScript, ok := resolvePkScript(h.network, req.Wallet)
|
||||
if !ok {
|
||||
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())
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/usecase"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
@@ -31,6 +33,53 @@ type HttpResponse[T any] struct {
|
||||
Result *T `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
type paginationRequest struct {
|
||||
Offset int32 `query:"offset"`
|
||||
Limit int32 `query:"limit"`
|
||||
|
||||
// OrderBy string `query:"orderBy"` // ASC or DESC
|
||||
// SortBy string `query:"sortBy"` // column name
|
||||
}
|
||||
|
||||
func (req paginationRequest) Validate() error {
|
||||
var errList []error
|
||||
|
||||
// this just safeguard for limit,
|
||||
// each path should have own validation.
|
||||
if req.Limit > 10000 {
|
||||
errList = append(errList, errors.Errorf("too large limit"))
|
||||
}
|
||||
if req.Limit < 0 {
|
||||
errList = append(errList, errors.Errorf("limit must be greater than or equal to 0"))
|
||||
}
|
||||
if req.Offset < 0 {
|
||||
errList = append(errList, errors.Errorf("offset must be greater than or equal to 0"))
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// if req.OrderBy != "" && req.OrderBy != "ASC" && req.OrderBy != "DESC" {
|
||||
// errList = append(errList, errors.Errorf("invalid orderBy value, must be `ASC` or `DESC`"))
|
||||
// }
|
||||
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "pagination validation error")
|
||||
}
|
||||
|
||||
func (req *paginationRequest) ParseDefault() error {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// if req.OrderBy == "" {
|
||||
// req.OrderBy = "ASC"
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolvePkScript(network common.Network, wallet string) ([]byte, bool) {
|
||||
if wallet == "" {
|
||||
return nil, false
|
||||
|
||||
@@ -16,5 +16,6 @@ func (h *HttpHandler) Mount(router fiber.Router) error {
|
||||
r.Post("/utxos/output/batch", h.GetUTXOsOutputByLocationBatch)
|
||||
r.Get("/utxos/output/:txHash", h.GetUTXOsOutputByLocation)
|
||||
r.Get("/block", h.GetCurrentBlock)
|
||||
r.Get("/tokens", h.GetTokens)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE EXTENSION pg_trgm;
|
||||
-- Indexer Client Information
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "runes_indexer_stats" (
|
||||
@@ -48,6 +49,7 @@ CREATE TABLE IF NOT EXISTS "runes_entries" (
|
||||
"etched_at" TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS runes_entries_rune_idx ON "runes_entries" USING BTREE ("rune");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS runes_entries_rune_gin_idx ON "runes_entries" USING GIN ("rune" gin_trgm_ops); -- to speed up queries with LIKE operator
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS runes_entries_number_idx ON "runes_entries" USING BTREE ("number");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "runes_entry_states" (
|
||||
|
||||
@@ -13,6 +13,12 @@ SELECT * FROM balances WHERE amount > 0 ORDER BY amount DESC, pkscript LIMIT $3
|
||||
-- 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;
|
||||
|
||||
-- name: GetTotalHoldersByRuneIds :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (rune_id, pkscript) * FROM runes_balances WHERE rune_id = ANY(@rune_ids::TEXT[]) AND block_height <= @block_height ORDER BY rune_id, pkscript, block_height DESC
|
||||
)
|
||||
SELECT rune_id, COUNT(DISTINCT pkscript) FROM balances WHERE amount > 0 GROUP BY rune_id;
|
||||
|
||||
-- name: GetOutPointBalancesAtOutPoint :many
|
||||
SELECT * FROM runes_outpoint_balances WHERE tx_hash = $1 AND tx_idx = $2;
|
||||
|
||||
@@ -57,6 +63,47 @@ SELECT * FROM runes_entries
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE runes_entries.rune_id = ANY(@rune_ids::text[]) AND etching_block <= @height;
|
||||
|
||||
-- name: GetRuneEntries :many
|
||||
WITH states AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON (rune_id) * FROM runes_entry_states WHERE block_height <= @height ORDER BY rune_id, block_height DESC
|
||||
)
|
||||
SELECT * FROM runes_entries
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE (
|
||||
@search = '' OR
|
||||
runes_entries.rune ILIKE @search || '%'
|
||||
)
|
||||
ORDER BY runes_entries.number
|
||||
LIMIT @_limit OFFSET @_offset;
|
||||
|
||||
-- name: GetOngoingRuneEntries :many
|
||||
WITH states AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON (rune_id) * FROM runes_entry_states WHERE block_height <= @height::integer ORDER BY rune_id, block_height DESC
|
||||
)
|
||||
SELECT * FROM runes_entries
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE (
|
||||
runes_entries.terms = TRUE AND
|
||||
states.mints < runes_entries.terms_cap AND
|
||||
(
|
||||
runes_entries.terms_height_start IS NULL OR runes_entries.terms_height_start <= @height::integer
|
||||
) AND (
|
||||
runes_entries.terms_height_end IS NULL OR @height::integer <= runes_entries.terms_height_end
|
||||
) AND (
|
||||
runes_entries.terms_offset_start IS NULL OR runes_entries.terms_offset_start + runes_entries.etching_block <= @height::integer
|
||||
) AND (
|
||||
runes_entries.terms_offset_end IS NULL OR @height::integer <= runes_entries.terms_offset_start + runes_entries.etching_block
|
||||
)
|
||||
|
||||
) AND (
|
||||
@search::text = '' OR
|
||||
runes_entries.rune ILIKE @search::text || '%'
|
||||
)
|
||||
ORDER BY (states.mints / runes_entries.terms_cap::float) DESC
|
||||
LIMIT @_limit OFFSET @_offset;
|
||||
|
||||
-- name: GetRuneIdFromRune :one
|
||||
SELECT rune_id FROM runes_entries WHERE rune = $1;
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ type RunesReaderDataGateway interface {
|
||||
GetRuneEntryByRuneIdAndHeight(ctx context.Context, runeId runes.RuneId, blockHeight uint64) (*runes.RuneEntry, error)
|
||||
// GetRuneEntryByRuneIdAndHeightBatch returns the RuneEntries for the given runeIds and block height.
|
||||
GetRuneEntryByRuneIdAndHeightBatch(ctx context.Context, runeIds []runes.RuneId, blockHeight uint64) (map[runes.RuneId]*runes.RuneEntry, error)
|
||||
// GetRuneEntries returns a list of rune entries, sorted by etching order. If search is not empty, it will filter the results by rune name (prefix).
|
||||
GetRuneEntries(ctx context.Context, search string, blockHeight uint64, limit int32, offset int32) ([]*runes.RuneEntry, error)
|
||||
// GetOngoingRuneEntries returns a list of ongoing rune entries (can still mint), sorted by mint progress percent. If search is not empty, it will filter the results by rune name (prefix).
|
||||
GetOngoingRuneEntries(ctx context.Context, search string, blockHeight uint64, limit int32, offset int32) ([]*runes.RuneEntry, error)
|
||||
// CountRuneEntries returns the number of existing rune entries.
|
||||
CountRuneEntries(ctx context.Context) (uint64, error)
|
||||
|
||||
@@ -56,6 +60,8 @@ type RunesReaderDataGateway interface {
|
||||
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)
|
||||
// GetTotalHoldersByRuneIds returns the total holders of each the given runeIds.
|
||||
GetTotalHoldersByRuneIds(ctx context.Context, runeIds []runes.RuneId, blockHeight uint64) (map[runes.RuneId]int64, error)
|
||||
}
|
||||
|
||||
type RunesWriterDataGateway interface {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: batch.go
|
||||
|
||||
package gen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: data.sql
|
||||
|
||||
package gen
|
||||
@@ -428,6 +428,118 @@ func (q *Queries) GetLatestIndexedBlock(ctx context.Context) (RunesIndexedBlock,
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOngoingRuneEntries = `-- name: GetOngoingRuneEntries :many
|
||||
WITH states AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON (rune_id) rune_id, block_height, mints, burned_amount, completed_at, completed_at_height FROM runes_entry_states WHERE block_height <= $1::integer ORDER BY rune_id, block_height DESC
|
||||
)
|
||||
SELECT runes_entries.rune_id, number, rune, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, turbo, etching_block, etching_tx_hash, etched_at, states.rune_id, block_height, mints, burned_amount, completed_at, completed_at_height FROM runes_entries
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE (
|
||||
runes_entries.terms = TRUE AND
|
||||
states.mints < runes_entries.terms_cap AND
|
||||
(
|
||||
runes_entries.terms_height_start IS NULL OR runes_entries.terms_height_start <= $1::integer
|
||||
) AND (
|
||||
runes_entries.terms_height_end IS NULL OR $1::integer <= runes_entries.terms_height_end
|
||||
) AND (
|
||||
runes_entries.terms_offset_start IS NULL OR runes_entries.terms_offset_start + runes_entries.etching_block <= $1::integer
|
||||
) AND (
|
||||
runes_entries.terms_offset_end IS NULL OR $1::integer <= runes_entries.terms_offset_start + runes_entries.etching_block
|
||||
)
|
||||
|
||||
) AND (
|
||||
$2::text = '' OR
|
||||
runes_entries.rune ILIKE $2::text || '%'
|
||||
)
|
||||
ORDER BY (states.mints / runes_entries.terms_cap::float) DESC
|
||||
LIMIT $4 OFFSET $3
|
||||
`
|
||||
|
||||
type GetOngoingRuneEntriesParams struct {
|
||||
Height int32
|
||||
Search string
|
||||
Offset int32
|
||||
Limit int32
|
||||
}
|
||||
|
||||
type GetOngoingRuneEntriesRow struct {
|
||||
RuneID string
|
||||
Number int64
|
||||
Rune string
|
||||
Spacers int32
|
||||
Premine pgtype.Numeric
|
||||
Symbol int32
|
||||
Divisibility int16
|
||||
Terms bool
|
||||
TermsAmount pgtype.Numeric
|
||||
TermsCap pgtype.Numeric
|
||||
TermsHeightStart pgtype.Int4
|
||||
TermsHeightEnd pgtype.Int4
|
||||
TermsOffsetStart pgtype.Int4
|
||||
TermsOffsetEnd pgtype.Int4
|
||||
Turbo bool
|
||||
EtchingBlock int32
|
||||
EtchingTxHash string
|
||||
EtchedAt pgtype.Timestamp
|
||||
RuneID_2 pgtype.Text
|
||||
BlockHeight pgtype.Int4
|
||||
Mints pgtype.Numeric
|
||||
BurnedAmount pgtype.Numeric
|
||||
CompletedAt pgtype.Timestamp
|
||||
CompletedAtHeight pgtype.Int4
|
||||
}
|
||||
|
||||
func (q *Queries) GetOngoingRuneEntries(ctx context.Context, arg GetOngoingRuneEntriesParams) ([]GetOngoingRuneEntriesRow, error) {
|
||||
rows, err := q.db.Query(ctx, getOngoingRuneEntries,
|
||||
arg.Height,
|
||||
arg.Search,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetOngoingRuneEntriesRow
|
||||
for rows.Next() {
|
||||
var i GetOngoingRuneEntriesRow
|
||||
if err := rows.Scan(
|
||||
&i.RuneID,
|
||||
&i.Number,
|
||||
&i.Rune,
|
||||
&i.Spacers,
|
||||
&i.Premine,
|
||||
&i.Symbol,
|
||||
&i.Divisibility,
|
||||
&i.Terms,
|
||||
&i.TermsAmount,
|
||||
&i.TermsCap,
|
||||
&i.TermsHeightStart,
|
||||
&i.TermsHeightEnd,
|
||||
&i.TermsOffsetStart,
|
||||
&i.TermsOffsetEnd,
|
||||
&i.Turbo,
|
||||
&i.EtchingBlock,
|
||||
&i.EtchingTxHash,
|
||||
&i.EtchedAt,
|
||||
&i.RuneID_2,
|
||||
&i.BlockHeight,
|
||||
&i.Mints,
|
||||
&i.BurnedAmount,
|
||||
&i.CompletedAt,
|
||||
&i.CompletedAtHeight,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getOutPointBalancesAtOutPoint = `-- name: GetOutPointBalancesAtOutPoint :many
|
||||
SELECT rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height FROM runes_outpoint_balances WHERE tx_hash = $1 AND tx_idx = $2
|
||||
`
|
||||
@@ -465,6 +577,105 @@ func (q *Queries) GetOutPointBalancesAtOutPoint(ctx context.Context, arg GetOutP
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRuneEntries = `-- name: GetRuneEntries :many
|
||||
WITH states AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON (rune_id) rune_id, block_height, mints, burned_amount, completed_at, completed_at_height FROM runes_entry_states WHERE block_height <= $4 ORDER BY rune_id, block_height DESC
|
||||
)
|
||||
SELECT runes_entries.rune_id, number, rune, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, turbo, etching_block, etching_tx_hash, etched_at, states.rune_id, block_height, mints, burned_amount, completed_at, completed_at_height FROM runes_entries
|
||||
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
|
||||
WHERE (
|
||||
$1 = '' OR
|
||||
runes_entries.rune ILIKE $1 || '%'
|
||||
)
|
||||
ORDER BY runes_entries.number
|
||||
LIMIT $3 OFFSET $2
|
||||
`
|
||||
|
||||
type GetRuneEntriesParams struct {
|
||||
Search interface{}
|
||||
Offset int32
|
||||
Limit int32
|
||||
Height int32
|
||||
}
|
||||
|
||||
type GetRuneEntriesRow struct {
|
||||
RuneID string
|
||||
Number int64
|
||||
Rune string
|
||||
Spacers int32
|
||||
Premine pgtype.Numeric
|
||||
Symbol int32
|
||||
Divisibility int16
|
||||
Terms bool
|
||||
TermsAmount pgtype.Numeric
|
||||
TermsCap pgtype.Numeric
|
||||
TermsHeightStart pgtype.Int4
|
||||
TermsHeightEnd pgtype.Int4
|
||||
TermsOffsetStart pgtype.Int4
|
||||
TermsOffsetEnd pgtype.Int4
|
||||
Turbo bool
|
||||
EtchingBlock int32
|
||||
EtchingTxHash string
|
||||
EtchedAt pgtype.Timestamp
|
||||
RuneID_2 pgtype.Text
|
||||
BlockHeight pgtype.Int4
|
||||
Mints pgtype.Numeric
|
||||
BurnedAmount pgtype.Numeric
|
||||
CompletedAt pgtype.Timestamp
|
||||
CompletedAtHeight pgtype.Int4
|
||||
}
|
||||
|
||||
func (q *Queries) GetRuneEntries(ctx context.Context, arg GetRuneEntriesParams) ([]GetRuneEntriesRow, error) {
|
||||
rows, err := q.db.Query(ctx, getRuneEntries,
|
||||
arg.Search,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
arg.Height,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetRuneEntriesRow
|
||||
for rows.Next() {
|
||||
var i GetRuneEntriesRow
|
||||
if err := rows.Scan(
|
||||
&i.RuneID,
|
||||
&i.Number,
|
||||
&i.Rune,
|
||||
&i.Spacers,
|
||||
&i.Premine,
|
||||
&i.Symbol,
|
||||
&i.Divisibility,
|
||||
&i.Terms,
|
||||
&i.TermsAmount,
|
||||
&i.TermsCap,
|
||||
&i.TermsHeightStart,
|
||||
&i.TermsHeightEnd,
|
||||
&i.TermsOffsetStart,
|
||||
&i.TermsOffsetEnd,
|
||||
&i.Turbo,
|
||||
&i.EtchingBlock,
|
||||
&i.EtchingTxHash,
|
||||
&i.EtchedAt,
|
||||
&i.RuneID_2,
|
||||
&i.BlockHeight,
|
||||
&i.Mints,
|
||||
&i.BurnedAmount,
|
||||
&i.CompletedAt,
|
||||
&i.CompletedAtHeight,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRuneEntriesByRuneIds = `-- name: GetRuneEntriesByRuneIds :many
|
||||
WITH states AS (
|
||||
-- select latest state
|
||||
@@ -971,6 +1182,43 @@ func (q *Queries) GetRunesUTXOsByRuneIdAndPkScript(ctx context.Context, arg GetR
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getTotalHoldersByRuneIds = `-- name: GetTotalHoldersByRuneIds :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (rune_id, pkscript) pkscript, block_height, rune_id, amount FROM runes_balances WHERE rune_id = ANY($1::TEXT[]) AND block_height <= $2 ORDER BY rune_id, pkscript, block_height DESC
|
||||
)
|
||||
SELECT rune_id, COUNT(DISTINCT pkscript) FROM balances WHERE amount > 0 GROUP BY rune_id
|
||||
`
|
||||
|
||||
type GetTotalHoldersByRuneIdsParams struct {
|
||||
RuneIds []string
|
||||
BlockHeight int32
|
||||
}
|
||||
|
||||
type GetTotalHoldersByRuneIdsRow struct {
|
||||
RuneID string
|
||||
Count int64
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalHoldersByRuneIds(ctx context.Context, arg GetTotalHoldersByRuneIdsParams) ([]GetTotalHoldersByRuneIdsRow, error) {
|
||||
rows, err := q.db.Query(ctx, getTotalHoldersByRuneIds, arg.RuneIds, arg.BlockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetTotalHoldersByRuneIdsRow
|
||||
for rows.Next() {
|
||||
var i GetTotalHoldersByRuneIdsRow
|
||||
if err := rows.Scan(&i.RuneID, &i.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const spendOutPointBalances = `-- name: SpendOutPointBalances :exec
|
||||
UPDATE runes_outpoint_balances SET spent_height = $1 WHERE tx_hash = $2 AND tx_idx = $3
|
||||
`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package gen
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
// source: info.sql
|
||||
|
||||
package gen
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package gen
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ func mapIndexerStateTypeToParams(src entity.IndexerState) gen.SetIndexerStatePar
|
||||
}
|
||||
}
|
||||
|
||||
func mapRuneEntryModelToType(src gen.GetRuneEntriesByRuneIdsRow) (runes.RuneEntry, error) {
|
||||
func mapRuneEntryModelToType(src gen.GetRuneEntriesRow) (runes.RuneEntry, error) {
|
||||
runeId, err := runes.NewRuneIdFromString(src.RuneID)
|
||||
if err != nil {
|
||||
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse rune id")
|
||||
|
||||
@@ -262,7 +262,7 @@ func (r *Repository) GetRuneEntryByRuneIdBatch(ctx context.Context, runeIds []ru
|
||||
runeEntries := make(map[runes.RuneId]*runes.RuneEntry, len(rows))
|
||||
var errs []error
|
||||
for i, runeEntryModel := range rows {
|
||||
runeEntry, err := mapRuneEntryModelToType(runeEntryModel)
|
||||
runeEntry, err := mapRuneEntryModelToType(gen.GetRuneEntriesRow(runeEntryModel))
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to parse rune entry model index %d", i))
|
||||
continue
|
||||
@@ -302,7 +302,7 @@ func (r *Repository) GetRuneEntryByRuneIdAndHeightBatch(ctx context.Context, run
|
||||
runeEntries := make(map[runes.RuneId]*runes.RuneEntry, len(rows))
|
||||
var errs []error
|
||||
for i, runeEntryModel := range rows {
|
||||
runeEntry, err := mapRuneEntryModelToType(gen.GetRuneEntriesByRuneIdsRow(runeEntryModel))
|
||||
runeEntry, err := mapRuneEntryModelToType(gen.GetRuneEntriesRow(runeEntryModel))
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to parse rune entry model index %d", i))
|
||||
continue
|
||||
@@ -316,6 +316,62 @@ func (r *Repository) GetRuneEntryByRuneIdAndHeightBatch(ctx context.Context, run
|
||||
return runeEntries, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRuneEntries(ctx context.Context, search string, blockHeight uint64, limit int32, offset int32) ([]*runes.RuneEntry, error) {
|
||||
rows, err := r.queries.GetRuneEntries(ctx, gen.GetRuneEntriesParams{
|
||||
Search: search,
|
||||
Height: int32(blockHeight),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
runeEntries := make([]*runes.RuneEntry, 0, len(rows))
|
||||
var errs []error
|
||||
for i, model := range rows {
|
||||
runeEntry, err := mapRuneEntryModelToType(model)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to parse rune entry model index %d", i))
|
||||
continue
|
||||
}
|
||||
runeEntries = append(runeEntries, &runeEntry)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
return runeEntries, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetOngoingRuneEntries(ctx context.Context, search string, blockHeight uint64, limit int32, offset int32) ([]*runes.RuneEntry, error) {
|
||||
rows, err := r.queries.GetOngoingRuneEntries(ctx, gen.GetOngoingRuneEntriesParams{
|
||||
Search: search,
|
||||
Height: int32(blockHeight),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
runeEntries := make([]*runes.RuneEntry, 0, len(rows))
|
||||
var errs []error
|
||||
for i, model := range rows {
|
||||
runeEntry, err := mapRuneEntryModelToType(gen.GetRuneEntriesRow(model))
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "failed to parse rune entry model index %d", i))
|
||||
continue
|
||||
}
|
||||
runeEntries = append(runeEntries, &runeEntry)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
return runeEntries, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CountRuneEntries(ctx context.Context) (uint64, error) {
|
||||
count, err := r.queries.CountRuneEntries(ctx)
|
||||
if err != nil {
|
||||
@@ -400,6 +456,25 @@ func (r *Repository) GetBalanceByPkScriptAndRuneId(ctx context.Context, pkScript
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTotalHoldersByRuneIds(ctx context.Context, runeIds []runes.RuneId, blockHeight uint64) (map[runes.RuneId]int64, error) {
|
||||
rows, err := r.queries.GetTotalHoldersByRuneIds(ctx, gen.GetTotalHoldersByRuneIdsParams{
|
||||
RuneIds: lo.Map(runeIds, func(runeId runes.RuneId, _ int) string { return runeId.String() }),
|
||||
BlockHeight: int32(blockHeight),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
holders := make(map[runes.RuneId]int64, len(rows))
|
||||
for _, row := range rows {
|
||||
runeId, err := runes.NewRuneIdFromString(row.RuneID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse RuneId")
|
||||
}
|
||||
holders[runeId] = row.Count
|
||||
}
|
||||
return holders, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error {
|
||||
if tx == nil {
|
||||
return nil
|
||||
|
||||
@@ -25,3 +25,11 @@ func (u *Usecase) GetBalancesByRuneId(ctx context.Context, runeId runes.RuneId,
|
||||
}
|
||||
return balances, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetTotalHoldersByRuneIds(ctx context.Context, runeIds []runes.RuneId, blockHeight uint64) (map[runes.RuneId]int64, error) {
|
||||
holders, err := u.runesDg.GetTotalHoldersByRuneIds(ctx, runeIds, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get total holders by rune ids")
|
||||
}
|
||||
return holders, nil
|
||||
}
|
||||
|
||||
@@ -46,3 +46,19 @@ func (u *Usecase) GetRuneEntryByRuneIdAndHeightBatch(ctx context.Context, runeId
|
||||
}
|
||||
return runeEntry, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetRuneEntries(ctx context.Context, search string, blockHeight uint64, limit, offset int32) ([]*runes.RuneEntry, error) {
|
||||
entries, err := u.runesDg.GetRuneEntries(ctx, search, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to listing rune entries")
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetOngoingRuneEntries(ctx context.Context, search string, blockHeight uint64, limit, offset int32) ([]*runes.RuneEntry, error) {
|
||||
entries, err := u.runesDg.GetOngoingRuneEntries(ctx, search, blockHeight, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to listing rune entries")
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user