diff --git a/CHANGELOG.md b/CHANGELOG.md index 193f6da..f09a0c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ What each release of this corpus changed, one release per tag. `Corrected` comes first in every release, because a fact that was wrong is what a returning reader has to see before anything else. The other sections are `Added`, `Revalidated`, and `Removed`, in that order, and a section with nothing in it is left out. +## 2026.08.3 + +### Corrected + +- `reference/api.md` and `reference/api-streaming.md` say the template-buyoffer socket handler is wired: at the pinned 2.2.0 tag `new_template_buyoffer` broadcasts on `lognewtbuyo`, where both pages previously recorded the handler as defined but never called. +- `reference/atomicassets/tables.md` records that the `mediatype`/`info` synthesis is live on both hosted deployments, which now report 2.2.0 on `/health`, replacing the note that pinned mainnet to the pre-2.0 behavior. + +### Revalidated + +- The atomicassets-api baseline moves from an untagged main-branch state to tag `2.2.0`: of the twenty-one cited files, fifteen are byte-identical to the old pin and the rest change nothing cited except the socket wiring above; both line-range citations into the repo still point at byte-identical content. + ## 2026.08.2 ### Revalidated diff --git a/README.md b/README.md index 53dac84..2667531 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ The pages are read against these baselines: - `atomicassets-contract` at `v2.0.0` - `atomicmarket-contract` at `v2.0.0` -- `atomicassets-api` at its `main` branch state, which carries no release tag; the streaming and rate-limit pages pin commit `f6419858` +- `atomicassets-api` at tag `2.2.0`, commit `cf8bf323`, the version both hosted deployments report on `/health` - `atomictools-contract` at commit `d89ce79e4`, which the deployed `atomictoolsx` ABI on WAX matches exactly -- `atomicassets-sdk` at main `80580c5` and `atomicmarket-sdk` at main `278bdfa`, both version 2.0.0 +- `atomicassets-sdk` at tag `v2.1.1`, commit `5c70c62`, and `atomicmarket-sdk` at tag `v2.4.1`, commit `437300b` - `@atomichub/vert` at `2.2.0` WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts while WAX testnet and jungle4 run V2, so an action that exists only in V2 fails when it is sent to mainnet (`reference/validation.md`). diff --git a/reference/api-streaming.md b/reference/api-streaming.md index 338cd30..182d9a9 100644 --- a/reference/api-streaming.md +++ b/reference/api-streaming.md @@ -2,7 +2,7 @@ scope: atomicassets-api realtime surface - Socket.IO namespaces, events, room subscription, connection shape, and the absence of socket auth or app-layer limits depends-on: [] key-modules: - - "atomicassets-api (main, f6419858): src/api/server.ts, src/api/utils.ts, src/api/notification.ts, src/api/namespaces/*/routes/*.ts" + - "atomicassets-api (2.2.0, cf8bf323): src/api/server.ts, src/api/utils.ts, src/api/notification.ts, src/api/namespaces/*/routes/*.ts" --- # atomicassets-api realtime (Socket.IO) @@ -11,7 +11,7 @@ key-modules: The API attaches a Socket.IO server to the same HTTP server that serves the REST endpoints, so realtime and REST share one host and port. The server is constructed with `transports: ['websocket']`, `allowEIO3: true`, and `cors: {origin: '*'}`, so clients connect over the WebSocket transport only (HTTP long-polling is disabled) and both Engine.IO v3 and v4 clients are accepted from any origin. The Engine.IO path is the Socket.IO default `/socket.io`; realtime channels are Socket.IO namespaces, not URL routes. A client selects a channel by connecting to the namespace whose name matches the REST path plus the resource, for example `wss://wax.api.atomicassets.io/atomicmarket/v1/sales`. Use a Socket.IO v4 client (the server is `socket.io ^4.8.3`); pin the client to the server's major and force the WebSocket transport, because the default transport list starts with polling, which this server does not serve. -Source: `atomicassets-api (main, f6419858) src/api/server.ts` (`SocketServer`, `new Server(..., {transports: ['websocket'], allowEIO3: true, cors: {origin: '*'}})`), `package.json` (`socket.io`), `src/api/utils.ts` (`createSocketApiNamespace` maps namespace name to `/v1/`); live probe of `wss://wax.api.atomicassets.io` connecting to five namespaces over the WebSocket transport (all handshakes succeeded) +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/server.ts` (`SocketServer`, `new Server(..., {transports: ['websocket'], allowEIO3: true, cors: {origin: '*'}})`), `package.json` (`socket.io`), `src/api/utils.ts` (`createSocketApiNamespace` maps namespace name to `/v1/`); live probe of `wss://wax.api.atomicassets.io` connecting to five namespaces over the WebSocket transport (all handshakes succeeded) ## Namespace and event catalog @@ -30,40 +30,41 @@ Each namespace name is the configured namespace path (`/atomicassets`, `/atomicm | `/atomicmarket/v1/auctions` | `new_auction` | `lognewauct` | `new_auctions` | | `/atomicmarket/v1/auctions` | `new_bid` | `auctionbid` | `new_bids` | | `/atomicmarket/v1/buyoffers` | `new_buyoffer` | `lognewbuyo` | `new_buyoffers` | +| `/atomicmarket/v1/template_buyoffers` | `new_template_buyoffer` | `lognewtbuyo` | `new_template_buyoffers` | Every listed namespace also emits `fork` (payload `{block_num}`) to the whole namespace when the connected reader rolls back a microfork, so a consumer holding optimistic state can invalidate everything at or after that block. The `back` and `update` asset events are emitted only when the `atomicassets` namespace is configured with `socket_features.asset_update: true`; the reference config ships it `false`, so a default deployment broadcasts `new_asset`, `burn`, and `fork` on the assets namespace and nothing else. The `atomictools` namespace registers no socket handler, so it exposes no realtime events even though its REST namespace exists. -Source: `atomicassets-api (main, f6419858) src/api/namespaces/atomicassets/routes/assets.ts` (`new_asset`/`burn`/`back`/`update`/`fork`, `asset_update` gate), `.../atomicassets/routes/offers.ts` (`create`), `.../atomicassets/routes/transfers.ts` (`new_transfer`), `.../atomicmarket/routes/sales.ts` (`new_sale`/`purchased_sale`), `.../atomicmarket/routes/auctions.ts` (`new_auction`/`new_bid`), `.../atomicmarket/routes/buyoffers.ts` (`new_buyoffer`), `.../atomicmarket/index.ts` (`socket()` wires sales/auctions/buyoffers), `.../atomictools/index.ts` (empty `socket()`), `config/server.config.example.json` (`socket_features.asset_update: false`) +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/namespaces/atomicassets/routes/assets.ts` (`new_asset`/`burn`/`back`/`update`/`fork`, `asset_update` gate), `.../atomicassets/routes/offers.ts` (`create`), `.../atomicassets/routes/transfers.ts` (`new_transfer`), `.../atomicmarket/routes/sales.ts` (`new_sale`/`purchased_sale`), `.../atomicmarket/routes/auctions.ts` (`new_auction`/`new_bid`), `.../atomicmarket/routes/buyoffers.ts` (`new_buyoffer`), `.../atomicmarket/index.ts` (`socket()` wires sales/auctions/buyoffers/template-buyoffers), `.../atomictools/index.ts` (empty `socket()`), `config/server.config.example.json` (`socket_features.asset_update: false`) ## What triggers a broadcast Socket events are not driven by the API's own database writes. The filler's notifier publishes each batch of matching action traces and table deltas to a Redis pub/sub channel named `eosio-contract-api:::api`; the API subscribes to that channel through `ApiNotificationReceiver`, and each socket route registered an `onData(, ...)` listener that queries the affected rows and emits the socket event. A deployment therefore broadcasts realtime events only when its API process shares Redis with a running filler whose reader is configured to publish notifications; an API pointed at a database with no live notifier serves REST normally but stays silent on every namespace. Connecting to a namespace succeeds regardless, because the handshake is independent of whether any notifier is publishing. -Source: `atomicassets-api (main, f6419858) src/api/notification.ts` (`ApiNotificationReceiver`, channel `eosio-contract-api:::api`), `src/api/namespaces/*/index.ts` (`socket()` constructs the receiver from `args.connected_reader` and registers the per-resource `onData` listeners); live probe held five namespaces open for 30 and 50 second windows and observed no events, consistent with connectivity being independent of notifier traffic +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/notification.ts` (`ApiNotificationReceiver`, channel `eosio-contract-api:::api`), `src/api/namespaces/*/index.ts` (`socket()` constructs the receiver from `args.connected_reader` and registers the per-resource `onData` listeners); live probe held five namespaces open for 30 and 50 second windows and observed no events, consistent with connectivity being independent of notifier traffic ## Market namespaces gate events behind opt-in rooms; asset and offer namespaces do not -The three `atomicmarket` namespaces broadcast their entity events only to Socket.IO rooms, and a fresh connection joins none of them. A client opts in by emitting a `subscribe` event whose payload sets each wanted room to a truthy value; the same handler leaves any room whose flag is absent or falsy, so `subscribe` is the full membership list on every call, not an additive toggle. The room names are `new_sales` and `purchased_sales` on `/atomicmarket/v1/sales`, `new_auctions` and `new_bids` on `/atomicmarket/v1/auctions`, and `new_buyoffers` on `/atomicmarket/v1/buyoffers`. Without a `subscribe` the market namespaces deliver only `fork`, which is broadcast namespace-wide. The `/atomicassets/v1/assets` and `/atomicassets/v1/offers` namespaces use no rooms: every subscriber receives every event the moment it connects, with no `subscribe` step. +The four `atomicmarket` namespaces broadcast their entity events only to Socket.IO rooms, and a fresh connection joins none of them. A client opts in by emitting a `subscribe` event whose payload sets each wanted room to a truthy value; the same handler leaves any room whose flag is absent or falsy, so `subscribe` is the full membership list on every call, not an additive toggle. The room names are `new_sales` and `purchased_sales` on `/atomicmarket/v1/sales`, `new_auctions` and `new_bids` on `/atomicmarket/v1/auctions`, `new_buyoffers` on `/atomicmarket/v1/buyoffers`, and `new_template_buyoffers` on `/atomicmarket/v1/template_buyoffers`. Without a `subscribe` the market namespaces deliver only `fork`, which is broadcast namespace-wide. The `/atomicassets/v1/assets` and `/atomicassets/v1/offers` namespaces use no rooms: every subscriber receives every event the moment it connects, with no `subscribe` step. -Source: `atomicassets-api (main, f6419858) src/api/namespaces/atomicmarket/routes/sales.ts`, `.../auctions.ts`, `.../buyoffers.ts` (each `namespace.on('connection', ...)` with a `subscribe` handler over a fixed `availableRooms` list; entity events use `namespace.in('').emit(...)` while `fork` uses `namespace.emit(...)`), `.../atomicassets/routes/assets.ts` and `.../offers.ts` (no connection handler, all events via `namespace.emit(...)`) +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/namespaces/atomicmarket/routes/sales.ts`, `.../auctions.ts`, `.../buyoffers.ts`, `.../template-buyoffers.ts` (each `namespace.on('connection', ...)` with a `subscribe` handler over a fixed `availableRooms` list; entity events use `namespace.in('').emit(...)` while `fork` uses `namespace.emit(...)`), `.../atomicassets/routes/assets.ts` and `.../offers.ts` (no connection handler, all events via `namespace.emit(...)`) ## Transfers ride the offers namespace There is no `/v1/transfers` Socket.IO namespace. The transfers socket handler registers on `/v1/offers`, the same namespace the offers handler uses, so `logtransfer` broadcasts arrive as the `new_transfer` event on `/atomicassets/v1/offers` alongside the offer `create` event. A consumer that wants transfer events must connect to the offers namespace and listen for `new_transfer`; connecting to a `/v1/transfers` namespace name yields a valid but permanently silent connection. -Source: `atomicassets-api (main, f6419858) src/api/namespaces/atomicassets/routes/transfers.ts` (`createSocketApiNamespace(this.server, this.core.path + '/v1/offers')`), `.../offers.ts` (same namespace path) +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/namespaces/atomicassets/routes/transfers.ts` (`createSocketApiNamespace(this.server, this.core.path + '/v1/offers')`), `.../offers.ts` (same namespace path) ## Template buyoffers emit no socket events at this commit -`reference/api.md` records that template-buyoffer socket notifications fire only for new offers, never for cancellation or fulfillment. The source is consistent with and narrower than that: the `templateBuyofferSockets` handler emits `new_template_buyoffer` only on `lognewtbuyo` and has no cancel or fulfill branch, but at this commit that handler is defined and never called. The `atomicmarket` namespace's `socket()` wires only the sales, auction, and buyoffer handlers, so `/atomicmarket/v1/template_buyoffers` accepts connections and its `new_template_buyoffers` room exists in the dead code, yet no template-buyoffer event is broadcast. Track template-buyoffer state changes by polling `/atomicmarket/v1/template_buyoffers` rather than over the socket. Re-check this when the notifier wiring changes, since it is a one-line registration away from going live. +`reference/api.md` records that template-buyoffer socket notifications fire only for new offers, never for cancellation or fulfillment. The source matches: the `atomicmarket` namespace's `socket()` registers `templateBuyofferSockets` beside the sales, auction, and buyoffer handlers, and that handler emits `new_template_buyoffer` (room `new_template_buyoffers`) only on `lognewtbuyo`, with no cancel or fulfill branch. Track cancellation and fulfillment by polling `/atomicmarket/v1/template_buyoffers` rather than over the socket. -Source: `atomicassets-api (main, f6419858) src/api/namespaces/atomicmarket/routes/template-buyoffers.ts` (`templateBuyofferSockets` defined, emits only on `lognewtbuyo`), `src/api/namespaces/atomicmarket/index.ts` (`socket()` calls `salesSockets`, `auctionSockets`, `buyofferSockets` only; no reference to `templateBuyofferSockets` anywhere in the tree) +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/namespaces/atomicmarket/routes/template-buyoffers.ts:127-170` (`templateBuyofferSockets`, emits only on `lognewtbuyo`), `src/api/namespaces/atomicmarket/index.ts:187-190` (`socket()` registers all four handlers); live `/health` reads of `wax.api.atomicassets.io` and `test.wax.api.atomicassets.io`, both reporting `2.2.0` ## Socket connections carry no app-layer auth or rate limit The Socket.IO server registers no connection or handshake middleware, so namespace connections are unauthenticated and uncapped by the application: any client may open any namespace and, on the market namespaces, `subscribe` to any room. The express-rate-limit middleware documented in `reference/api.md` is mounted on the REST namespace paths and never sees the WebSocket upgrade, which Socket.IO intercepts on the HTTP server ahead of express, so REST rate limiting does not bound socket connections or event volume. Any connection ceiling a consumer hits in practice comes from a fronting proxy or CDN, not the API. Treat the reference deployment's actual socket exposure as operator- and edge-specific, not a software guarantee. -Source: `atomicassets-api (main, f6419858) src/api/server.ts` (`SocketServer` constructs `new Server(...)` with no `io.use(...)` and no per-namespace auth; the express `limiter` is mounted per REST namespace path in `src/api/namespaces/*/index.ts`, not on the Socket.IO server) +Source: `atomicassets-api (2.2.0, cf8bf323) src/api/server.ts` (`SocketServer` constructs `new Server(...)` with no `io.use(...)` and no per-namespace auth; the express `limiter` is mounted per REST namespace path in `src/api/namespaces/*/index.ts`, not on the Socket.IO server) ## Consumption example diff --git a/reference/api.md b/reference/api.md index da22a21..fc054df 100644 --- a/reference/api.md +++ b/reference/api.md @@ -35,7 +35,9 @@ Source: live probes of `https://wax.api.atomicassets.io/atomicmarket/v1/royaltie ## Template buyoffers keep all lifecycle states -AtomicMarket template buyoffers in the atomicassets-api follow a three-state lifecycle: `lognewtbuyo` inserts a row in state 0 (LISTED), `canceltbuyo` flips it to 1 (CANCELED), and `fulfilltbuyo` flips it to 2 (SOLD), setting the seller and inserting the fulfilled asset rows. Rows are never deleted or archived: no maintenance job cleans up CANCELED or SOLD offers, so they persist indefinitely as state markers. The `/v1/template_buyoffers` endpoint applies no state filter by default: without an explicit `state` query parameter it returns offers in all three states, so clients that only want active offers must pass `state=0`. No socket notifications are broadcast for template buyoffers at the pinned commit: the socket handler for new offers exists in the source but is never wired into the `atomicmarket` namespace, and cancellation and fulfillment have no handler at all (`reference/api-streaming.md`). Poll the endpoint rather than waiting on socket events. The filler and API state enums both encode LISTED=0, CANCELED=1, SOLD=2 and map 1:1. +AtomicMarket template buyoffers in the atomicassets-api follow a three-state lifecycle: `lognewtbuyo` inserts a row in state 0 (LISTED), `canceltbuyo` flips it to 1 (CANCELED), and `fulfilltbuyo` flips it to 2 (SOLD), setting the seller and inserting the fulfilled asset rows. Rows are never deleted or archived: no maintenance job cleans up CANCELED or SOLD offers, so they persist indefinitely as state markers. The `/v1/template_buyoffers` endpoint applies no state filter by default: without an explicit `state` query parameter it returns offers in all three states, so clients that only want active offers must pass `state=0`. Template buyoffers broadcast one socket event at the pinned tag: `templateBuyofferSockets` is wired into the `atomicmarket` namespace and emits `new_template_buyoffer` on `lognewtbuyo`, while cancellation and fulfillment have no handler at all (`reference/api-streaming.md`). Poll the endpoint for any state change past creation. Both hosted deployments report `2.2.0` on `/health`, so the wiring is live there. + +Source (socket wiring and deployment version): `atomicassets-api (2.2.0, cf8bf323) src/api/namespaces/atomicmarket/index.ts:187-190` (`socket()` registers `templateBuyofferSockets`); live `GET /health` on `wax.api.atomicassets.io` and `test.wax.api.atomicassets.io`, both reporting `2.2.0` The filler and API state enums both encode LISTED=0, CANCELED=1, SOLD=2 and map 1:1. ## The `state` field means something different on each listing endpoint diff --git a/reference/atomicassets/tables.md b/reference/atomicassets/tables.md index 3370ce3..07886ce 100644 --- a/reference/atomicassets/tables.md +++ b/reference/atomicassets/tables.md @@ -109,7 +109,7 @@ Secondary indexes: none. Written only by `setschematyp`, which fully replaces the row's `format_type` vector on every call (not additive). Never passes through `atomicdata::serialize`/`deserialize`; these are plain ABI-serialized rows, not custom-binary blobs. Changed in V2: this table does not exist in V1. -Do not treat the API's `mediatype`/`info` fields as evidence of a `schematypes` row. The atomicassets-api includes `mediatype` and `info` on every attribute of a schema's `format` in its HTTP response even when the on-chain `schematypes` table has no row for that schema: with no descriptor set it defaults `mediatype` to the attribute's own `name` and `info` to `null`. To know whether a real `FORMAT_TYPE` descriptor was set, read the `schematypes` table on chain rather than inferring it from the API's schema response. This synthesis ships in atomicassets-api 2.0.0 (live-confirmed on the wax-testnet deployment, which reports that version on `/health`); deployments on the 1.7 line, including the WAX mainnet reference deployment at the time of the read, return schema `format` entries as plain `{name, type}` with no `mediatype`/`info` fields at all. +Do not treat the API's `mediatype`/`info` fields as evidence of a `schematypes` row. The atomicassets-api includes `mediatype` and `info` on every attribute of a schema's `format` in its HTTP response even when the on-chain `schematypes` table has no row for that schema: with no descriptor set it defaults `mediatype` to the attribute's own `name` and `info` to `null`. To know whether a real `FORMAT_TYPE` descriptor was set, read the `schematypes` table on chain rather than inferring it from the API's schema response. This synthesis ships since atomicassets-api 2.0.0 and is live on both hosted deployments, which report `2.2.0` on `/health`; a WAX mainnet schema read returns the synthesized fields (`mediatype` defaulting to the attribute's own `name`, `info` as `null`). A deployment on the pre-2.0 line returns schema `format` entries as plain `{name, type}` with no `mediatype`/`info` fields at all. Source: `include/atomicassets.hpp:373-379`, `src/atomicassets.cpp:517-568` (`setschematyp`) diff --git a/reference/validation.md b/reference/validation.md index b04e7a2..e6172fa 100644 --- a/reference/validation.md +++ b/reference/validation.md @@ -12,7 +12,7 @@ This log traces how every fact in `reference/` and `guides/` was checked before - `atomicassets-contract` at `v2.0.0` - `atomicmarket-contract` at `v2.0.0` -- `atomicassets-api` at its current `main` branch state (no release tag; indexer behavior and API surface are read from the running source tree; streaming and rate-limit pages pin `f6419858`) +- `atomicassets-api` at tag `2.2.0`, commit `cf8bf323` (both hosted deployments report this version on `/health`) - `atomictools-contract` at commit `d89ce79e4` (the upstream repository has no release tag; the deployed `atomictoolsx` ABI on WAX matches this commit exactly) - `atomicassets-sdk` at tag `v2.1.1`, commit `5c70c62` (published as `@atomichub/atomicassets` 2.1.1) - `atomicmarket-sdk` at tag `v2.4.1`, commit `437300b` (published as `@atomichub/atomicmarket` 2.4.1) @@ -31,8 +31,8 @@ WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts (confi | Page | Primary source (repo + key files) | Verification tier | Notes | | --- | --- | --- | --- | -| `reference/api.md` | `atomicassets-api`: `src/api/server.ts`, `src/api/namespaces/*/openapi.ts`; live probes of `wax.api.atomicassets.io` and `test.wax.api.atomicassets.io` | both | The Swagger-UI section cites both the server routing source and live probes of `/docs`, `/openapi.json`, `/docs/swagger-ui-init.js` in one `Source:` line. The pagination-cap section is a live-observed fact against the hosted deployment. The buyoffer-lifecycle-states section describes indexer state-machine behavior with no dedicated `Source:` line in this page (see `reference/atomicassets-api.md` for the indexer side). The rate-limits section is live-observed (`ratelimit-limit: 240`, `ratelimit-policy: 240;w=60`, plus the legacy `x-ratelimit-*` set) and cross-cited to `src/api/server.ts` and the config schema. The two-sales-routes and royalty-416 sections are live-chain only, each carrying its own `Source:` line naming the probes: both sales routes and their `_count` siblings answer 200 with equal counts, the served OpenAPI document carries `/atomicmarket/v2/sales` and no `/atomicmarket/v1/sales`, and the royalty route answers 416 on mainnet for every collection and on testnet only for a collection with no config. The claim that `/v2/sales` omits waiting sales is not validated and is held in `learning/api.md` rather than stated here. | -| `reference/api-streaming.md` | `atomicassets-api` (main, `f6419858`): `src/api/server.ts`, `src/api/utils.ts`, `src/api/notification.ts`, `src/api/namespaces/*/routes/*.ts`; live Socket.IO probe of `wss://wax.api.atomicassets.io` | both | Namespace names, WebSocket-only transport, and connectivity are live-confirmed (five namespaces handshook). The event catalog, payload shapes, room semantics, transfers-on-offers quirk, and the unwired template-buyoffer handler are source-read; no socket events were observed in the probe windows. | +| `reference/api.md` | `atomicassets-api`: `src/api/server.ts`, `src/api/namespaces/*/openapi.ts`; live probes of `wax.api.atomicassets.io` and `test.wax.api.atomicassets.io` | both | The Swagger-UI section cites both the server routing source and live probes of `/docs`, `/openapi.json`, `/docs/swagger-ui-init.js` in one `Source:` line. The pagination-cap section is a live-observed fact against the hosted deployment. The buyoffer-lifecycle-states section describes indexer state-machine behavior with no dedicated `Source:` line (see `reference/atomicassets-api.md` for the indexer side), while its socket-wiring and deployment-version sentences carry their own `Source:` line naming the tag and the live `/health` reads. The rate-limits section is live-observed (`ratelimit-limit: 240`, `ratelimit-policy: 240;w=60`, plus the legacy `x-ratelimit-*` set) and cross-cited to `src/api/server.ts` and the config schema. The two-sales-routes and royalty-416 sections are live-chain only, each carrying its own `Source:` line naming the probes: both sales routes and their `_count` siblings answer 200 with equal counts, the served OpenAPI document carries `/atomicmarket/v2/sales` and no `/atomicmarket/v1/sales`, and the royalty route answers 416 on mainnet for every collection and on testnet only for a collection with no config. The claim that `/v2/sales` omits waiting sales is not validated and is held in `learning/api.md` rather than stated here. | +| `reference/api-streaming.md` | `atomicassets-api` (`2.2.0`, `cf8bf323`): `src/api/server.ts`, `src/api/utils.ts`, `src/api/notification.ts`, `src/api/namespaces/*/routes/*.ts`; live Socket.IO probe of `wss://wax.api.atomicassets.io` | both | Namespace names, WebSocket-only transport, and connectivity are live-confirmed (five namespaces handshook). The event catalog, payload shapes, room semantics, transfers-on-offers quirk, and the wired, creation-only template-buyoffer handler are source-read at the tag; no socket events were observed in the probe windows. | | `reference/atomictools/actions.md` | `atomictools-contract` (commit `d89ce79e4`): `src/link.cpp`, `src/auth.cpp`, `include/atomictoolsx.hpp`; live `get_abi` diff against `atomictoolsx` on WAX mainnet | both | Every action cites header and implementation line ranges. The full action/table list was diffed against the deployed ABI and matches the pinned source exactly; `config.version` reads `1.0.0` live. | | `reference/atomictools/tables.md` | `atomictools-contract` (commit `d89ce79e4`): `include/atomictoolsx.hpp`, `src/link.cpp`; live `get_table_rows` against `wax.greymass.com` | both | Two tables (`links`, `config`), each with its own citation. Row shapes and the `assetidshash` secondary index confirmed by live primary- and secondary-index reads. | | `reference/media.md` | Live reads of `wax.api.atomicassets.io` (templates, schemas, collections across alien.worlds, farmersworld, gpk.topps, official.wax, kogsofficial) and a public IPFS gateway (`ipfs.io`); type/layer facts drawn from `reference/atomicassets/serialization.md`, `custom-types.md`, `data-precedence.md` | both | Field-name conventions and value shapes (bare CIDv0/CIDv1, CID-plus-path) are live-observed across five major WAX collections; the media FORMAT-type convention (`image`/`string`, not `ipfs`) is live-read from schema `format`; gateway resolution is confirmed by a live `ipfs.io` fetch returning `image/webp` with WebP magic bytes. No dedicated `Source:` line consolidates the page; each section carries its own live-read citation. |