@artblocks/abx-token-api 0.1.0-alpha.22 → 0.1.0-alpha.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,1140 @@
1
+ # @artblocks/abx-token-api
2
+
3
+ ## 0.1.0-alpha.24
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [c9f7aeb]
8
+ - @artblocks/abx-sdk@0.1.0-alpha.21
9
+ - @artblocks/abx-storage@0.1.0-alpha.21
10
+ - @artblocks/abx-indexer@0.1.0-alpha.22
11
+
12
+ ## 0.1.0-alpha.23
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [84ca9dd]
17
+ - Updated dependencies [84ca9dd]
18
+ - @artblocks/abx-sdk@0.1.0-alpha.20
19
+ - @artblocks/abx-indexer@0.1.0-alpha.21
20
+ - @artblocks/abx-storage@0.1.0-alpha.20
21
+
22
+ ## 0.1.0-alpha.22
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies [b1c333d]
27
+ - Updated dependencies [b1c333d]
28
+ - @artblocks/abx-sdk@0.1.0-alpha.19
29
+ - @artblocks/abx-indexer@0.1.0-alpha.20
30
+ - @artblocks/abx-storage@0.1.0-alpha.19
31
+
32
+ ## 0.1.0-alpha.21
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [77a6248]
37
+ - Updated dependencies [77a6248]
38
+ - @artblocks/abx-sdk@0.1.0-alpha.18
39
+ - @artblocks/abx-indexer@0.1.0-alpha.19
40
+ - @artblocks/abx-storage@0.1.0-alpha.18
41
+
42
+ ## 0.1.0-alpha.20
43
+
44
+ ### Minor Changes
45
+
46
+ - c5b8ec3: `ipfs` and `arweave` metadata fields now resolve on both planes, through a gateway the collection
47
+ chooses — renderer **spec v11**.
48
+
49
+ These are the two representations that are content-addressed, the two where the locator IS the
50
+ integrity hash, and the two this protocol tells creators to prefer. They were also the two the
51
+ on-chain renderer could not serve. So `abx deploy --onchain-uri --backend ipfs|arweave` worked around
52
+ it by baking a gateway HOST into a `url` field, which welded a hostname the creator could never
53
+ migrate and made `abx_provenance` report `source: url` for bytes that live on IPFS.
54
+
55
+ The fix separates the two facts that the baked URL had fused. The CID/txid stays in the field, as
56
+ identity, under that field's own lock. The HTTPS prefix becomes a project-wide preference in two
57
+ reserved collection-scope fields, `abx_gateway_ipfs` / `abx_gateway_arweave`, with `https://ipfs.io/ipfs/`
58
+ and `https://arweave.net/` as floors. The renderer, the canonical generator, and the off-chain
59
+ resolver all read those two fields and apply the same wrap, so a token served from the chain and from
60
+ a resolver produces the same URL.
61
+
62
+ A dead or slow gateway is now a **repoint**, not a rewrite:
63
+
64
+ ```bash
65
+ abx set-gateway 0xYourContract --ipfs https://your-dedicated.mypinata.cloud/ipfs/
66
+ ```
67
+
68
+ One transaction, every token, no re-upload — and it works on fields you have already locked, because
69
+ the locked value is the CID and the gateway was never in it.
70
+
71
+ **What changes for you**
72
+
73
+ - `--onchain-uri --backend ipfs|arweave` writes the bare CID (or `cid/{id}.ext` for the O(1) directory)
74
+ as an `ipfs`/`arweave` field instead of a gateway URL as `url`/`url-template`. `--backend cloud` is
75
+ unchanged — an HTTPS CDN locator _is_ the address.
76
+ - New `abx set-gateway <addr> [--ipfs <prefix>] [--arweave <prefix>]` (`none` clears to the public
77
+ default). Two flags, so a project can pay for a dedicated IPFS gateway and leave Arweave public.
78
+ - New `--ipfs-gateway` / `--arweave-gateway` on every deploy command. Plain `--gateway` (the
79
+ storage/upload gateway) seeds the preference for the backend in use, so passing it still does what
80
+ you meant. Nothing is written when no gateway is named — silence keeps the floor live.
81
+ - `abx set-field` refuses the two gateway keys and points at `set-gateway`, which validates the
82
+ prefix, the scope and the representation. Every way to set them by hand fails silently.
83
+ - The `display.gateway` contract param is **superseded and no longer read**. It could name only one
84
+ prefix for both schemes and existed only on code projects.
85
+ - SDK: `projectGatewayPrefix`, `projectGatewayUrl`, `gatewayPrefixFrom`, `contentIdFromLocator`,
86
+ `GATEWAY_FIELD`, `GATEWAY_FLOOR`. For a host running its own resolver, `ABX_IPFS_GATEWAY` /
87
+ `ABX_ARWEAVE_GATEWAY` are now a **floor**: a project that stated a preference on chain gets that
88
+ preference from every conforming resolver, and host config only fills a silence.
89
+
90
+ **New canonical addresses** (both testnets; nothing else moved — no token implementation, factory, or
91
+ `initialize` ABI changed):
92
+
93
+ | Contract | Address |
94
+ | -------------------------------- | -------------------------------------------- |
95
+ | `AbxMetadataRenderer` (spec v11) | `0x85C1aE1F076d808fF7c1729F21B85038Fa16105E` |
96
+ | `AbxGenerator` (Sepolia) | `0xb7104AdFa6Fb5615e46E2a681A2Ff043B08fADB5` |
97
+ | `AbxGenerator` (Base Sepolia) | `0x2c1b7Cf6C54E4acBcB54FCc395f7Af88eB4fC8Ce` |
98
+
99
+ Also fixes a crash on `abx deploy-code --copies`: an undefined `bytes` reference threw a
100
+ `ReferenceError` at the end of the dry-run plan and, worse, at the confirmation prompt of a real
101
+ deploy.
102
+
103
+ ### Patch Changes
104
+
105
+ - 5b29f53: Drop-in for every reconstruct caller; new exports for hosted/self-host watch loops so they stop
106
+ reimplementing `eth_getLogs` chunking and a second per-project fetch.
107
+
108
+ **`getLogsAdaptive` is public and accepts `Address | Address[]`.** Address-list chunking defaults to
109
+ 1000 (ordinary RPC tier cap) via `addressBatch` — an RPC opinion, not a protocol constant; `0`
110
+ disables splitting. Per-tick block windows stay with the watcher (the reference one still uses 5000).
111
+
112
+ **`reconstructFromLogs(client, prior, logs, {toBlock})`** is the fold / head-read half of
113
+ `reconstructIncremental` given logs the caller already holds. `toBlock` is the inclusive scan head
114
+ those logs cover, so a capped window cannot stamp past the last block actually scanned.
115
+ `reconstructIncremental` is now a getLogs + that function.
116
+
117
+ **A Transfer no longer re-downloads every on-chain script chunk.** Chunk content has no log;
118
+ `ScriptUpdated` is the ping. `applyHeadReads` keeps the prior digest unless that ping is in `fresh`
119
+ (or the prior digest is missing). Same change helps the single-address path.
120
+
121
+ The reference token-api watcher now calls `getLogsAdaptive` for the registered set and hands each
122
+ touched project's logs to `reindex({logs, toBlock})`, so the watch tick is the only getLogs.
123
+
124
+ - Updated dependencies [c5b8ec3]
125
+ - Updated dependencies [5b29f53]
126
+ - @artblocks/abx-sdk@0.1.0-alpha.17
127
+ - @artblocks/abx-indexer@0.1.0-alpha.18
128
+ - @artblocks/abx-storage@0.1.0-alpha.17
129
+
130
+ ## 0.1.0-alpha.19
131
+
132
+ ### Patch Changes
133
+
134
+ - Updated dependencies [9c287c0]
135
+ - Updated dependencies [b5744a1]
136
+ - Updated dependencies [9c287c0]
137
+ - @artblocks/abx-sdk@0.1.0-alpha.16
138
+ - @artblocks/abx-indexer@0.1.0-alpha.17
139
+ - @artblocks/abx-storage@0.1.0-alpha.16
140
+
141
+ ## 0.1.0-alpha.18
142
+
143
+ ### Minor Changes
144
+
145
+ - 7fec2d7: Off chain, `animation_url` is now a link to this node's route instead of an inlined `data:` URI
146
+
147
+ The two URI-valued keys disagreed about the same thing. For on-chain **content**, `image` has always
148
+ been served as a link to the resolver's route — bytes should not ride inside a JSON document that
149
+ marketplaces re-fetch on every view — while `animation_url` was inlined as
150
+ `data:text/html;base64,…`. That is backwards on the rule's own logic: `animation_url` is usually the
151
+ _larger_ asset.
152
+
153
+ Worse, a reserved key's resolved value is repeated verbatim into the `artifacts` listing, so an
154
+ inlined document shipped **twice** in one response. A 3 MB on-chain HTML artwork meant roughly 8 MB of
155
+ JSON per metadata fetch, of which the sibling `image` — the small one — contributed a single line.
156
+
157
+ And the inline form was already the exception rather than the rule: a code project's `animation_url`
158
+ has always been the live-view route. Only a project with an explicit `animation` field holding
159
+ on-chain content took the `data:` path.
160
+
161
+ ```jsonc
162
+ // before // after
163
+ "image": "http://node/…/7/image", "image": "http://node/…/7/image",
164
+ "animation_url": "data:text/html;base64,PGh0…", "animation_url": "http://node/…/7/data/animation_url",
165
+ "artifacts": [ "artifacts": [
166
+ {"key":"image", "uri":"http://…"}, {"key":"image", "uri":"http://…"},
167
+ {"key":"animation_url", "uri":"data:text/…"} {"key":"animation_url", "uri":"http://…/data/animation_url"}
168
+ ] ]
169
+ ```
170
+
171
+ - **The bytes are unchanged and still on chain.** `abx_provenance` still reports
172
+ `source: inline`/`reader`/`renderer` with `status: 'on-chain'` — the route changes where the bytes
173
+ ride, not where they live. The route (`/…/data/{field}`, the data plane's own serving route) returns
174
+ them with the declared Content-Type, and 302s to a durable locator when one exists.
175
+ - **Locators are untouched.** `url`, `url-template`, and a computed `text/uri-list` still land verbatim
176
+ on both planes: they are already URIs, and re-hosting one would hide it.
177
+ - **The on-chain renderer is unchanged** — a contract has no URL space, so `tokenURI` still inlines the
178
+ document, and that self-contained form is the durability floor. **No contract changed and no address
179
+ moved.**
180
+ - **`abx inspect`'s on-chain readout is unaffected**; it reads the contract's `tokenURI`, not this
181
+ document.
182
+
183
+ Adopts the position the abx-services team argued while upgrading their resolver — theirs already served
184
+ the route form, so this closes the divergence in their favor and the reference is what changed. The
185
+ class was invisible to the cross-implementation parity audit, whose fixtures are client-free while the
186
+ `renderer` representation needs a live `eth_call`; a new `animation-route.test.ts` boots a real server
187
+ on a real socket and follows the link, because a value promising bytes at a URL is a dead link if
188
+ nothing serves that URL.
189
+
190
+ - 7fec2d7: An edition's per-id cap now folds from the log, so a capped edition stops reporting itself as open
191
+
192
+ `DefaultMaxSupplySet` — added in the contract-audit release precisely so the log could tell an open
193
+ edition from a capped one — reached the ABI and stopped there. `reconstruct.ts` had no fold case, so
194
+ the collection-wide default never landed in `ProjectState`, and **the common shape was the broken
195
+ one**: `--copies N` sets the cap at `initialize` and never calls `setMaxSupply`, so no id has a
196
+ `MaxSupplyUpdated` of its own and every capped edition ever deployed folded as _uncapped_. The
197
+ head-read lane (`listTokens`, which calls `maxSupply(id)`) said `N` the whole time — one field name,
198
+ two answers, on the value that decides whether a buy button renders. Found by the abx-services team
199
+ while upgrading their resolver/indexer to this release.
200
+
201
+ - **`ProjectState.defaultMaxSupply`** — the collection-wide per-id default (`'0'` = open; `null` ⇒ not
202
+ an edition). The 1155 analogue of `maxInvocations`.
203
+ - **`TokenState.maxSupply` is now the _effective_ cap** — an id's own override, else that default —
204
+ and so equals `maxSupply(id)` on chain and its head-read twin `TokenRow.maxSupply`. Previously it
205
+ was the override alone, and absent otherwise.
206
+ - **`TokenState.maxSupplyOverridden`** — whether this id has ever been explicitly overridden. The
207
+ distinction the on-chain getter cannot express (`maxSupply(id)` returns `0` for both "never capped"
208
+ and "deliberately closed") and the log now can, plus a signal that the monotonic never-increase rule
209
+ is in force for that id.
210
+ - **`editionCapOf(token)`** — new SDK export returning `{kind:'open'} | {kind:'capped', cap} |
211
+ {kind:'closed'}`, so nothing re-derives the rule. Used by the dashboard and `abx tokens`; pass a
212
+ head-read row and a `'0'` cap reads `open`, which is all a state read can honestly say.
213
+ - **The dashboard stops lying twice.** It tested `maxSupply` for truthiness: a `--copies 10` id read
214
+ "(open)", and — because `'0'` is a non-empty string — an id deliberately closed would have read
215
+ "12 / 0". It now reads "12 / 10" and "12 (closed — no more can be minted)".
216
+ - **Projection columns**: `projects.default_max_supply`, `tokens.max_supply_overridden` (migration-added,
217
+ so an existing store keeps working; NULL stays NULL rather than becoming a fabricated `0`).
218
+
219
+ Also in this pass, from the same review:
220
+
221
+ - **`SPINE_EVENT_DOC` had drifted to 41 of 52 events**, so eleven folded events carried an empty
222
+ human description and defaulted to Register 2 — including `TransferValidatorUpdated`, which belongs
223
+ to Register 1, and the factory's `Deployed`. All eleven are described, and a new
224
+ `spine-doc-coverage.test.ts` fails the build if the table and the spine ABI drift apart again in
225
+ either direction.
226
+ - **One provenance body in the resolver's `renderer` branch.** The `text/uri-list` arm hand-built an
227
+ object byte-identical to `onChainProv(field, 'renderer', …)` — same keys, same order, the note string
228
+ typed a second time — so the only thing that branch could do was drift from `sourceNote`.
229
+ - **Dead `abx_params`-era test scaffolding deleted** (a `paramsOf(json) => json.abx_params` reader, an
230
+ RPC stub, a schema factory), including an import of a `ParamMember` type that no longer exists —
231
+ which nothing caught, because `tsconfig` includes only `src`, so test files are never typechecked.
232
+
233
+ ### Patch Changes
234
+
235
+ - Updated dependencies [7fec2d7]
236
+ - @artblocks/abx-sdk@0.1.0-alpha.15
237
+ - @artblocks/abx-indexer@0.1.0-alpha.16
238
+ - @artblocks/abx-storage@0.1.0-alpha.15
239
+
240
+ ## 0.1.0-alpha.17
241
+
242
+ ### Minor Changes
243
+
244
+ - 1b6b741: Rename "artist" to "creator" throughout, including the protocol vocabulary
245
+
246
+ ABX is a tool for creators generally. Art remains a first-class use case, but it should not be baked
247
+ into the product's framing — and it was, down to the on-chain identifiers.
248
+
249
+ **Breaking, and deliberately a clean break** (pre-launch, folded into the one redeploy that four
250
+ rounds of security remediation already required — deferring would have meant either living with the
251
+ old vocabulary permanently or spending a second deployment on a word):
252
+
253
+ - `AuthOption` is now `Creator`, `TokenOwner`, `Address`, `CreatorOrTokenOwner`, `CreatorOrAddress`,
254
+ `TokenOwnerOrAddress`, `CreatorOrTokenOwnerOrAddress`. Order is unchanged, so the `uint8` values
255
+ are identical — only the names moved. Users type these: `--schema key:Type:Creator`.
256
+ - The on-chain collection metadata field keys `artist` / `artist_links` are now `creator` /
257
+ `creator_links`. These are `bytes32` keys written on-chain and emitted verbatim into every
258
+ collection's `contractURI` JSON.
259
+ - CLI flags `--artist` / `--artist-links` are now `--creator` / `--creator-links`. No aliases.
260
+ - `@artblocks/abx-token-api` renames its `art` module to `content`: `generateArt` →
261
+ `generateContent`, `artContentHash` → `contentHash`.
262
+ - `AbxMetadataRenderer.SPEC_VERSION` is **7**, and `isCurrentRenderer` gates on it. A v6 renderer
263
+ emits a different member key, so it is genuinely behind, not cosmetically so.
264
+
265
+ Package CHANGELOGs are left alone on purpose: they record what past alpha versions actually shipped,
266
+ and rewriting them would make the release history lie about flag names that really were `--artist`.
267
+
268
+ - 1b6b741: Renderer spec v10 projects four long-reserved keys; edition trust anchors move
269
+
270
+ **Renderer spec v10.** Four keys the spec reserved from the start were projected on chain by nothing:
271
+ `background_color` and `youtube_url` (token), `banner_image` and `featured_image` (collection). A
272
+ documented reserved key that no surface emits is a hole rather than a saved byte — a creator sets it,
273
+ observes nothing, and cannot tell whether the tool or the marketplace is at fault. All four now project
274
+ when their representation is chain-reachable, and are omitted when unset. `isCurrentRenderer` gates on
275
+ 10; a v9 renderer silently drops them.
276
+
277
+ **`collaborators` is no longer a reserved key.** It was in the registry, projected by neither plane, and
278
+ because reserved keys are excluded from the resolver's `artifacts` listing, setting it on chain did
279
+ nothing observable anywhere. It is now an ordinary creator key — which means it **does** list in
280
+ `artifacts`. Put a collaborator list in a named artifact (`abx attach`) or in `description`.
281
+
282
+ **The resolver's two collection image keys are now symmetric with the renderer.** `featured_image` is a
283
+ top-level `contractURI` key instead of artifacts-only, and `banner_image` projects for every
284
+ chain-reachable representation instead of only `url` (an `inline` or `reader` banner used to vanish).
285
+
286
+ **Docs now carry the three-list split** — which keys both planes project, which are the off-chain
287
+ resolver's alone by design (`abx_provenance.status`, operator overlay, effects/live-view, the
288
+ `artifacts` listing, collection `image` courtesy, `image_data`, off-chain-decode values), and which are
289
+ not reserved at all. That split is the thing to read before adding a key to either surface: drift on a
290
+ key both planes _can_ emit is what produced three earlier remediations.
291
+
292
+ **Addresses.** The three edition factories move (trust anchors) along with their implementations, and
293
+ the renderer moves. `AbxEditionLib`, `AbxMetadataLib`, `AbxCodeLib`, `AbxParamsLib`, all three ERC-721
294
+ factories, the chunk store, the seed source, both minters and both generators keep their addresses.
295
+ Existing clones keep running their frozen implementations; re-point a live collection at the new
296
+ renderer with `abx set-renderer` rather than redeploying it.
297
+
298
+ - 1b6b741: Independent-audit remediation: `abx_provenance` reshaped, `abx_params` removed, hook ABI widened
299
+
300
+ An independent adversarial audit of the contracts found no Critical or High issue, no theft path and
301
+ no cross-project reach — but four Mediums, eight Lows and five architectural notes. Everything is
302
+ addressed. Consumer-visible changes:
303
+
304
+ **`abx_provenance` entries are now `{field, source, note}`.** They lost `onChain`, which was wrong in
305
+ both directions — a `url` reported `false` although the URL string is stored on chain, while an
306
+ `inline` value of `"https://…"` reported `true` for a pure pointer — and `verifiedAgainstChain`,
307
+ which was hardcoded `null` on every entry. `source` says where the bytes came from; whether a value
308
+ resolves on chain is visible in the value, and that judgment belongs to the reader. The off-chain
309
+ resolver drops `onChain` for the same reason (it was exactly `status === 'on-chain'`) and keeps
310
+ `status`, which it can genuinely compute.
311
+
312
+ **`abx_params` no longer appears in `tokenURI`.** It was emitted by the on-chain renderer AND the
313
+ resolver and parsed back by nobody. Params enumerate directly from the contract — `tokenParamKeys`,
314
+ `tokenParam`, `paramSchemaKeys` — which is canonical, needs no indexer, and is what a chain-only
315
+ reader should use; a code project's script still receives them through `tokenData`. Traits for
316
+ marketplaces belong in `attributes`, unchanged.
317
+
318
+ **A computed `image` locator now lands verbatim.** A field renderer returning
319
+ `("text/uri-list", "ipfs://…")` was being data-wrapped into `data:text/uri-list;base64,…`, which no
320
+ marketplace dereferences. `image` and `animation_url` now share one implementation of the rule.
321
+
322
+ **`IAbxTransferHook.onTokenTransfer` gains `operator` and `amount`.** Without them a hook on a
323
+ shared-supply ERC-1155 cannot tell a real transfer from a zero-amount no-op — which let any address
324
+ fire the param lifecycle for an id it held no copy of. The token also refuses to notify on
325
+ zero-amount and self-transfers.
326
+
327
+ **`pingURI` is owner-only**, and `paramSchemaHead` / `selectOption` are new: resolving one selected
328
+ option no longer copies the whole option table on every render.
329
+
330
+ `AbxMetadataRenderer.SPEC_VERSION` is 8. Every canonical address moves — every token type is now
331
+ library-linked, including the two ERC-721 image factories, because the metadata field store was
332
+ externalized into the new `AbxMetadataLib` to restore EIP-170 headroom before any fix was stacked on
333
+ top of 187 bytes of margin.
334
+
335
+ The CLI now warns when `--copies` meets a holder-writable `--schema`: on an edition, params belong to
336
+ the id, so every holder shares one value and the last writer wins.
337
+
338
+ - 1b6b741: Upgrading from alpha.12/alpha.13 — what breaks, in one place
339
+
340
+ This release lands the contract-audit branch. It is a **compatibility break with earlier alphas**, which
341
+ is fine at this stage (greenfield alpha, no backwards-compatibility promise) but is worth having in one
342
+ list rather than spread across a dozen changesets. Nothing here is a deprecation with a migration
343
+ window; the old shapes are gone.
344
+
345
+ **Every canonical address moved**, and four manifest fields are new (`metadataLib`, `paramsLib`,
346
+ `codeLib`, `editionLib` — the delegatecalled write-path libraries). Resolve through `getDeployment` and
347
+ the upgrade is transparent; anything you have _stored_ keyed by a factory address is not. A project
348
+ indexed from a superseded anchor reads `isCanonical: false` against the new manifest — correct, since it
349
+ is what a creator sees if they don't redeploy, but it is a data question, not a version bump.
350
+
351
+ **The metadata document changed: renderer spec v4 → v9.** If you assemble or assert on it:
352
+
353
+ - `abx_provenance` entries lost `onChain` (it was exactly `status === 'on-chain'`, and wrong in both
354
+ directions) and `verifiedAgainstChain` (hardcoded `null` on every entry). `source` and `status` stay.
355
+ - The `abx_params` block is gone from **both** lanes. Params enumerate from the contract —
356
+ `tokenParamKeys` / `tokenParam` / `paramSchemaKeys` — which is canonical and needs no indexer; a code
357
+ project's script still receives them through `tokenData`.
358
+ - `artist` / `artist_links` are now `creator` / `creator_links` (reserved on-chain collection field
359
+ keys), and `AuthOption.Artist*` is `AuthOption.Creator*` (numeric values unchanged).
360
+ - A computed `image` locator (`text/uri-list`) lands **verbatim** instead of being wrapped into a
361
+ `data:` URI no marketplace dereferences.
362
+ - The on-chain renderer no longer duplicates a computed image into `artifacts`. The off-chain resolver
363
+ still emits the complete listing — that asymmetry is specified, not a bug.
364
+ - Four long-reserved keys now project on chain: `background_color`, `youtube_url` (token) and
365
+ `banner_image`, `featured_image` (collection). `featured_image` also becomes a top-level resolver key
366
+ instead of artifacts-only, and `banner_image` projects for every chain-reachable representation
367
+ rather than `url` alone.
368
+ - `collaborators` is **no longer reserved** — projected by neither plane and excluded from `artifacts`
369
+ by its own reserved-ness, so it did nothing. It is now an ordinary creator key that does list.
370
+
371
+ **`tokenData` divergences fixed, so rendered output can change:** a contract-scope `seed` no longer
372
+ leaks into a token's `tokenData` (the on-chain generator never read one), `seed`'s wire format is always
373
+ raw 32-byte hex regardless of schema, and `</script` escaping is case-insensitive to match the on-chain
374
+ generator. Cached renders for affected code projects should be re-derived.
375
+
376
+ **Removed:** `paramKeysOf`, `paramSchemaOf`, `paramDataOf` on `AbxMetadataRenderer` — orphaned when
377
+ params left `tokenURI`. Read the token directly instead.
378
+
379
+ **Two hook interfaces widened** (`ConfigurableParams` is extension **v3**; a hook built for the old
380
+ signature reverts against a v3 token): `IAbxTransferHook.onTokenTransfer` gained `operator` + `amount`;
381
+ `IAbxConfigureHook.onParamConfigured` gained `dataLength` + `dataBlobAddress`.
382
+
383
+ **Additive:** `ParamHooksFrozen` folds into `ParamHooks.locked`; `paramSchemaHead` + `selectOption`
384
+ replace pulling a whole option table to resolve one index; `readParamHooks`; `abxJs()` / `gunzipScript()`
385
+ on the generator for piecewise document assembly; and the three ERC-1155 ABIs now carry
386
+ `MetadataUpdate` / `BatchMetadataUpdate` / `DefaultMaxSupplySet`, which were emitted but not decodable.
387
+
388
+ ### Patch Changes
389
+
390
+ - Updated dependencies [1b6b741]
391
+ - Updated dependencies [1b6b741]
392
+ - Updated dependencies [1b6b741]
393
+ - Updated dependencies [1b6b741]
394
+ - Updated dependencies [1b6b741]
395
+ - Updated dependencies [1b6b741]
396
+ - Updated dependencies [f64a31f]
397
+ - Updated dependencies [1b6b741]
398
+ - Updated dependencies [1b6b741]
399
+ - Updated dependencies [1b6b741]
400
+ - Updated dependencies [1b6b741]
401
+ - Updated dependencies [1b6b741]
402
+ - Updated dependencies [1b6b741]
403
+ - Updated dependencies [1b6b741]
404
+ - Updated dependencies [1b6b741]
405
+ - Updated dependencies [1b6b741]
406
+ - Updated dependencies [1b6b741]
407
+ - @artblocks/abx-sdk@0.1.0-alpha.14
408
+ - @artblocks/abx-indexer@0.1.0-alpha.15
409
+ - @artblocks/abx-storage@0.1.0-alpha.14
410
+
411
+ ## 0.1.0-alpha.16
412
+
413
+ ### Patch Changes
414
+
415
+ - Updated dependencies [528c6c6]
416
+ - @artblocks/abx-sdk@0.1.0-alpha.13
417
+ - @artblocks/abx-indexer@0.1.0-alpha.14
418
+ - @artblocks/abx-storage@0.1.0-alpha.13
419
+
420
+ ## 0.1.0-alpha.15
421
+
422
+ ### Minor Changes
423
+
424
+ - afa9dd4: ERC-1155 editions ship with full parity: a Series is many unique tokens (721); an **Edition** is
425
+ many copies of a token (1155). The creator's word is **copies** — `abx deploy img.png --copies 100`
426
+ (a single-artwork edition, `open` = uncapped), `abx deploy-series ./art --copies 50` (each image an
427
+ edition), `abx deploy-code sketch.js --copies 25` (code editions). Without `--copies`, the 721
428
+ lanes are unchanged.
429
+
430
+ Three new canonical contracts twin the 721 lineup — `OneOfOneEdition`, `EditionImage`,
431
+ `EditionCode` — plus `AbxFixedPriceMinter1155`, a per-id fixed-price sale singleton
432
+ (`minter configure/buy --token-id [--quantity]`). New owner ops: `mint --token-id --amount`,
433
+ `transfer --amount`, `set-max-supply` (per-id cap, only ever decreases), `ping-uri` (re-emit the
434
+ native `URI` event after a re-point). ERC-1155C creator-token enforcement is the same `--721c`
435
+ opt-in (same validator registry and ERC-165 ids as 721C). Editions announce one new extension
436
+ (`abx.extension.edition-supply`, per-id supply/cap); everything else — metadata fields, params,
437
+ royalties, renderer, storage, effects — is the same protocol surface on both standards.
438
+
439
+ **Integrator surface (audience line):** additive only. `ProjectState.contractType` gains
440
+ `'1of1-edition' | 'edition' | 'edition-code'`; `TokenState` gains `supply`/`maxSupply`/`holders`
441
+ (editions only; `minted` means supply > 0 there); the spine ABI now decodes
442
+ `TransferSingle`/`TransferBatch`/`URI`/`MaxSupplyUpdated`; `ChainDeployment` gains the four
443
+ edition anchors (recorded for Sepolia + Base Sepolia, CREATE2-identical); token-api summaries gain
444
+ an optional `copies` field. No existing export, route, column, or event shape changed.
445
+
446
+ ### Patch Changes
447
+
448
+ - Updated dependencies [afa9dd4]
449
+ - Updated dependencies [8c254d5]
450
+ - @artblocks/abx-sdk@0.1.0-alpha.12
451
+ - @artblocks/abx-indexer@0.1.0-alpha.13
452
+ - @artblocks/abx-storage@0.1.0-alpha.12
453
+
454
+ ## 0.1.0-alpha.14
455
+
456
+ ### Patch Changes
457
+
458
+ - d40caf4: Requested by an integrator (abx-services) — the conformance surface now lives in the neutral
459
+ layer. Five pieces that only token-api or storage exposed before, and that any third-party
460
+ resolver needs to reproduce the reference behavior byte-for-byte, move into `@artblocks/abx-sdk`:
461
+
462
+ - **The generator-document family** (`ABX_JS`, `escapeInlineScript`/`escapeInlineJson`,
463
+ `buildGeneratorDocument`, `injectTokenDataIntoHtml` — new `src/generator-document.ts`): pure
464
+ string operations with no resolver-specific behavior, so the SDK, `abx preview`, and any
465
+ third-party provider now share one definition instead of the CLI importing token-api just for
466
+ this.
467
+ - **The registry-dependency family** (`DEPENDENCY_REGISTRY_ABI`, `activeRegistry`,
468
+ `resolveRegistryDep`, `registryDepUrl`, `dependencyScriptTags`, `URL_BUDGET_BYTES`), merged into
469
+ the SDK's existing `deps.ts` alongside its registry-pointer logic. `node:zlib` can't come along
470
+ — SDK core has to stay reachable from a browser bundle — so decompression is now an INJECTED
471
+ `inflate?: (bytes: Uint8Array) => Uint8Array` threaded through `resolveRegistryDep`/
472
+ `dependencyScriptTags`; omitting it when a resolved dep actually needs decompressing throws a
473
+ new typed `InflateRequiredError` naming the fix, rather than crashing opaquely or silently
474
+ degrading. `@artblocks/abx-sdk/node` gains `nodeInflate` (a one-line `gunzipSync` wrapper) so a
475
+ Node host wires it in one line; a browser host passes a `DecompressionStream`-based
476
+ implementation instead. (Merge note: the prior `dependencyRegistryReadAbi` and token-api's
477
+ `DEPENDENCY_REGISTRY_ABI` declared the identical `getDependencyDetails` entry twice — deduped
478
+ into one array that also carries `getDependencyScript`.)
479
+ - **`contentTypeFromPath`** (new `src/mime.ts`): the MIME extension-map lookup, verbatim.
480
+ - **Gateway resolution, split pure/env** (new `src/gateways.ts`): `resolveGatewayBase` is now PURE
481
+ (no env read — takes resolved `{ipfs?, arweave?}` overrides) and `gatewayUrlFor` moves alongside
482
+ it; `gatewayConfigFromEnv()` is the one place that reads `ABX_IPFS_GATEWAY`/
483
+ `ABX_ARWEAVE_GATEWAY`, kept separate so a host with its own gateway config never has to touch
484
+ `process.env` through this module at all.
485
+ - **`exponentialBackoffDelay(attempt, baseMs, capMs)`** (`util.ts`): `min(baseMs * 2^(attempt-1),
486
+ capMs)`, 1-indexed like the existing `linearBackoffDelay`. Prefer it for a sustained rate limit
487
+ or an overloaded upstream; `linearBackoffDelay` stays right for a one-off transient failure.
488
+ `service.ts`'s own retry ladder is unchanged (still linear — its rationale comment stands).
489
+
490
+ Token-api and storage keep every existing export working, re-exported from the SDK where the
491
+ implementation moved — no removals, and token-api's own `nodeInflate`-pre-wired wrappers mean its
492
+ internal call sites (`code.ts`'s document assembly, `deps.ts`'s `depStatusReport`) needed no
493
+ signature changes at all. The browser-bundle test (`packages/sdk/test/browser-bundle.test.ts`)
494
+ stays green with all of this now exported from the core index — proof that the injected-`inflate`
495
+ design actually keeps `node:zlib` out of the bundle.
496
+
497
+ - d40caf4: Two first-party consumers adopt the backoff/gateway/data-URI helpers hoisted into `@artblocks/abx-sdk`
498
+ last round, and `mint-page` refuses a 1/1 target instead of scaffolding a page that hangs. Drop-in for
499
+ everyone — no behavior change in the adoption itself, and the mint-page refusal only fires for a
500
+ contract type the page never worked against.
501
+
502
+ **`token-api`'s chain watcher now calls `exponentialBackoffDelay`/`linearBackoffDelay` instead of
503
+ re-deriving them inline.** `dueForRetry`'s failed-project retry and the tick loop's repeated-failure
504
+ delay were the sdk helpers reimplemented by hand; both now call the shared functions, with the exact
505
+ index mapping (`min(attempts, 8) + 1` into the 1-indexed exponential helper) that reproduces identical
506
+ delays for every `attempts`/`failures` value — proved by an old-formula-vs-sdk-helper equality table
507
+ in `packages/token-api/test/watcher-backoff.test.ts` (attempts/failures 0..12, several `baseMs`
508
+ values, and the cap boundary explicitly).
509
+
510
+ **The `mint-page` template's generated app now reads gateway URLs and `data:` URIs through the sdk**
511
+ (`gatewayUrlFor`/`resolveGatewayBase`/`parseDataUri`) instead of a hand-rolled `ipfs://`-slicing
512
+ `toGateway` and a hand-parsed comma-split in `fetchJson`. Same output for every input (plain
513
+ `https://`, empty string, `ipfs://`, `ar://`, base64 and percent-encoded `data:` bodies) — verified
514
+ byte-for-byte before the swap, and pinned by a new mintpage.test.ts assertion that the emitted
515
+ `lib/meta.ts` imports the sdk helpers and no longer contains the old hand-rolled body.
516
+
517
+ **`mint-page` now refuses a non-Series target.** The generated page reads `maxInvocations`/`paused`
518
+ and mints through the shared fixed-price minter — a 1/1 (`OneOfOneImage`) has none of that, so pointing
519
+ `mint-page` at one silently wrote a page that sat on "Loading…" forever with no diagnostic. `cmdMintPage`
520
+ now probes the same ERC-165 `supportsInterface(IAbxSequentialMint)` `sdk/anchors.ts`'s
521
+ `isCurrentSeriesFactory` uses, and refuses with a `CliError` pointing at `abx deploy-series --dir
522
+ <folder> --count 1` (matching the existing guidance in `sales.mdx`). A probe that can't run at all (RPC
523
+ unreachable) is left undecided and does NOT refuse — scaffolding stays usable offline, same tolerance
524
+ the existing best-effort `name()` read already has.
525
+
526
+ Also folded in: `PUBLIC_RPC` (the mint-page template's keyless-RPC table) gains `base` and
527
+ `base-sepolia` — the toolkit's own default chain was missing from it, so every default-chain deploy
528
+ needed a manual `--rpc` just to scaffold its own mint page.
529
+
530
+ - Updated dependencies [d40caf4]
531
+ - Updated dependencies [d40caf4]
532
+ - @artblocks/abx-sdk@0.1.0-alpha.11
533
+ - @artblocks/abx-storage@0.1.0-alpha.11
534
+ - @artblocks/abx-indexer@0.1.0-alpha.12
535
+
536
+ ## 0.1.0-alpha.13
537
+
538
+ ### Patch Changes
539
+
540
+ - 11fa933: The remaining phases of the simplification refactor that hadn't yet gotten a changeset: the CLI's
541
+ internal module split, the token-api/effects/mint-page convergence on the SDK, the shipped skill's
542
+ rewrite for the simplified surface, and a new SDK README.
543
+
544
+ - **`abx`'s `main.ts` split into domain command modules** (`commands/{deploy,project,reads,service,
545
+ scaffold,storage}.ts`, shared `output.ts`/`errors.ts`), with one exit-discipline rule
546
+ (`process.exitCode` + return, or a typed `CliError`, everywhere — bare `process.exit` only at the
547
+ entry guard, the top-level catch, and the keep-alive SIGINT handler). Purely internal: a 207-fixture
548
+ byte-diff matrix (every help text, dry-run, error path, and exit code) confirmed identical output
549
+ before and after.
550
+ - **token-api / effects / mint-page converge on the SDK**: `@artblocks/abx-storage` gains one
551
+ `resolveGatewayBase` (`readiness.ts`), replacing three near-identical copies (two in token-api, one
552
+ inline in storage itself); token-api exports `buildGeneratorDocument` so the CLI's `abx preview`
553
+ consumes the real generator-document assembler instead of a hand-kept duplicate; the effects runner
554
+ now resolves its config via the SDK's `readEnv` and gets a `makePublicClient` fallback transport, so
555
+ `ABX_RPC_URL` accepts a comma-separated failover list like every other RPC var; the scaffolded
556
+ mint-page app now imports ABIs from `@artblocks/abx-sdk/abi` and a browser-safe `makePublicClient` +
557
+ typed `readSaleConfig` instead of hand-rolled fetch/decode, and pins its generated `package.json` to
558
+ the SDK's _resolved_ version via a new `@artblocks/abx-sdk/package.json` export (alpha version
559
+ counters diverge per package under changesets, so pinning the CLI's own number could produce an
560
+ unsatisfiable range).
561
+ - **The shipped skill (`.claude/skills/abx-self-host/`) is rewritten for the surface phases 0–5
562
+ actually shipped**: every warning made obsolete by an enforcement is deleted rather than softened —
563
+ predict-only deploy-preview addresses, the `approvals N` line, the single `ABX_REMOTE_SELF_*`
564
+ credential grammar, `doctor`'s version/provenance ladder, `storage show --check`, the render/storage
565
+ combo validator's dry-run row, and `ABX_DEPLOYER_PK` as the only key name. Retired names swept from
566
+ `dev-loop-test`, the agent-eval scenarios, and spec prose. The skill ships bundled inside this CLI
567
+ package (co-versioned via `SKILL.md` frontmatter), so it rides this same patch.
568
+ - **New `packages/sdk/README.md`**: what the SDK is, the send-injection model (`PreparedTx` +
569
+ `SendTx`, `makeHotSender` for a hot key, bring-your-own for a wallet/multisig), a complete
570
+ deploy → upload → mint → read walkthrough against real exports, and browser-use notes (explicit
571
+ `rpcUrls`, no env, the `/node` subpath is Node-only). Included in the npm tarball automatically
572
+ (README is one of the files npm always packs, regardless of the `files` allowlist).
573
+
574
+ - Updated dependencies [11fa933]
575
+ - Updated dependencies [11fa933]
576
+ - Updated dependencies [11fa933]
577
+ - Updated dependencies [11fa933]
578
+ - @artblocks/abx-sdk@0.1.0-alpha.10
579
+ - @artblocks/abx-storage@0.1.0-alpha.10
580
+ - @artblocks/abx-indexer@0.1.0-alpha.11
581
+
582
+ ## 0.1.0-alpha.12
583
+
584
+ ### Patch Changes
585
+
586
+ - df298d8: Fix three ways the reference resolver misreported on-chain state — and the head-read bug underneath one of them
587
+
588
+ From an engineering audit by the abx-services team (2026-08-04), who run the same protocol on a
589
+ hosted node against real testers and so reach long-tail states a single-node run rarely does. All
590
+ three findings reproduced. Verifying the second one turned up a fourth defect that was its actual
591
+ cause, and that one is the most consequential of the set.
592
+
593
+ **1 · A renderer-computed TEXT field was dropped entirely.** `resolveImage` had an `R.renderer`
594
+ branch; `resolveText` did not. So a `renderer`-represented `description`, `external_url`,
595
+ `animation_url`, `background_color`, `youtube_url`, or `name` matched no branch and resolved **as if
596
+ unset** — silently replaced by the operator's off-chain value with provenance reporting `off-chain`,
597
+ or omitted from the metadata altogether. A creator who committed a computed field on-chain got the
598
+ operator's version served instead, and nothing errored anywhere.
599
+
600
+ `resolveText` now mirrors `AbxMetadataRenderer._appendText`'s `R_RENDERER` arm exactly, because the
601
+ two must agree: with `tokenURIRenderer` set the chain assembles this JSON and the resolver merely
602
+ re-serves it, so any difference is a resolver contradicting the token's own `tokenURI`. That means
603
+ the **declared** contentType is used verbatim (`renderer` is the one representation that types itself
604
+ on-chain) rather than an assumed `text/html`, and a `text/uri-list` result (RFC 2483 — the canonical
605
+ generator's directory branch) lands as the locator it is, never data-wrapped. This is a deliberate
606
+ divergence from the shape the audit recommended (pointing at the node's own `/data/{field}` route):
607
+ parity with the on-chain renderer is the stronger constraint here. A field renderer that reverts now
608
+ degrades that one field — with provenance saying the on-chain attempt failed, not a clean
609
+ `off-chain` — instead of being indistinguishable from an unset field.
610
+
611
+ **2 · The `name` fallback asserted an ERC-721 `name()` read it had not performed.** When
612
+ `state.name` is null the served value is the raw contract address, and the note still said
613
+ `on-chain (ERC-721 name() + #id)`. A hosted node served `"name": "0xb844…c35e56 #0"` beside that note
614
+ while the contract's `name()` was `"ABXdoku"`. The wrong name is cosmetic; provenance is the surface
615
+ you would point an artist at to audit their own metadata, so a false positive there costs more. The
616
+ note is now conditional, and deliberately does not adjudicate _why_ the value was empty — the
617
+ projection cannot tell an unnamed contract from a failed read, and guessing is how the original note
618
+ came to lie.
619
+
620
+ **3 · The live view answered "not a code project" for a code project that was merely mid-index.**
621
+ `/a/` collapsed every `resolveLiveView` null into one confident verdict about the **contract**, when
622
+ at least one is a statement about the **index**: `chunkCount` is a head read, so a template-mode
623
+ project whose chunks have not been folded in yet reads as zero. A tester got
624
+ `live · caught up · 127 events · 32 tokens` from the node and this 404, concluded the resolver did
625
+ not recognise the SeriesCode factory, and a `--full` re-index did not clear it — the confident
626
+ wording sent them looking in the wrong place. The route now consults `contractType` (folded from the
627
+ deployed extensions, so it knows before any code lands) and answers **503 + Retry-After** for "not
628
+ yet" versus 404 for "not ever". Both verdicts are answered before the chain client is touched, since
629
+ neither needs an RPC. The three-way decision is extracted as `liveViewAvailability(state)` so it has
630
+ a test seam at all.
631
+
632
+ **4 · The head-read multicall failed _whole_ on the flagship lane, and took identity, trust, and the
633
+ URI lane with it.** This was not in the audit — it is why finding 2 fires, found by reproducing it
634
+ instead of accepting "an RPC hiccup". `applyHeadReads` batched `name`, `symbol`, `owner`,
635
+ `contractURI`, `isAbxClone`, `implementation`, the four URI-config getters, and **one `tokenURI` per
636
+ token** into a single multicall. A multicall is one `eth_call`: on a fully-on-chain project
637
+ `tokenURI(id)` assembles the entire metadata document on-chain, so a handful of those legs exceeds a
638
+ public node's budget and _every_ leg in the aggregate reports failure — including legs that answer
639
+ fine alone.
640
+
641
+ Measured on Base Sepolia `0xB844F4D2137a8Ce785Cbc80D281A36DBD1c35E56` (32 tokens, chain-complete):
642
+ 1 tokenURI leg → 5/5 succeeded; **4 legs → 0/8 succeeded**, while `name()` on its own returned
643
+ `"ABXdoku"`. Every resolver indexing that collection therefore believed it had no name, no symbol,
644
+ no `contractURI`, no canonical proof — and **no `tokenURIRenderer`, i.e. that it was not in the
645
+ on-chain-URI lane at all.** Deterministic for any sizable on-chain collection, not a hiccup.
646
+
647
+ Reads are now split by cost class: the cheap fixed-size ones that decide a project's identity and
648
+ lane go in a batch of their own and can never be collateral damage, while the unbounded ones
649
+ (`contractURI`, per-token `tokenURI`, script chunks) read in small chunks through a helper that
650
+ re-asks a failed chunk one leg at a time before believing it. After the fix, that same contract
651
+ reconstructs with its name, symbol, both URI renderers, and all 32 token URIs intact.
652
+
653
+ 12 regression tests across the four, including the null-name case both surfaces missed because every
654
+ parity fixture on both sides hardcoded a non-null name.
655
+
656
+ - Updated dependencies [df298d8]
657
+ - Updated dependencies [df298d8]
658
+ - Updated dependencies [df298d8]
659
+ - @artblocks/abx-sdk@0.1.0-alpha.9
660
+ - @artblocks/abx-storage@0.1.0-alpha.9
661
+ - @artblocks/abx-indexer@0.1.0-alpha.10
662
+
663
+ ## 0.1.0-alpha.11
664
+
665
+ ### Patch Changes
666
+
667
+ - Updated dependencies [e325b46]
668
+ - @artblocks/abx-sdk@0.1.0-alpha.8
669
+ - @artblocks/abx-indexer@0.1.0-alpha.9
670
+ - @artblocks/abx-storage@0.1.0-alpha.8
671
+
672
+ ## 0.1.0-alpha.10
673
+
674
+ ### Patch Changes
675
+
676
+ - Updated dependencies [1b50f9d]
677
+ - Updated dependencies [1b50f9d]
678
+ - @artblocks/abx-sdk@0.1.0-alpha.7
679
+ - @artblocks/abx-storage@0.1.0-alpha.7
680
+ - @artblocks/abx-indexer@0.1.0-alpha.8
681
+
682
+ ## 0.1.0-alpha.9
683
+
684
+ ### Patch Changes
685
+
686
+ - 1158420: Fixes from a parallel cold-agent sweep: a chain typo no longer crashes every command, and `inspect` stops over-promising on hand-written PRNGs.
687
+
688
+ **An unknown `ABX_CHAIN` printed a raw Node stack trace — from every command.** Chain-derived values
689
+ were resolved at module scope, in `token-api` (which the CLI imports) and in the CLI itself, so the
690
+ throw happened during module evaluation, before `main()` existed to catch it. `ABX_CHAIN=mainnet abx
691
+ doctor` dumped an internal source path and exited 1 — including from the one command whose job is to
692
+ tell you what is wrong with your environment. Those resolutions are lazy now, and the CLI validates
693
+ the variable up front with an answer rather than a crash: unknown values list the shipped chains, and
694
+ a mainnet-shaped value says plainly that ABX is testnet-only today.
695
+
696
+ **`abx inspect` reported "(no PRNG)" for a hand-written seeded generator — with the _stronger_
697
+ reproducibility verdict attached.** The `seeded` check only recognized p5's `randomSeed(`, so a
698
+ vanilla LCG or xorshift matched no branch and fell through to "traits look derived from the
699
+ seed/params directly". That is the common case, not an edge one — the skill's own canonical
700
+ dependency-free example hand-rolls an LCG, and all three sketches written by agents in the sweep hit
701
+ it. A hand-rolled generator now gets the `careful` verdict and is told the truth: deterministic and
702
+ reproducible on-chain, but only by porting that exact generator and call order into Solidity.
703
+
704
+ Also: `--yes` is now documented in `deploy-code --help` (its own placeholder-identity refusal already
705
+ told you to pass it), and the `--onchain-uri` raster warning now names the two routes that actually
706
+ deliver a no-server image instead of only one.
707
+
708
+ Found by the 2026-08-03 parallel sweep (8 cold Sonnet/Haiku agents, isolated sandboxes).
709
+
710
+ - Updated dependencies [1158420]
711
+ - Updated dependencies [1158420]
712
+ - Updated dependencies [1158420]
713
+ - @artblocks/abx-sdk@0.1.0-alpha.6
714
+ - @artblocks/abx-indexer@0.1.0-alpha.7
715
+ - @artblocks/abx-storage@0.1.0-alpha.6
716
+
717
+ ## 0.1.0-alpha.8
718
+
719
+ ### Minor Changes
720
+
721
+ - feba8c2: A resolver is no longer an object store: effect outputs split into **bound** and **referenced**
722
+ (`specs/protocol/effects.md → Bound vs referenced`), and the artifact registry enforces the split.
723
+
724
+ An output is **bound** iff a binding stitches its _content_ into the metadata JSON (today exactly
725
+ `render/traits` → `attributes`); everything else is **referenced** — the projection carries its URL,
726
+ or it only appears in the `artifacts` manifest. That one distinction decides who holds the bytes, and
727
+ it is now the wire rule rather than a runner constant.
728
+
729
+ - **`POST /v1/effect-artifacts` derives the mode from the binding, and refuses both mismatches.**
730
+ Bytes for a referenced output → `400` (the resolver redirects either way, so the bytes buy no
731
+ capability and cost it storage, retention and egress). A locator for a bound output → `400` (its
732
+ content is assembled into `tokenURI`; a pointer there used to be recorded and then silently never
733
+ stitch — a wrong answer served confidently). Bound content is capped at **64 KB**, and a locator
734
+ that only the producer could resolve (loopback/private host, presigned expiring URL) is rejected.
735
+ The resolver never fetches a locator while handling the write, and serves registered locators by
736
+ `302` — never by proxying.
737
+ - **Bound content moved out of byte custody** into the artifact row (`effect_artifacts.bytes`). Two
738
+ distinct rules, deliberately not one: a node **MUST** serve and stitch bound content only at the
739
+ token's current settled `inputsHash`, and it **MAY** drop superseded content whenever it likes
740
+ (nothing may read it, and it is re-creatable). The reference drops eagerly, on each bound
741
+ registration, so it holds at most `64 KB × minted × bound outputs` — but retention is a service
742
+ policy, not an obligation. Either way "conforming means holding a bounded amount of JSON in the
743
+ database you already run" is now literally true: a resolver in the publish topology needs no object
744
+ storage at all.
745
+ - **`abx-effects-publish/v1` is gone** (not deprecated): the two routes ride `abx-control-plane/v1`.
746
+ Once referenced output is locator-only, accepting a registration is a database insert, so the
747
+ capability flag described a distinction that no longer exists. A service that won't take a caller's
748
+ artifacts refuses on the credential (`403`) — interfaces describe wire grammar, tokens describe
749
+ permission. The interface ids are also explicitly **all-or-nothing**, and the conformance fixture
750
+ now checks that every route a declared interface names actually answers.
751
+ - **The runner declares bound outputs** (`EffectOutputDecl.bound`), refuses to start when it has a
752
+ publish token but a backend that can't name a locator, preflights the descriptor + credential
753
+ before spending a render, and **latches** on a permanent (4xx) publish failure instead of
754
+ re-rendering every sweep forever. Skips now re-register rows, so a transient publish failure heals
755
+ without a re-render.
756
+ - **The CLI refuses the impossible combination up front**: `abx render --remote`, `abx effects`
757
+ against a remote resolver, and `abx deploy-effects` all require a backend that can name a reachable
758
+ URL — `cloud` (S3/R2 + public base), `ipfs`, or `arweave`, named as **peers**. Derived output is
759
+ re-creatable, so the protocol has no preference among schemes: a chosen `https://` gateway or
760
+ bucket URL is exactly as legitimate as `ipfs://`/`ar://`, and reachability — not durability — is
761
+ the requirement. Rendering **co-located** with the resolver remains fully supported on any backend,
762
+ including `fs`.
763
+
764
+ Breaking for producers that relied on pushing media bytes to a resolver: publish a locator instead,
765
+ or co-locate. Breaking for clients that read `abx-effects-publish/v1` from a descriptor.
766
+
767
+ ### Patch Changes
768
+
769
+ - Updated dependencies [feba8c2]
770
+ - @artblocks/abx-sdk@0.1.0-alpha.5
771
+ - @artblocks/abx-indexer@0.1.0-alpha.6
772
+ - @artblocks/abx-storage@0.1.0-alpha.5
773
+
774
+ ## 0.1.0-alpha.7
775
+
776
+ ### Minor Changes
777
+
778
+ - 67b686b: `abx contracturi`, and the read plane stops answering a bare 404 to three different problems.
779
+
780
+ Both halves come from one real failure: an agent driving a hosted resolver wanted collection
781
+ metadata, pattern-matched off `/t/{chainId}/{address}/{id}`, dropped the token id, got a bare `404`,
782
+ and reported the service as broken. The documented route (`/c/{chainId}/{address}`) was right there —
783
+ but there was also no command to just _ask_, and the 404 gave it nothing to correct.
784
+
785
+ - **New `abx contracturi <address>`** — the collection-level counterpart of `tokenuri`. Reads
786
+ `contractURI()` (ERC-7572) from the contract, **follows it**, and decodes: a `data:` URI inline
787
+ (the on-chain lane), an `https://` URL by fetching it (the off-chain lane). A contract commits its
788
+ own metadata base on-chain (`contractURIBase`), so the chain — not a doc, not a service
789
+ descriptor — is the authoritative answer to where a project's metadata lives. Nobody needs to
790
+ hand-build a resolver URL. When the fetch fails, the message says so plainly: the URL came from
791
+ the chain, so a bad status is about the _service_ (unregistered project · wrong chain · down),
792
+ never a mistyped path.
793
+ - **Read-plane responses now carry a machine `code`**, so the three causes of "no metadata came
794
+ back" are distinguishable — they were one indistinguishable `{"error": "…"}` `404`:
795
+ - `400 invalid_request` — a real route, wrong shape. Names the correct template, and carries
796
+ `didYouMean` when the fix is obvious (a `/t/…` missing its token id → `/c/{chainId}/{address}`).
797
+ - `404 unknown_route` — this node serves nothing at that path; the body lists what it does serve.
798
+ - `404 not_registered` — the path and chain were fine; this node doesn't index that contract.
799
+ - `400 unsupported_chain` — wrong chain, plus the `chains` this node does serve. Was a bare `404`;
800
+ now matches what the control plane already answered for the same condition.
801
+ - `ServiceErrorCode` gains `unknown_route`. The spec's Errors section now covers the read plane too,
802
+ with a **MUST** on distinguishing the three misses — and an explicit **MUST NOT** on treating
803
+ route templates as per-node discoverable configuration. The route grammar is fixed by the
804
+ `abx-token-api/v1` interface; these responses are diagnostics, not a discovery mechanism.
805
+ - **The conformance fixture checks all of it** (`pnpm conformance <base-url>`), so any provider can
806
+ self-verify in one command. Also fixed: the documented `pnpm conformance -- <base-url>` form
807
+ parsed `--` as a flag and swallowed the base URL, printing usage instead of running.
808
+
809
+ ### Patch Changes
810
+
811
+ - Updated dependencies [67b686b]
812
+ - @artblocks/abx-sdk@0.1.0-alpha.4
813
+ - @artblocks/abx-indexer@0.1.0-alpha.5
814
+ - @artblocks/abx-storage@0.1.0-alpha.4
815
+
816
+ ## 0.1.0-alpha.6
817
+
818
+ ### Minor Changes
819
+
820
+ - a72723d: A standard indexing lifecycle, and registration that no longer blocks on a slow chain RPC
821
+ (specs/self-host-toolkit/remote-services.md → The indexing lifecycle).
822
+
823
+ - **Fixed: a slow register triggered a retry storm.** The SDK's per-attempt timeout (30s) plus its
824
+ retry ladder meant a cold reconstruct that outran one request was **re-POSTed up to four times**,
825
+ each starting another full replay against the RPC that was already too slow to answer — and the
826
+ caller then saw "nothing responded" even though the registration was durable and indexing was
827
+ underway. A timed-out register now asks whether it landed (a status read) instead of re-POSTing, and
828
+ the resolver coalesces concurrent catch-ups for one project into a single run.
829
+ - **`POST /v1/projects` answers in two conformant shapes, discriminated by HTTP status:** `200` with
830
+ the completed summary, or `202` + `{accepted, project: {status}}` when catch-up is deferred. The
831
+ registration is normatively **durable before catch-up** and visible on the list immediately, so a
832
+ flaky RPC makes for a slower backfill rather than a lost add. No `?wait=`/`Prefer:` negotiation — the
833
+ status code is the discriminator, and clients handle both. The reference resolver answers _by
834
+ deadline_ (`ABX_REGISTER_DEADLINE_MS`, default 8s): the common case (a fresh deploy) stays
835
+ synchronous with real counts; only the pathological case defers.
836
+ - **Closed lifecycle enum + error classes, on the status and list routes:**
837
+ `queued | backfilling | live | stale | failed`, plus credential-free
838
+ `error.class ∈ {rpc_unavailable, rpc_rate_limited, not_abx_contract, internal}` (fixed per-class
839
+ messages, never a scrubbed upstream string). Status gains top-level `headBlock` (so lag / % complete
840
+ is computable without knowing a service has a watcher) and `attempts`; the list carries `status` +
841
+ the error class, so a client renders "3 live, 1 backfilling, 1 failed (rpc_rate_limited)" in one
842
+ request. SDK: `IndexStatus`, `IndexErrorClass`, `isAccepted()`, `indexProgress()`,
843
+ `classifyIndexError()`, and `AbxServiceClient.awaitIndexed()` — one wait loop for the CLI, the
844
+ effects runner, and any hosted agent.
845
+ - **The same five words on your own node.** `abx status [address] [--remote [name|url]] [--watch]`:
846
+ bare is the node summary (now with each project's state), an address gives lifecycle + scan floor +
847
+ blocks-indexed-vs-head + cause, and `--remote` asks a service. (`status` = who is serving it and how
848
+ fresh; `state` = what the chain says. Both `--help` texts now say so.)
849
+ - **New observability the self-hosted node never had:** the chain watcher marks projects `stale` when
850
+ it falls far behind head or its ticks keep failing (previously visible only in the node's log),
851
+ re-queues a backfill interrupted by a restart (previously left registered-but-empty until a manual
852
+ `abx index`), and retries a `failed` catch-up on exponential backoff instead of hammering a
853
+ rate-limited RPC every tick. Lifecycle rows live in their own table: they survive a projection wipe
854
+ and are never clobbered by a re-add.
855
+ - **CLI:** `abx add|index --remote` prints `registered — backfilling…`, polls to `live`, then prints
856
+ the same summary a synchronous service would have given; `--no-wait` returns at the 202 and names
857
+ the command to check later. A post-op nudge (`ownerops`) never blocks on someone else's backfill.
858
+ A caught-up project with **0 events** now warns instead of printing ✓ (a real ABX clone always emits
859
+ a spine, so zero means wrong chain/floor or an RPC that didn't serve the logs).
860
+ - **Conformance fixture** accepts either register shape, asserts durable-before-catch-up, lifecycle
861
+ membership, `headBlock`, that a deferred catch-up actually reaches `live`, and that no error message
862
+ carries a URL.
863
+ - Fixed `scripts/mock-remote-service.mts`, which imported the token API by a path that resolved
864
+ against `scripts/` and could silently fall back to a _published_ build outside the repo — the
865
+ fixture was testing the last release instead of the working tree. The fixture also re-points
866
+ scenarios by their fixture header now, so a new one can't keep a dead contract address.
867
+
868
+ Found by a cold-agent sweep over the above (10 parallel clean rooms, haiku + sonnet) and fixed here:
869
+
870
+ - **`abx status --remote <name>` with no address** parsed the flag itself as the address and sent it
871
+ as a URL path segment.
872
+ - **A register whose catch-up already failed** was announced as "registered — failed (…is catching
873
+ up…)", and with `--no-wait` it exited 0 and then claimed the provider "now serves" the project. A
874
+ known failure is now an error in both lanes — there is nothing left to wait for.
875
+ - **A `failed` status said what broke but not whose problem it was.** Both the failure error and
876
+ `abx status` now carry a per-class action line ("the SERVICE can't reach its chain RPC — not your
877
+ key, address, or chain…"), plus a `follow` line naming `--watch`, so a red word isn't a dead end.
878
+ - **A `live` project showed a misleading completion percentage.** `toBlock` only advances when a
879
+ project has _events_, so a fully current project on a busy chain read as `2/202 (0%)`.
880
+ `indexProgress()` now returns a ratio only while `backfilling`; `live` reads "caught up", `stale`
881
+ reads "not tracking head right now".
882
+ - **`--remote-token` was misattributed on a 401** — the error blamed `ABX_REMOTE_<NAME>_TOKEN` even
883
+ when the caller passed an override, making the override look ignored at exactly the moment someone
884
+ is testing a replacement key.
885
+ - **`not_registered` on a read** (status/reindex) now names the register command instead of echoing a
886
+ 404, and a 5xx carrying a failure `class` becomes a wait-vs-broken error.
887
+ - **`abx verify`'s summary** read `✓ 0/1 up to date` for a project with no off-chain renders at all —
888
+ "zero of one succeeded" to two independent reviewers. It now says "nothing to render for this
889
+ project", and otherwise leads with polarity ("N of M token(s) current").
890
+ - **`abx doctor` now reports named remotes** and flags a credential stored under a name the CLI does
891
+ not read (`ABX_REMOTE_<NAME>_KEY`). That fault presents as "it acts like I never gave it a key" and
892
+ previously only surfaced from `abx remote <name>` — which a creator reaches _after_ doctor.
893
+ - **Skill: the `npx --no-install abx version` probe was documented as failing cleanly.** It doesn't —
894
+ npm will run any `abx` binary already in the npx cache, which in a real sweep reported a months-old
895
+ build as the project's CLI (and if a plain `npx abx` ever ran on that machine, the bare name is a
896
+ squatted package). The skill now probes `./node_modules/.bin/abx` directly.
897
+ - Also documented: how a multi-word provider name folds into `ABX_REMOTE_<NAME>_*`, and what
898
+ `watching: no` means on a status readout.
899
+
900
+ A second sweep round over those fixes caught three more, including one the first round's fix created:
901
+
902
+ - **`abx verify --remote` never checked byte integrity at all** — both of its lanes only ask "is there
903
+ a current render / is this a placeholder", and a green ✓ from that was standing in for "the served
904
+ bytes match the on-chain commitment". A reviewer hit the worst version of this: `--remote` (the form
905
+ the skill tells you to use for a hosted project) reported ✓ on a token whose bytes genuinely did NOT
906
+ hash-match, while bare `abx verify` on the same project reported `✗ keccak256 MISMATCH`. It now calls
907
+ the service's own purpose-built `GET /api/project/:addr/verify` (which holds both the bytes and the
908
+ chain) and reports that verdict separately from the render summary — and when it _can't_ run that
909
+ check (no credential, older node) it says "byte integrity NOT checked" instead of leaving a ✓ to
910
+ imply it passed. The remedy names both real causes (an unbridged durable locator vs. bytes that only
911
+ exist on the creator's machine, which a hosted resolver can never serve).
912
+ - **`abx verify` exited 0 while printing a byte MISMATCH**, in both lanes — nothing could gate on it.
913
+ An integrity mismatch now fails the command; a missing render or placeholder is a normal state and
914
+ still exits 0.
915
+ - **`abx add --dry-run` silently ignored the flag and performed the registration**, local or remote.
916
+ It now refuses and names the read-only commands (`abx state`, `abx status`) instead. Silently doing
917
+ the thing when the caller asked to preview is the one outcome that must never happen.
918
+ - **`PRAGMA busy_timeout` was set third in the store schema**, after the WAL switch it needs to
919
+ protect — so two processes opening the same store at once (parallel CLI runs, or a co-located
920
+ effects runner starting alongside the resolver) could fail outright with `database is locked`
921
+ instead of waiting the moment out. It is now the first statement.
922
+
923
+ A third round, re-running the scenario that found the verify bug (it now catches it) turned up:
924
+
925
+ - **`abx add --remote` ended on "it now serves <url>"** — true about indexing, silent about whether
926
+ the bytes are right, and two reviewers stopped there and reported a blank page as fixed. It now names
927
+ the byte check (`abx verify <addr> --remote <name>`) in the same breath.
928
+ - **`canonical:` collapsed a tri-state.** `isCanonical` is `true | false | null`, and both readouts
929
+ printed "unverified" for the last two — so "the chain says this is NOT a clone of the configured
930
+ factory" (a trust finding) looked identical to "the check never ran" (no factory on this chain, normal
931
+ on a dev chain). Two reviewers read the collapsed word as a second failure sitting next to a real one.
932
+ - **`abx verify --remote` gave a bare `fetch failed`** for an endpoint that was down, where
933
+ `abx status --remote` names the host and asks whether it's running. Two commands, one condition, two
934
+ error qualities — now consistent.
935
+ - `abx status <addr>` printed the address twice when the project has no name.
936
+ - Skill: registering with a provider on **their** hostname vs. a domain you control decides whether
937
+ leaving later costs a transaction — now stated in the managed-provider section, before you bake it.
938
+
939
+ ### Patch Changes
940
+
941
+ - Updated dependencies [a72723d]
942
+ - @artblocks/abx-sdk@0.1.0-alpha.3
943
+ - @artblocks/abx-indexer@0.1.0-alpha.4
944
+ - @artblocks/abx-storage@0.1.0-alpha.3
945
+
946
+ ## 0.1.0-alpha.5
947
+
948
+ ### Minor Changes
949
+
950
+ - 3745bd3: Remote services are first-class: a provider-neutral control plane, named remotes, and a service
951
+ descriptor (specs/self-host-toolkit/remote-services.md).
952
+
953
+ - **Control plane moves to `/v1`** (hard cutover; `/admin/*` is gone — redeploy self-hosted nodes):
954
+ `POST/GET /v1/projects`, `DELETE|reindex|status /v1/projects/{chainId}/{address}`,
955
+ `POST /v1/effect-artifacts|effect-status`. `chainId` is explicit and validated everywhere; every
956
+ error carries a machine `code` (`unauthorized` 401 · `forbidden` 403 · `unsupported_chain` ·
957
+ `not_registered` · `disabled`) replacing the old prose-sniffed 404. One bearer guard replaces the
958
+ four inline copies; OPTIONS preflight now answers so browser clients can send `Authorization`.
959
+ - **`GET /.well-known/abx-service`** — the public service descriptor: `interfaces` (present iff
960
+ actually enabled), `chains`, `auth` (with optional provider-set `signupUrl`/`docsUrl` via
961
+ `ABX_SERVICE_*` env), and `render.attached` (managed rendering, probed from the runner's
962
+ `/health`) — so an agent can match a project to a provider before registering.
963
+ - **Named remotes in the CLI**: `--remote <name>` reads `ABX_REMOTE_<NAME>_URL`/`_TOKEN`
964
+ (a managed provider's per-account key — never falls back to `ABX_RESOLVER_ADMIN_TOKEN`);
965
+ `--remote <url> [--remote-token <t>]` for ad-hoc targets; bare `--remote` stays the self-host
966
+ default. New `abx remote [name|url]` inspects a service's descriptor and the projects a token
967
+ sees. `migrate --from/--to` accept names; only the destination needs a credential.
968
+ - **The SDK gains its first HTTP surface**: `AbxServiceClient` (endpoint + injected bearer, retry
969
+ on 5xx/network, immediate typed `AbxServiceError` on 4xx) — shared by the CLI and the effects
970
+ runner's publish lane. `envSuffix()` is the shared env-name normalization.
971
+ - **Conformance fixture**: `pnpm conformance -- <base-url> [--token …]` self-verifies any
972
+ implementation; the e2e suite runs it against the reference container.
973
+
974
+ ### Patch Changes
975
+
976
+ - 3745bd3: Membrane fixes found by a 20-run cold-agent regression sweep (sonnet + haiku, black-box clean rooms).
977
+
978
+ - **A 500 no longer leaks the node's own credentials.** An upstream RPC failure surfaced viem's
979
+ message, which embeds the endpoint URL — and a keyed RPC URL _is_ a credential, so on a
980
+ multi-tenant provider any tenant who could provoke a 500 got the operator's RPC key. The cause now
981
+ goes to the node's log; the wire gets a generic message, an `internal_error` code, and a
982
+ credential-free hint about the failure class. Normative in the remote-services spec.
983
+ - **The service client no longer discards a 5xx body.** The service's own words survive the retry
984
+ ladder, and an exhausted ladder says "failed — last response …" rather than mislabelling a
985
+ server that answered as "unreachable". The descriptor probe drops to 2 attempts, so a typo'd
986
+ provider URL fails in ~1s instead of grinding 5s, with distinct "nothing responded" vs
987
+ "answered, but serves no descriptor" messages.
988
+ - **Conflicting duplicate `.env` keys are reported.** First-wins is unchanged, but a stale second
989
+ `ABX_RPC_URLS_<CHAIN>` line silently pointed the CLI at another network while every check read
990
+ green — the symptom surfaced far away as "no contract at that address". Only genuinely
991
+ _conflicting_ duplicates warn (identical repeats stay quiet).
992
+ - **"No contract at …" errors now name the endpoint they asked** (redacted), because a chain key
993
+ can't distinguish two RPCs that both claim it.
994
+ - **A misnamed remote credential is called out.** `ABX_REMOTE_<NAME>_KEY` (or `_API_KEY`, `_SECRET`)
995
+ is not read, so it previously reported as "no token" while the value sat in `.env`; both
996
+ `abx remote` and the register path now name the near-miss and the correct `_TOKEN` name.
997
+ - **`--dry-run` explains a missing trust anchor instead of crashing.** On a chain where the
998
+ configured factory has no code, `deploy`/`deploy-series` previews died inside
999
+ `predictDeterministicAddress` with a raw `returned no data ("0x")`; they now report it the way
1000
+ `abx predict` and a real deploy already did, and name the two ways forward. The keyless
1001
+ `--for` requirement also fails fast instead of after several steps of output.
1002
+ - **The placeholder-identity guard is one shared predicate** across all three deploy commands
1003
+ (it was copy-pasted, and one copy's comment claimed coverage it didn't have), pinned by a new
1004
+ regression test: a real send refuses tool defaults, a preview only warns.
1005
+ - **The served dashboard's empty state no longer prints `pnpm abx demo`** — a contributor-only
1006
+ invocation on a page a published user sees.
1007
+
1008
+ - Updated dependencies [3745bd3]
1009
+ - Updated dependencies [3745bd3]
1010
+ - @artblocks/abx-sdk@0.1.0-alpha.2
1011
+ - @artblocks/abx-indexer@0.1.0-alpha.3
1012
+ - @artblocks/abx-storage@0.1.0-alpha.2
1013
+
1014
+ ## 0.1.0-alpha.4
1015
+
1016
+ ### Patch Changes
1017
+
1018
+ - 4074766: Fix the dashboard's block-explorer links, which were hardcoded to `https://sepolia.etherscan.io`. Every
1019
+ link on the page — contract, owner, implementation, each event's tx — pointed at Ethereum Sepolia no
1020
+ matter which chain was being served, so a dashboard for a normal `abx demo` (Base Sepolia by default)
1021
+ sent you to an explorer where the contract does not exist. The SDK now derives the explorer from viem's
1022
+ own chain metadata (`explorerUrl`/`chainById`), so adding a chain brings its explorer along and no
1023
+ hand-maintained table can drift. The CLI's separate copy of that table is collapsed into the same
1024
+ helper; `signer.ts` was already doing it correctly.
1025
+
1026
+ Drop the demo's opening "trust anchor" step. It asserted that only the canonical factory can make a
1027
+ token that _is_ an ABX token, which is false — anything following the protocol's event spine is an ABX
1028
+ token, and the factory is one route to that, not the definition. The same overclaim in the index step
1029
+ ("verified real") now reports the fact instead: made by the canonical factory, or not. The demo opens
1030
+ on the renderer step, and resolving the factory no longer prints a line of its own there.
1031
+
1032
+ - Updated dependencies [4074766]
1033
+ - @artblocks/abx-sdk@0.1.0-alpha.1
1034
+ - @artblocks/abx-indexer@0.1.0-alpha.2
1035
+ - @artblocks/abx-storage@0.1.0-alpha.1
1036
+
1037
+ ## 0.1.0-alpha.3
1038
+
1039
+ ### Patch Changes
1040
+
1041
+ - Updated dependencies [c21ea30]
1042
+ - @artblocks/abx-indexer@0.1.0-alpha.1
1043
+
1044
+ ## 0.1.0-alpha.2
1045
+
1046
+ ### Patch Changes
1047
+
1048
+ - afb36a3: Fix three bugs that made the documented first run (`abx demo`) look broken.
1049
+
1050
+ **A 0-event index was reported as success, and served.** `eth_getLogs` is
1051
+ read-after-write inconsistent on load-balanced RPCs: `waitForTransactionReceipt`
1052
+ resolves against a node that has the block, then the log query lands on one that
1053
+ doesn't yet and returns nothing for a block we _know_ contains our deploy. The CLI
1054
+ took that single read at face value, printed `✓ reconstructed 0 events`, stored the
1055
+ empty projection and served an empty dashboard — no events, no token, nothing to
1056
+ look at. This reproduced 100% of the time against `https://sepolia.base.org`, which
1057
+ is the **default endpoint when there is no `.env`** — so the documented first run
1058
+ was the path that broke. Every post-deploy index (`deploy`, `demo`, `deploy-series`,
1059
+ `deploy-code`, `add`) now re-scans with backoff instead of trusting one read, since
1060
+ having just minted means the spine cannot legitimately be empty. If it still comes
1061
+ back empty, that is now reported as a failure naming `abx index <addr> --full` as
1062
+ the recovery, rather than dressed up as a ✓.
1063
+
1064
+ **The demo told you to press a button that does not exist.** It ended with _"Open
1065
+ the dashboard, then hit 'Re-index from chain'"_. The dashboard is read-only —
1066
+ re-index and verify are admin actions that 404 unless the node has an
1067
+ `ABX_RESOLVER_ADMIN_TOKEN` — so that control isn't there to find. It now points at
1068
+ the spine table (which _is_ the reconstruction) and at `abx index <addr> --full` to
1069
+ replay it. The dashboard's own note also stopped printing a **shortened** address
1070
+ inside a copy-pasteable command, and no longer suggests `--remote` for a local node.
1071
+
1072
+ **Re-running the demo crashed after spending a transaction.** With port 8787 already
1073
+ busy — an `abx demo` or `abx serve` in another terminal, i.e. exactly what happens
1074
+ when you run the demo twice — the deploy went through, was paid for, and _then_ the
1075
+ serve step died with an unhandled Node `EADDRINUSE` stack trace. The port is now
1076
+ preflighted before anything irreversible, so it refuses with "Nothing was deployed"
1077
+ and suggests `--port <n+1>`.
1078
+
1079
+ **`abx demo` is now a walkthrough rather than a smoke test.** The docs point a
1080
+ first-time reader here, but it asserted its interesting claims without ever showing
1081
+ them — "reconstructed 9 events — no provider involved" with the events invisible.
1082
+ It now teaches, continuously and without pauses (so agents and CI behave
1083
+ identically): it explains why the trust anchor is the factory and not a spoofable
1084
+ event, names what goes on chain versus what stays a keccak256 commitment, prints
1085
+ the reconstructed event spine with what each event told us (tagged ABX vs plain
1086
+ ERC-721/7572), then **deletes its own local projection and replays it from the
1087
+ deploy block**, comparing a sha256 fingerprint of every chain-derived field to prove
1088
+ it lands on identical state. Finally it reads the token back the way a marketplace
1089
+ would. `abx deploy` is unchanged — it stays terse.
1090
+
1091
+ Adds `Store.dropProjection(address)` / `SelfHostIndexer.dropProjection()`: discard a
1092
+ project's reconstructed projection while keeping its registration, so the next index
1093
+ rebuilds from the deploy block. That's the primitive the rebuild proof needs, and it
1094
+ makes "the projection is a disposable cache" a checkable claim rather than a comment.
1095
+
1096
+ ## 0.1.0-alpha.1
1097
+
1098
+ ### Patch Changes
1099
+
1100
+ - 48d96c5: Add **`abx preview`** — the studio lane for code projects: run the program on
1101
+ localhost, live, while it's still being made. No chain, no key, no deploy.
1102
+
1103
+ `abx preview --script art.js --schema "palette:HexColor:TokenOwner"` serves a
1104
+ studio on `localhost:8788` — shuffle seeds, drive every declared PostParam from a
1105
+ real typed input (a color picker for `HexColor`, a dropdown for `Select`), read the
1106
+ traits the program actually reported, and `/grid` to see N seeds at once. `/view`
1107
+ is the bare document. The program is re-read from disk on every render, so the loop
1108
+ is edit-and-refresh with no watcher and no restart.
1109
+
1110
+ It serves the **same template-mode document the generator serves** — the real
1111
+ `abx.js`, the real canonical `tokenData` shape, the real dependency script tags —
1112
+ with a synthetic bytes32 seed in place of a minted one, so what you approve is what
1113
+ deploys. There is no second copy of the runtime to drift from. (`@artblocks/abx-token-api`
1114
+ now exports `ABX_JS` and the inline-safety escapes so the CLI can build that exact
1115
+ document rather than reimplement it.)
1116
+
1117
+ `--shoot <dir>` drives the same server headlessly to PNGs plus a `traits.json` and
1118
+ exits — that's how an agent, which can't open a browser, sees what the creator sees.
1119
+ It also flags the two silent killers: no frame reporting traits (⇒ no marketplace
1120
+ `attributes` on any lane) and identical traits across every seed (⇒ the program
1121
+ isn't reading `abx.tokenData.seed`, so the drop mints N identical tokens).
1122
+
1123
+ Why a live server rather than a screenshot sweep: a still flattens every time-based
1124
+ piece, and `abx.done()` exists precisely because stills need a settle point — so a
1125
+ proof sheet of an animated piece is a set of arbitrary frozen frames presented as
1126
+ the work. Frames render into a fixed 1000×1000 viewport and are scaled to fit their
1127
+ slot, so a program that hardcodes its canvas size is never clipped to its own corner.
1128
+
1129
+ Skill: add **Phase 0**, an explicit authoring phase that puts every deploy decision
1130
+ (hosting, thumbnail, traits, storage, wallet, supply, royalties, name/symbol) off
1131
+ the table until the creator says ship, and points at `abx preview` for the loop. The
1132
+ skill previously went straight from "the creator brought an idea" to "pick a deploy
1133
+ lane", so an agent helping someone _design_ a piece front-loaded infrastructure
1134
+ questions while there was still nothing to look at.
1135
+
1136
+ Skill: also teach CLI resolution — probe project-local (`npx --no-install abx`)
1137
+ before global, install `@artblocks/abx-cli` (not `@artblocks/abx-sdk`, which ships
1138
+ no binary), and default to a per-project install. The skill previously assumed `abx`
1139
+ was already on PATH and gave no bootstrap path at all, so agents improvised — one
1140
+ installed the SDK, then went global unprompted.