feat: implement get balances by address batch

This commit is contained in:
Gaze
2024-04-18 18:26:14 +07:00
parent f6e2ff2029
commit 9eb7703e8d
5 changed files with 154 additions and 20 deletions

View File

@@ -88,23 +88,13 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
balanceList := make([]balance, 0, len(balances))
for id, b := range balances {
var name string
var symbol rune
var decimal uint8
if runeEntry, ok := runeEntries[id]; ok {
name = runeEntry.SpacedRune.String()
symbol = runeEntry.Symbol
decimal = runeEntry.Divisibility
}
if symbol == 0 {
symbol = '¤'
}
runeEntry := runeEntries[id]
balanceList = append(balanceList, balance{
Amount: b.Amount.String(),
Id: id.String(),
Name: name,
Symbol: string(symbol),
Decimals: decimal,
Name: runeEntry.SpacedRune.String(),
Symbol: string(runeEntry.Symbol),
Decimals: runeEntry.Divisibility,
})
}

View File

@@ -0,0 +1,145 @@
package httphandler
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
"golang.org/x/sync/errgroup"
)
type getBalanceQuery struct {
Wallet string `json:"wallet"`
Id string `json:"id"`
BlockHeight uint64 `json:"blockHeight"`
}
type getBalancesByAddressBatchRequest struct {
Queries []getBalanceQuery `json:"queries"`
}
func (r getBalancesByAddressBatchRequest) Validate() error {
var errList []error
for _, query := range r.Queries {
if query.Wallet == "" {
errList = append(errList, errors.Errorf("queries[%d]: 'wallet' is required"))
}
if query.Id != "" && !isRuneIdOrRuneName(query.Id) {
errList = append(errList, errors.Errorf("queries[%d]: 'id' is not valid rune id or rune name"))
}
}
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
}
type getBalancesByAddressBatchResult struct {
List []*getBalancesByAddressResult `json:"list"`
}
type getBalancesByAddressBatchResponse = HttpResponse[getBalancesByAddressBatchResult]
func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
var req getBalancesByAddressBatchRequest
if err := ctx.BodyParser(&req); err != nil {
return errors.WithStack(err)
}
if err := req.Validate(); err != nil {
return errors.WithStack(err)
}
var latestBlockHeight uint64
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
if err != nil {
return errors.Wrap(err, "error during GetLatestBlock")
}
latestBlockHeight = uint64(blockHeader.Height)
processQuery := func(ctx context.Context, query getBalanceQuery, queryIndex int) (*getBalancesByAddressResult, error) {
pkScript, ok := resolvePkScript(h.network, query.Wallet)
if !ok {
return nil, errs.NewPublicError(fmt.Sprintf("unable to resolve pkscript from \"queries[%d].wallet\"", queryIndex))
}
blockHeight := query.BlockHeight
if blockHeight == 0 {
blockHeight = latestBlockHeight
}
balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight)
if err != nil {
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
}
runeId, ok := h.resolveRuneId(ctx, query.Id)
if ok {
// filter out balances that don't match the requested rune id
for key := range balances {
if key != runeId {
delete(balances, key)
}
}
}
balanceRuneIds := lo.Keys(balances)
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx, balanceRuneIds)
if err != nil {
return nil, errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
}
balanceList := make([]balance, 0, len(balances))
for id, b := range balances {
var name string
var symbol rune
var decimal uint8
if runeEntry, ok := runeEntries[id]; ok {
name = runeEntry.SpacedRune.String()
symbol = runeEntry.Symbol
decimal = runeEntry.Divisibility
}
if symbol == 0 {
symbol = '¤'
}
balanceList = append(balanceList, balance{
Amount: b.Amount.String(),
Id: id.String(),
Name: name,
Symbol: string(symbol),
Decimals: decimal,
})
}
result := getBalancesByAddressResult{
BlockHeight: blockHeight,
List: balanceList,
}
return &result, nil
}
results := make([]*getBalancesByAddressResult, len(req.Queries))
eg, ectx := errgroup.WithContext(ctx.UserContext())
for i, query := range req.Queries {
i := i
query := query
eg.Go(func() error {
result, err := processQuery(ectx, query, i)
if err != nil {
return errors.Wrapf(err, "error during processQuery for query %d", i)
}
results[i] = result
return nil
})
}
if err := eg.Wait(); err != nil {
return errors.WithStack(err)
}
resp := getBalancesByAddressBatchResponse{
Result: &getBalancesByAddressBatchResult{
List: results,
},
}
return errors.WithStack(ctx.JSON(resp))
}

View File

@@ -94,9 +94,6 @@ type getTransactionsResponse = HttpResponse[getTransactionsResult]
func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
var req getTransactionsRequest
if err := ctx.ParamsParser(&req); err != nil {
return errors.WithStack(err)
}
if err := ctx.QueryParser(&req); err != nil {
return errors.WithStack(err)
}

View File

@@ -101,12 +101,13 @@ func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
for outPoint, balances := range groupedBalances {
runeBalances := make([]runeBalance, 0, len(balances))
for _, balance := range balances {
runeEntry := runeEntries[balance.RuneId]
runeBalances = append(runeBalances, runeBalance{
RuneId: balance.RuneId,
Rune: runeEntries[balance.RuneId].SpacedRune,
Symbol: string(runeEntries[balance.RuneId].Symbol),
Rune: runeEntry.SpacedRune,
Symbol: string(runeEntry.Symbol),
Amount: balance.Amount,
Divisibility: runeEntries[balance.RuneId].Divisibility,
Divisibility: runeEntry.Divisibility,
})
}

View File

@@ -7,6 +7,7 @@ import (
func (h *HttpHandler) Mount(router fiber.Router) error {
r := router.Group("/v2/runes")
r.Post("/balances/wallet/batch", h.GetBalancesByAddressBatch)
r.Get("/balances/wallet/:wallet", h.GetBalancesByAddress)
r.Get("/transactions", h.GetTransactions)
r.Get("/holders/:id", h.GetHolders)