Skip to content
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

#### Indexer client (`github.com/mintlayer/go-sdk/indexer`)

Cursor pagination for the api-server v2 endpoints introduced in mintlayer-core PR #2130 (api-server 1.4.1):

- **Pools**: `ListPoolsPage` and `PoolsPager` — cursor envelope over `/v2/pool`; cursors work only with the default `by_height` sort. On `ListPoolsPage` any other sort with a cursor is a server 400 `Bad request`, propagated as `*HTTPError` (api-server contract); `PoolsPager` rejects the same combination client-side with a `*RequestError`.
- **Transactions**: `ListTransactionsPage` and `TransactionsPager` — cursor envelope over the global listing (newest block first, transactions in block order within each block); `WithOffsetMode("legacy"|"absolute")` selects the offset-based listing instead, and cursor + `offset_mode` is rejected client-side with a `*RequestError`. `Transaction.BlockID` is documented as the confirming block's hash (empty for mempool transactions).
- **Holders**: `ListCoinHolders`, `ListTokenHolders`, `CoinHoldersPager`, `TokenHoldersPager`, and the `Holder` type (address + amount; token ids are exact case-sensitive bech32, unknown ids are 404 `Token not found`).
- **Order book**: `GetOrderBook`, `OrderBookPager`, and the `OrderBookPage`/`OrderBookLevel`/`OrderBookPrice` types — aggregated price levels (exact reduced-rational price plus floored decimal rendering) per side; required `WithSide`, side-specific cursors, and the truncation invariant (`Truncated` true ⇒ `NextCursor` nil at the server's `OrderBookMaxOrders` = 10000-order cap; the paginator stops there).
- **Shared paginator**: generic `Pager[T]` (`NewPager`, `NextPage`, `Walk`, `NextCursor`, `Truncated`) and the `CursorPage[T]` `{items, next_cursor}` envelope; pre-1.4.1 bare-array responses are accepted and wrapped, a server-issued empty cursor is treated as end-of-listing, an empty fetched page ends the walk (the api-server issues a cursor only alongside items), and `NextPage` never loses the unserved remainder of a partially consumed page.
- **List options**: `WithItems` (validated 1..=100 = `MaxNumItems`; the server rejects `items=0` on every paginated endpoint), `WithOffset`, `WithCursor` (opaque, non-empty; never constructed client-side), `WithSort`, `WithOffsetMode`, `WithSide`; inapplicable options fail client-side with `*RequestError` — the single-page methods at call time, the cursor pagers at construction time (`PoolsPager` rejects non-default sorts, `TransactionsPager` rejects `WithOffsetMode`).
- **Typed errors**: `HTTPError.Message`/`HTTPError.Kind` populated from the server's `{"error": ...}` body, `ErrorKind` classification, and an `errors.Is` sentinel for every well-known server error (`ErrInvalidCursor`, `ErrInvalidNumItems`, `ErrTokenNotFound`, `ErrBadRequest`, `ErrInvalidOffsetMode`, `ErrInvalidPoolsSortOrder`, `ErrInvalidTokenID`, `ErrInvalidOrderPair`).

Documented semantics: page stability is only guaranteed once the indexer's scanner is fully caught up (walks during catch-up/reorgs may skip or repeat entries); offset-based listings remain unchanged as the simple alternative for shallow listings; on pools/holders/order book a cursor silently overrides the offset page position server-side.

Docs and examples: README indexer section, `docs/indexer.md` (cursor pagination guide, holders/order book reference, error table), and the runnable `examples/indexer-paging`.

---

## [0.1.0] — 2026-04-16
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Requires Go 1.21+. No CGO. The WASM cryptography runtime is embedded in the bina

| Guide | Description |
|-------|-------------|
| [docs/indexer.md](docs/indexer.md) | Full indexer client reference: chain, blocks, transactions, addresses, pools, tokens, orders, statistics |
| [docs/indexer.md](docs/indexer.md) | Full indexer client reference: chain, blocks, transactions, addresses, pools, tokens, orders, statistics, cursor pagination |
| [docs/node.md](docs/node.md) | Full node client reference: chainstate, mempool, P2P, block submission |
| [docs/wallet.md](docs/wallet.md) | Full wallet client reference: lifecycle, accounts, balances, transactions |
| [docs/wasm.md](docs/wasm.md) | Full WASM client reference: keys, addresses, inputs, outputs, signing, fees |
Expand Down Expand Up @@ -165,6 +165,14 @@ info, err := c.GetAddressInfo(ctx, "mxtc1...")
pools, err := c.ListPools(ctx, indexer.PoolListOpts{Sort: "by_pledge"})
pool, err := c.GetPool(ctx, "pool1...")

// Cursor-paginated listings (pools, transactions, holders, order book)
holderPager := indexer.CoinHoldersPager(c)
err := holderPager.Walk(ctx, func(h indexer.Holder) bool {
fmt.Println(h.Address, h.Amount.Decimal)
return true
})
book, err := c.GetOrderBook(ctx, "ML_tmltken1...", indexer.WithSide(indexer.SideAsk))

// Tokens
token, err := c.GetToken(ctx, "ttml1...")
tokens, err := c.FindTokensByTicker(ctx, "MYTOKEN", indexer.PageOpts{Items: 10})
Expand All @@ -177,7 +185,7 @@ order, err := c.GetOrder(ctx, "order1...")
stats, err := c.GetCoinStatistics(ctx)
```

Non-2xx responses are returned as `*indexer.HTTPError` with a `StatusCode` field.
Non-2xx responses are returned as `*indexer.HTTPError` with a `StatusCode` field. Every well-known api-server v2 error is classified in `Kind` and has a sentinel (e.g. `ErrInvalidCursor`, `ErrInvalidNumItems`, `ErrTokenNotFound`) matched via `errors.Is`; invalid option values fail client-side with `*indexer.RequestError`.

---

Expand Down Expand Up @@ -333,6 +341,7 @@ that only import the top-level package do not need to also import `github.com/mi
|---|---|
| [examples/send-coins/](examples/send-coins/main.go) | Derive key → fetch UTXOs → build, sign, and submit a transaction |
| [examples/issue-token/](examples/issue-token/main.go) | Issue a fungible token and mint an initial supply via the wallet daemon |
| [examples/indexer-paging/](examples/indexer-paging/main.go) | Cursor-paginated indexer listings: holders walk, both-side order book, transactions with cursor and offset_mode, pools |

---

Expand Down Expand Up @@ -369,5 +378,6 @@ Pass the network constant to any function that derives addresses or encodes tran

- `node.RPCError` — JSON-RPC error from the node daemon (`Code`, `Message`)
- `wallet.RPCError` — JSON-RPC error from the wallet daemon
- `indexer.HTTPError` — non-2xx HTTP response from the indexer (`StatusCode`, `Body`)
- `indexer.HTTPError` — non-2xx HTTP response from the indexer (`StatusCode`, `Body`, `Message`, `Kind`); every `Kind` other than `ErrorKindOther` has an `errors.Is` sentinel (e.g. `indexer.ErrInvalidCursor`, `indexer.ErrInvalidNumItems`, `indexer.ErrTokenNotFound`)
- `indexer.RequestError` — request rejected client-side before it was sent (invalid `WithItems`/`WithCursor`/`WithSide`/… value or combination), with `Option` and `Reason`
- WASM errors are plain `error` values with a descriptive message prefixed by `mintlayer:`.
Loading
Loading