@artblocks/abx-token-api 0.1.0-alpha.3 → 0.1.0-alpha.30

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