Skip to content

Commit f8069f1

Browse files
author
Jenita
committed
changes to add support for searchUtxosByAsset
Signed-off-by: Jenita <[email protected]>
1 parent f995e56 commit f8069f1

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

internal/api/localstatequery.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"github.com/blinklabs-io/cardano-node-api/internal/node"
2222
"github.com/blinklabs-io/gouroboros/ledger"
23+
"github.com/blinklabs-io/gouroboros/protocol/localstatequery"
2324
"github.com/gin-gonic/gin"
2425
)
2526

@@ -30,6 +31,7 @@ func configureLocalStateQueryRoutes(apiGroup *gin.RouterGroup) {
3031
group.GET("/tip", handleLocalStateQueryTip)
3132
group.GET("/era-history", handleLocalStateQueryEraHistory)
3233
group.GET("/protocol-params", handleLocalStateQueryProtocolParams)
34+
group.GET("/utxos/search-by-asset", handleLocalStateQuerySearchUTxOsByAsset)
3335
// TODO: uncomment after this is fixed:
3436
// - https://github.com/blinklabs-io/gouroboros/issues/584
3537
// group.GET("/genesis-config", handleLocalStateQueryGenesisConfig)
@@ -374,3 +376,139 @@ func handleLocalStateQueryGenesisConfig(c *gin.Context) {
374376
//}
375377
c.JSON(200, genesisConfig)
376378
}
379+
380+
type responseLocalStateQuerySearchUTxOsByAsset struct {
381+
UTxOs []utxoItem `json:"utxos"`
382+
Count int `json:"count"`
383+
}
384+
385+
type utxoItem struct {
386+
TxHash string `json:"tx_hash"`
387+
Index uint32 `json:"index"`
388+
Address string `json:"address"`
389+
Amount uint64 `json:"amount"`
390+
Assets interface{} `json:"assets,omitempty"`
391+
}
392+
393+
// handleLocalStateQuerySearchUTxOsByAsset godoc
394+
//
395+
// @Summary Search UTxOs by Asset
396+
// @Tags localstatequery
397+
// @Produce json
398+
// @Param policy_id query string true "Policy ID (hex)"
399+
// @Param asset_name query string true "Asset name (hex)"
400+
// @Param address query string false "Optional: Filter by address"
401+
// @Success 200 {object} responseLocalStateQuerySearchUTxOsByAsset
402+
// @Failure 400 {object} responseApiError
403+
// @Failure 500 {object} responseApiError
404+
// @Router /localstatequery/utxos/search-by-asset [get]
405+
func handleLocalStateQuerySearchUTxOsByAsset(c *gin.Context) {
406+
// Get query parameters
407+
policyIdHex := c.Query("policy_id")
408+
assetNameHex := c.Query("asset_name")
409+
addressStr := c.Query("address")
410+
411+
// Validate required parameters
412+
if policyIdHex == "" {
413+
c.JSON(400, apiError("policy_id parameter is required"))
414+
return
415+
}
416+
if assetNameHex == "" {
417+
c.JSON(400, apiError("asset_name parameter is required"))
418+
return
419+
}
420+
421+
// Parse policy ID (28 bytes)
422+
policyIdBytes, err := hex.DecodeString(policyIdHex)
423+
if err != nil {
424+
c.JSON(400, apiError("invalid policy_id hex: "+err.Error()))
425+
return
426+
}
427+
if len(policyIdBytes) != 28 {
428+
c.JSON(400, apiError("policy_id must be 28 bytes"))
429+
return
430+
}
431+
var policyId ledger.Blake2b224
432+
copy(policyId[:], policyIdBytes)
433+
434+
// Parse asset name
435+
assetName, err := hex.DecodeString(assetNameHex)
436+
if err != nil {
437+
c.JSON(400, apiError("invalid asset_name hex: "+err.Error()))
438+
return
439+
}
440+
441+
// Parse optional address
442+
var addrs []ledger.Address
443+
if addressStr != "" {
444+
addr, err := ledger.NewAddress(addressStr)
445+
if err != nil {
446+
c.JSON(400, apiError("invalid address: "+err.Error()))
447+
return
448+
}
449+
addrs = append(addrs, addr)
450+
}
451+
452+
// Connect to node
453+
oConn, err := node.GetConnection(nil)
454+
if err != nil {
455+
c.JSON(500, apiError(err.Error()))
456+
return
457+
}
458+
// Async error handler
459+
go func() {
460+
err, ok := <-oConn.ErrorChan()
461+
if !ok {
462+
return
463+
}
464+
c.JSON(500, apiError(err.Error()))
465+
}()
466+
defer func() {
467+
// Close Ouroboros connection
468+
oConn.Close()
469+
}()
470+
// Start client
471+
oConn.LocalStateQuery().Client.Start()
472+
473+
// Get UTxOs (either by address or whole set)
474+
var utxos *localstatequery.UTxOsResult
475+
if len(addrs) > 0 {
476+
utxos, err = oConn.LocalStateQuery().Client.GetUTxOByAddress(addrs)
477+
} else {
478+
utxos, err = oConn.LocalStateQuery().Client.GetUTxOWhole()
479+
}
480+
if err != nil {
481+
c.JSON(500, apiError(err.Error()))
482+
return
483+
}
484+
485+
// Filter UTxOs by asset
486+
results := make([]utxoItem, 0)
487+
for utxoId, output := range utxos.Results {
488+
// Check if output has assets
489+
assets := output.Assets()
490+
if assets == nil {
491+
continue
492+
}
493+
494+
// Check if the asset exists in this UTxO
495+
amount := assets.Asset(policyId, assetName)
496+
if amount > 0 {
497+
item := utxoItem{
498+
TxHash: hex.EncodeToString(utxoId.Hash[:]),
499+
Index: uint32(utxoId.Idx),
500+
Address: output.Address().String(),
501+
Amount: output.Amount(),
502+
Assets: assets,
503+
}
504+
results = append(results, item)
505+
}
506+
}
507+
508+
// Create response
509+
resp := responseLocalStateQuerySearchUTxOsByAsset{
510+
UTxOs: results,
511+
Count: len(results),
512+
}
513+
c.JSON(200, resp)
514+
}

0 commit comments

Comments
 (0)