Repository navigation
feat(atomicmarket): filter and sort listings by effective mint so one parameter ranges over the mint a bridged or a plain asset shows - #234
Merged
Conversation
… parameter ranges over the mint a bridged or a plain asset shows A bridged asset shows its original mint and a plain asset shows its template mint, but the market endpoints could filter and sort only by the template mint. A client that filtered a collection with both kinds of asset by the mint on the card got the wrong listings for every bridged asset. The sales, auctions, buyoffers and template buyoffers lists and their counts now accept min_effective_mint and max_effective_mint, the lists accept sort=effective_mint, and the sales templates list accepts the two filters. The effective mint of an asset is its original mint when a link row with a mint exists, else its template mint. A listing matches when at least one asset has an effective mint and every such asset lies in the range, so a listing of a plain asset stays in the result. The sort uses the lowest effective mint of the listing and leaves out a listing with none, and the count applies the same condition. The rule is one scalar aggregate per listing. The planner cannot reorder it, where a join form let it build a hash of the link rows once per listing and an array overlap form grew with the range. No index holds the value, so each listing in scope costs a probe of the asset and link tables. A request with one of the three parameters and no collection_name returns 400 for that reason: without the scope the probe runs for every listing of the chain. The template_mint filters and sorts keep their query text. The release has no migration. Signed-off-by: Rob Konsdorf <rob@facings.io>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The correlated queries span several high-volume endpoints and have acknowledged cold-cache and all-state latency risks requiring final human operational review.
Review effort: Balanced
Findings: None
What changed in this PR
Adds effective-mint filtering and sorting across AtomicMarket listing APIs, using bridged assets’ original mint or plain assets’ template mint.
Changes:
- Adds shared effective-mint SQL filtering, sorting, validation, and collection scoping.
- Integrates behavior across sales, auctions, buyoffers, and template buyoffers.
- Adds OpenAPI documentation, changelog notes, and comprehensive integration coverage.
| File | Description |
|---|---|
src/api/namespaces/atomicmarket/utils.ts |
Implements shared effective-mint query logic. |
src/api/namespaces/atomicmarket/openapi.ts |
Documents the new filter parameters. |
src/api/namespaces/atomicmarket/routes/sales.ts |
Exposes sales filters and sort. |
src/api/namespaces/atomicmarket/routes/auctions.ts |
Exposes auction sort. |
src/api/namespaces/atomicmarket/routes/buyoffers.ts |
Exposes buyoffer sort. |
src/api/namespaces/atomicmarket/routes/template-buyoffers.ts |
Exposes template-buyoffer sort. |
src/api/namespaces/atomicmarket/handlers/sales.ts |
Applies effective-mint sorting to v1 sales. |
src/api/namespaces/atomicmarket/handlers/sales2.ts |
Applies filtering and sorting to v2 sales and templates. |
src/api/namespaces/atomicmarket/handlers/auctions.ts |
Applies effective-mint sorting to auctions. |
src/api/namespaces/atomicmarket/handlers/buyoffers.ts |
Applies effective-mint sorting to buyoffers. |
src/api/namespaces/atomicmarket/handlers/template-buyoffers.ts |
Applies sorting to fulfilled template buyoffers. |
src/api/namespaces/atomicmarket/effective-mint-suite.ts |
Provides shared integration scenarios. |
src/api/namespaces/atomicmarket/handlers/sales.get-sales.integration.test.ts |
Tests v1 sales behavior. |
src/api/namespaces/atomicmarket/handlers/sales2.integration.test.ts |
Tests v2 sales and state rechecking. |
src/api/namespaces/atomicmarket/handlers/sales2.get-sales-templates.integration.test.ts |
Tests sales-template filtering. |
src/api/namespaces/atomicmarket/handlers/auctions.integration.test.ts |
Tests auction behavior. |
src/api/namespaces/atomicmarket/handlers/buyoffers.integration.test.ts |
Tests buyoffer behavior. |
src/api/namespaces/atomicmarket/handlers/template-buyoffers.integration.test.ts |
Tests template-buyoffer behavior. |
CHANGELOG.md |
Documents the 2.6.0 feature and constraints. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This was referenced Oct 6, 2026
Merged
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.
A bridged asset shows its original mint and a plain asset shows its template mint, but the market endpoints filter and sort only by the template mint. A client that filters a collection with both kinds of asset by the mint on the card gets the wrong listings for every bridged asset. This PR adds an effective mint filter and sort to the market listing endpoints. Tracking: ENG-1267. Lane M.
Endpoints.
/v0/sales,/v1/sales,/v2/sales,/v1/auctions,/v1/buyoffers,/v1/template_buyoffersand their_countroutes acceptmin_effective_mintandmax_effective_mint, and the lists acceptsort=effective_mint./v1/sales/templatesaccepts the two filters and has no mint sort. These are the endpoints that reachbuildTemplateMintFilter. The market/v1/assetsroute is an asset endpoint that already serves theoriginal_mintfilters, so it does not change.Rule. The effective mint of an asset is its
original_mintwhen a link row with a mint exists, else its template mint. An asset with neither is ignored. A listing matches when at least one of its assets has an effective mint and the effective mint of every such asset lies in the range, so a listing of a plain asset stays in the result. Either bound alone is valid, and a minimum above the maximum returns 400.sort=effective_mintorders by the lowest effective mint of the listing and returns only listings that have one, with or without a bound. The count applies the same condition, so list and count agree. A template buyoffer has asset rows only after fulfilment, so the filter matches fulfilled template buyoffers only, as thetemplate_mintfilter does.Scope. A request with one of the three parameters needs
collection_nameand returns 400 without it. No index holds the effective mint, so each listing in scope costs a probe of the asset and link tables. Without a collection that probe runs for every listing of the chain: on a database with 174 million sales, the request took 8 s for listed sales and passed a 30 s statement timeout in the other forms tried. The gate tests only that the parameter is present. A request that names large collections still pays for each of their listings, and the statement timeout stays the limit for it, as for thetemplate_mintfilter today.Query form. The rule is one scalar aggregate per listing, shared by the five handlers through
buildEffectiveMintFilterandbuildEffectiveMintSort:The v1 handlers read the listing's asset rows from their asset table (
atomicassets_offers_assets,atomicmarket_auctions_assets,atomicmarket_buyoffers_assets,atomicmarket_template_buyoffers_assets) in place of theasset_idsarray.HAVINGmakes a listing with no mint a NULL range, which never matches. The planner cannot reorder this form. Two other forms were measured on the same database and rejected: anEXISTSwith a join let the planner build a hash of the link rows one time per listing (17 s to 26 s where the aggregate took about 1 s), and an array overlap onasset_idsgrew with the range and passed 30 s for an open bound. A request without the new parameters keeps its query text.Plans, read-only
EXPLAIN (ANALYZE, BUFFERS)of the generated SQL on a replica of that database, for one collection with 14,343 listed sales and 158,260 sales in all states, bounds 1 to 10:/v2/sales, state listed, bounds,sort=effective_mint(state recheck CTE)/v2/sales/_count, same request/v2/sales, all sale states, bounds/v1/sales, state listed, bounds/v1/buyoffers, boundsEach plan reads the listing rows of the collection through the existing collection index, then the asset row and the link row by primary key per listing asset. No plan holds a sequential scan of a large table. The all-states plan scans two sale-state partitions of a few pages each sequentially. The cold numbers come from the heap reads of the asset rows. The request over all sale states is slow, as the
template_mintfilter on/v1/salesis today for the same scope (it passed 30 s in a control run).Tests. 64 new integration tests: a shared suite of 12 cases on each of the five listing handlers, 3 cases on
/v1/sales/templates, and 1 case for the/v2/salesstate recheck path. They assert exact ids for a linked and an unlinked asset in and out of range, the original mint winning over the template mint, a null link mint, a multi-asset listing with an asset above and one below the range, an asset with no mint, each bound alone, the sort in both orders with its count and a second page, the message of each 400, and a request that sends equaltemplate_mintandeffective_mintbounds. The existingtemplate_minttests pass with no edit. Unit: 596 passing, 37 pending (596 on the base). Integration on a fresh PostgreSQL 16 database: 779 passing, 0 failing (715 on the base).pnpm check-typesandpnpm lintexit 0.Reviews. One cold code review: 6 findings, 0 blocking, 6 applied (5 test gaps and the changelog references). One cold security review of the SQL construction, the parameter reuse, the validation and the gate: 0 findings. It left one open decision, a cap on the count of collection names for these parameters, which this PR does not add.
Not verified. No run of the new image on a deployed chain. The plans above come from hand-run SQL that the handlers generate, not from HTTP requests.