Repository navigation
feat(indexer): keyset pagination, holders, order book (api-server v2) - #4
Merged
erubboli merged 5 commits intoOct 5, 2026
Conversation
Mirror the api-server v2 changes from mintlayer-core PR #2130
(api-server 1.4.1):
- statistics/coin and statistics/token/{id}: document that all four
supply counters are always present (unwritten = zero)
- new holders listings: GET /statistics/coin/holders and
GET /statistics/token/{id}/holders with cursor + offset support
- new order book: GET /order/pair/{pair}/book with side=ask|bid,
aggregated price levels (exact rational + floored decimal string),
side-specific cursors, and the truncated invariant
(truncated => next_cursor null, walk cannot continue)
- shared cursor envelope Page<T> and a reusable Pager that follows
next_cursor, stops on null, and clamps page sizes to 1..=100
- typed errors mapped from the server's error bodies: invalid cursor,
invalid number of items, bad request, token not found
- Transaction.block_id/timestamp/confirmations are now Option<String>
(the server emits JSON null for pending transactions)
- transactions: offset_mode=legacy|absolute for the offset listing;
cursor and offset_mode are mutually exclusive (server 400)
- pools: cursor walk pinned to the default creation-height sort
- docs and a runnable example covering all four walks
- Pager: keep the resume cursor on a failed fetch (clone instead of
take), so a retried walk resumes instead of restarting; pinned by a
mid-walk 500 retry test.
- Widen pagination offsets to u64 to match the server contract
(OffsetMode::Absolute offsets are global transaction indexes).
- CHANGELOG: document the v2 pagination API, typed errors, Transaction
optionality break, and the pagination example.
- order_book_pager: document that a truncated book ends the walk
indistinguishably from a complete one.
- docs: pools/transactions return plain arrays without a cursor param;
quote the exact wire messages ("Invalid cursor", "Invalid number of
items", "Bad request", "Token not found").
- Declare examples/indexer-pagination with required-features = ["indexer"] so --no-default-features --all-targets builds keep working. - README: list the indexer-pagination example; docs: order_book_pager returns Result. - Pager::next_page doc: buffered items survive once the walk is exhausted.
- Pager::start_from rewinds an exhausted pager (resets done/pending), so cursor() always matches what the next fetch uses; pinned by a test. - OffsetMode::Absolute doc: state the precise upstream boundary (the page holds the transactions with global indexes below the offset). - Coin-holder test fixtures: make atoms/decimal pairs self-consistent at the coin's 11 decimals. - indexer-pagination example header: no longer claims an end-to-end walk.
erubboli
self-requested a review
October 5, 2026 10:38
erubboli
approved these changes
Oct 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the api-server v2 changes from mintlayer-core PR #2130 (commit 2684078c546, api-server 1.4.1), mirroring the existing SDK structure (one client method per endpoint, request-option pattern, typed responses, typed errors). No breaking changes to existing method signatures — the one behavioral fix is
Transaction's pending-tx fields becomingOptionso mempool entries decode.New endpoints & response types
GET /statistics/coin/holders,GET /statistics/token/{id}/holders: address-balance holders listing (Holder { address, amount }, amounts rendered with the coin's/token's decimals). Unknown token →Error::TokenNotFound.GET /order/pair/{pair}/book: aggregated order book per side (OrderBook/OrderBookLevel/OrderBookPrice, exactnumer/denomprice plus floored-toward-zero decimal). Side-specific cursors (ask cursor on a bid walk →Error::InvalidCursor). Truncation invariant encoded in the type:truncated: truealways arrives withnext_cursor: null, documented, and the pager stops on it. Coin tickers match case-insensitively; token ids are preserved exactly (bech32 is case-sensitive).circulating_supply,preminted,burned,staked) are always present; unwritten counters are zero.Cursor pagination
Page<T> { items, next_cursor }and a reusablePager<T>walker (*_pagerconstructors for pools, the global transaction listing, both holders listings, and the order book): transparently followsnext_cursor, stops onnull, keeps its position across failed fetches (retriable), typed errors surface asSome(Err).MAX_NUM_ITEMS); server-sideitems=0/items>100rejection mapped toError::InvalidNumItems.list_transactions_with_offset_mode(OffsetMode::Legacy/Absolute); combining cursor + offset_mode →Error::BadRequest(documented). Pools: cursors only with the defaultby_heightsort (documented; other sorts keep the offset-basedlist_pools).Typed errors
Error::InvalidCursor,InvalidNumItems,BadRequest,TokenNotFound, mapped from exact{"error": ...}server bodies; anything else keeps the existingError::Httpfallback.Tests & docs
block_id), both book sides, truncated book stops the pager, last page, invalid-cursor/invalid-num-items/token-not-found, page-size clamping, retry-resume after a failed fetch,start_fromrewind.docs/indexer.md+ README: per-endpoint cursor rules, error table with exact wire messages, runnable snippets; newexamples/indexer-pagination.rs(holders, both-side book, transactions with cursor andoffset_mode, pools — noting the sort/cursor restriction).Validation:
cargo test --all-features(107 tests) green,cargo clippy --all-targets --all-featuresand--no-default-features --all-targets0 warnings,cargo fmt --check,cargo docclean. Review loop (4-dimension adversarial reviewer fan-out) run recursively to 0 findings: cycle 1 → 11 findings, cycle 2 → 5, cycle 3 → 4, cycle 4/5 → 0.