@artblocks/abx-sdk 0.1.0-alpha.29 → 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 (73) hide show
  1. package/CHANGELOG.md +4 -2462
  2. package/README.md +2 -3
  3. package/dist/anchors.d.ts +4 -5
  4. package/dist/anchors.d.ts.map +1 -1
  5. package/dist/anchors.js +8 -11
  6. package/dist/anchors.js.map +1 -1
  7. package/dist/chunks.d.ts +4 -13
  8. package/dist/chunks.d.ts.map +1 -1
  9. package/dist/chunks.js +4 -13
  10. package/dist/chunks.js.map +1 -1
  11. package/dist/create2.d.ts +2 -3
  12. package/dist/create2.d.ts.map +1 -1
  13. package/dist/create2.js +2 -3
  14. package/dist/create2.js.map +1 -1
  15. package/dist/creator-token.d.ts +4 -4
  16. package/dist/creator-token.js +4 -4
  17. package/dist/deployments.d.ts +3 -4
  18. package/dist/deployments.d.ts.map +1 -1
  19. package/dist/deployments.js +16 -84
  20. package/dist/deployments.js.map +1 -1
  21. package/dist/deps.d.ts +3 -3
  22. package/dist/deps.d.ts.map +1 -1
  23. package/dist/deps.js +3 -3
  24. package/dist/deps.js.map +1 -1
  25. package/dist/env.d.ts +1 -3
  26. package/dist/env.d.ts.map +1 -1
  27. package/dist/env.js +1 -3
  28. package/dist/env.js.map +1 -1
  29. package/dist/gateways.js +2 -2
  30. package/dist/generator-document.d.ts +1 -2
  31. package/dist/generator-document.d.ts.map +1 -1
  32. package/dist/generator-document.js +1 -2
  33. package/dist/generator-document.js.map +1 -1
  34. package/dist/generator.d.ts +1 -1
  35. package/dist/inspect.js +2 -2
  36. package/dist/inspect.js.map +1 -1
  37. package/dist/mime.d.ts +2 -2
  38. package/dist/mime.js +2 -2
  39. package/dist/onchain-uri.d.ts +2 -2
  40. package/dist/onchain-uri.d.ts.map +1 -1
  41. package/dist/onchain-uri.js +4 -5
  42. package/dist/onchain-uri.js.map +1 -1
  43. package/dist/ops.js +2 -2
  44. package/dist/ops.js.map +1 -1
  45. package/dist/policy.d.ts +1 -2
  46. package/dist/policy.d.ts.map +1 -1
  47. package/dist/policy.js +1 -2
  48. package/dist/policy.js.map +1 -1
  49. package/dist/probe.d.ts.map +1 -1
  50. package/dist/probe.js +3 -28
  51. package/dist/probe.js.map +1 -1
  52. package/dist/reconstruct.d.ts +2 -3
  53. package/dist/reconstruct.d.ts.map +1 -1
  54. package/dist/reconstruct.js +3 -4
  55. package/dist/reconstruct.js.map +1 -1
  56. package/dist/service.d.ts +3 -4
  57. package/dist/service.d.ts.map +1 -1
  58. package/dist/service.js +1 -1
  59. package/dist/service.js.map +1 -1
  60. package/dist/staging.d.ts +2 -11
  61. package/dist/staging.d.ts.map +1 -1
  62. package/dist/staging.js +5 -30
  63. package/dist/staging.js.map +1 -1
  64. package/dist/token.d.ts +1 -1
  65. package/dist/token.js +1 -1
  66. package/dist/tokendata.d.ts +3 -3
  67. package/dist/tokendata.js +3 -3
  68. package/dist/tokens.d.ts +1 -2
  69. package/dist/tokens.d.ts.map +1 -1
  70. package/dist/tokens.js.map +1 -1
  71. package/dist/types.d.ts +3 -5
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,2469 +1,11 @@
1
1
  # @artblocks/abx-sdk
2
2
 
3
- ## 0.1.0-alpha.29
4
-
5
- ### Minor Changes
6
-
7
- - c198237: Closes the remaining 2026-08-27 w7 agent sweep backlog (B60-B65): a new command, a real behavior
8
- change, and several truthfulness/legibility fixes.
9
-
10
- - **`abx remote set <name> --url <url>`** — the third-party twin of `abx auth login`. Installs
11
- `ABX_REMOTE_<NAME>_URL`/`_TOKEN` into `.env` for any hosted metadata provider, not just the
12
- first-party `abx` remote. The token is read from stdin (piped, or a TTY prompt) — never a
13
- command-line argument, never printed — and re-running replaces the pair in place rather than
14
- duplicating it. Reuses `auth login`'s own safety rails (symlink/git-tracked/unignored refusal,
15
- atomic write) via newly-exported `saveEnvSecret`/`assertPrivateEnvPath`.
16
- - **`abx scaffold solidity [<dir>]`** replaces the proposed `scaffold-minter-hook` with one Foundry
17
- workspace for ABX's real Solidity extension surfaces. Renderers and configure/transfer/augment
18
- hooks remain separate deployable roles; canonical ERC-721/ERC-1155 minters are imported rather than
19
- reinvented. The unsafe burn-to-combine example was removed.
20
- - **`abx-contracts~2.0.0`** is the canonical Soldeer package boundary (B49): interfaces,
21
- implementations, minters and reference hooks ship from the existing contracts source tree, with an
22
- exact compatibility version. Generated projects no longer vendor drift-prone interface copies.
23
- B50 is resolved without a mock protocol: reference-hook tests deploy a real canonical SeriesCode
24
- clone and exercise schema auth, empty-blob refusal, hook ordering and transfers against it.
25
- - **`.env` duplicate keys are now last-wins**, matching conventional dotenv tooling instead of the
26
- previous (surprising, backwards) first-wins behavior. `parseEnvContent` keeps the last value at the
27
- key's original position; the printed warning updated to match.
28
- - **`abx state` now says when it can't enumerate PostParams**, instead of silently showing nothing —
29
- a non-canonical/pre-enumeration contract used to read as "confirmed no params" when it actually meant
30
- "this node couldn't check." New human-readable line + `params.enumerable` in `--json`.
31
- - **A throttled progress line during a large `abx add`/`abx verify` scan** — a range-capped RPC's scan
32
- could run 15+ minutes with zero output after one line up front. Now prints `… still scanning:
33
- N/total blocks (X%)` periodically via a new `onChunk` hook threaded through
34
- `getLogsAdaptive`→`reconstructProject`/`reconstructIncremental`→the indexer's `reindex()`.
35
- - **Three smaller truthfulness fixes**: `abx remote <url>` now flags a loopback descriptor
36
- `baseUrl` the same way it already flags an unsupported chain; `configure-param` now notes when the
37
- canonical display differs from what was typed (a bare `ff7f50` normalizing to `#ff7f50`, a Select
38
- index resolving to its label); and owner-op `--dry-run --json` now emits the prepared transaction,
39
- expected signer, signing lane, and simulation result as structured data.
40
-
41
- ## 0.1.0-alpha.28
42
-
43
- ### Patch Changes
44
-
45
- - c5e0d84: Published package `homepage` and `bugs` fields now point at docs.abx.io instead of a private GitHub
46
- repo that 404s. File reports with `abx feedback`; `npm bugs` no longer names a repository the public
47
- cannot open.
48
-
49
- Reported in the 2026-08-27 tester batch (feedback 40142396).
50
-
51
- - c5e0d84: Read commands no longer collapse "the RPC is down" into "this isn't an ABX contract."
52
-
53
- `abx state` now refuses an ignored singular `ABX_RPC_URL`, probes chain reachability, and retains the
54
- errors from its required getter reads. A dead endpoint, a node that answers block/code queries but
55
- rate-limits `eth_call`, a codeless address, and code without ABX getters now receive distinct answers;
56
- an RPC failure is never stated as a contract verdict. `abx add`'s deploy-block discovery no longer
57
- guesses "a non-archive RPC?" when the same node `abx doctor` just certified — the failure can also be
58
- rate-limiting or a refused historical `getCode`. The `deploy-code` Surfaces block still flags an
59
- orphaned collection-wide thumbnail, and now names the per-token `set-field --field image` hatch (raw
60
- SVG bytes, not a data-URI) instead of claiming you cannot backfill at all. `submit-app` now confirms
61
- only the on-chain update and tells users to verify hosted catalog copy separately instead of promising
62
- an unverified refresh on the provider's "next read."
63
-
64
- Reported in the 2026-08-27 tester batch (feedback ae7cc4a8, bd75dc5f, 89315be8, 1f3c0992, c2101d6f).
65
-
66
- ## 0.1.0-alpha.27
67
-
68
- ### Patch Changes
69
-
70
- - 4072f0c: Add `abx auth login`, an OAuth 2.0 Device Authorization Grant flow for first-party and named remote
71
- services. Human email/OTP approval stays in the browser; the CLI polls according to RFC 8628 and
72
- writes the resulting bearer key to a Git-ignored `.env` without printing it. The bundled ABX skill
73
- now recommends this agent-safe flow and keeps manual signup only as a recovery path. The SDK's
74
- service-descriptor documentation likewise distinguishes provider recovery URLs from standard OAuth
75
- discovery.
76
-
77
- ## 0.1.0-alpha.26
78
-
79
- ### Minor Changes
80
-
81
- - b182f23: Drop-in for existing SDK and CLI consumers; adds optional first-party service and feedback surfaces.
82
-
83
- Add the built-in `abx` remote at `https://services.abx.io`, authenticated by an email-verified free
84
- `ABX_SERVICES_API_KEY`, so projects no longer need to copy a service URL or signup handoff into agent
85
- instructions. The current key includes hosted resolver and managed-rendering access; the docs state
86
- that hosted infrastructure may become paid and preserve the provider-neutral/self-hosted exit route.
87
-
88
- Add `abx feedback`: core protocol/contract/CLI/SDK/skill/docs reports go to the ABX team by default,
89
- while `--remote <provider>` uses the provider-standard `abx-service-feedback/v1` interface. Reports
90
- are structured, preview-only until the human re-runs with `--yes`, and queryable with `--mine`. The
91
- SDK exports the interface id, feedback types, and discovery/submit/history client methods.
92
- CLI help enumerates every closed feedback field so an agent can construct a valid report without a
93
- failed probe.
94
-
95
- Move the public documentation links to `docs.abx.io` and teach the shipped skill the verified signup,
96
- credential boundary, two feedback targets, explicit consent gate, and current hosted-service terms.
97
-
98
- ## 0.1.0-alpha.25
99
-
100
- ### Patch Changes
101
-
102
- - 8f2f78a: Rename and globally refactor the shipped agent skill from `abx-self-host` to `abx` now that managed
103
- remote hosting, no-host on-chain projects, deployment, and owner operations are all first-class
104
- lanes. The installer archives recognized legacy skill copies outside the discovery tree before
105
- installing the new name, preserving user customizations while preventing duplicate triggers. The
106
- runtime `.abx-self-host/` data directory is unchanged. Scoped install help names the automatic
107
- legacy archive behavior, and `doctor` prints every legacy copy path before recommending migration.
108
-
109
- Replace the accumulated custody-first guide with a surfaces-first lifecycle covering collection
110
- shape, runtime, metadata/image/animation/traits, custody, resolution, rendering, authority,
111
- mutability, confirmation, verification, and state-driven recovery. The always-loaded router and
112
- references are substantially smaller, contradictions are removed, and integrity tests now enforce
113
- Agent Skills metadata limits, router size, reference navigation, known stale claims, and Codex UI
114
- metadata.
115
-
116
- Add `abx capabilities [--json]`, a typed product-boundary contract for native deployment lanes,
117
- canonical extension seams, irreversible choices, and unsupported cases. CLI help and tests consume
118
- the same contract, including the current EditionCode renderer/dependency support and static-edition
119
- on-chain-image support. Root help now exposes `ABX_CHAIN`, and param-hook help matches the actual
120
- SeriesCode/EditionCode contract surface.
121
-
122
- Redact configured credential-bearing RPC URLs at the CLI's top-level error boundary so upstream
123
- provider errors cannot echo API keys into terminal or agent transcripts.
124
-
125
- ## 0.1.0-alpha.24
126
-
127
- ### Patch Changes
128
-
129
- - 61503c3: On-chain content: measure the RPC instead of guessing, and refuse no size.
130
-
131
- `deploy --onchain-image`, `deploy-series --onchain-image` and `set-field --file` refused content past
132
- **100 KB per token**. That threshold was reasoned from geth's 50M `--rpc.gascap` default minus a
133
- margin, on the stated theory that "hosted providers commonly cap well below geth's default".
134
-
135
- Measured 2026-08-24, with a state-override `eth_call` that reports the gas a node actually provisions:
136
-
137
- | Endpoint | `eth_call` cap | Content it serves |
138
- | ------------------------------------------------------------------ | --------------- | ----------------- |
139
- | **`sepolia.base.org`** — the default endpoint of our default chain | **600,000,000** | **~729 KB** |
140
- | `base-sepolia.publicnode.com` · `base-sepolia.drpc.org` | 50,000,000 | ~117 KB |
141
- | `ethereum-sepolia.publicnode.com` | 50,000,000 | ~117 KB |
142
-
143
- Providers do not commonly cap lower — **50M is the floor**, and the chain we ship as the default serves
144
- twelve times that. So the refusal sat _below every endpoint that could be found_: it blocked content all
145
- of them could read, and was 7x too strict on the default chain. A creator who wanted a 312 KB image
146
- permanently on-chain was told no by a number about somebody else's infrastructure.
147
-
148
- **There is now no refusal at any size**, and `--allow-unreadable-onchain` is gone with it (it existed
149
- only to skip the refusal). In its place the toolkit states both costs and measures the one that varies:
150
-
151
- - **Write** is chunked into 22,000-byte SSTORE2 transactions, so no block gas limit binds it at any
152
- size. It is purely money at ~200 gas/byte, and the CLI now says so instead of implying a ceiling.
153
- - **Read** is one `eth_call`, and whether it succeeds belongs to whoever's endpoint is asking. Below
154
- ~117 KB (~50M gas) every endpoint measured serves it, and the CLI prints a plain note. Above that it
155
- probes the RPC you are actually on — no transaction, no key, one call — and reports its real
156
- allowance beside the estimate.
157
-
158
- ```
159
- ⚠ big.png is 312KB on-chain — reading tokenURI costs ~172M gas, which is past the ~50M gas floor
160
- that every endpoint serves.
161
- Your RPC allows ~600M gas (sepolia.base.org), so it reads for you — up to ~729KB. A 50M-capped
162
- provider stops at ~117KB, and marketplaces or indexers on one will see a REVERT rather than your
163
- token. Writing is unaffected: ~64M gas, chunked, no block-limit issue. This is the
164
- permanence-vs-reach trade — --backend arweave is the other side of it.
165
- ```
166
-
167
- The distinction the output keeps making, because it is the one that matters: **your RPC is not the
168
- marketplace's RPC.** A measured cap proves _you_ can read the token, never that OpenSea can. Past even
169
- your own endpoint's cap the CLI says that plainly — and still does not stop you, because bytes someone
170
- wants permanent are their call, not ours.
171
-
172
- New in the SDK: `probeEthCallGasCap` / `probeBestEthCallGasCap` (in `probe.ts`, alongside the existing
173
- getLogs capability probe that already argued this exact principle — measure the endpoint, don't trust a
174
- table), `ETH_CALL_GAS_FLOOR`, and `readableBytesAtGas`. `classifyOnchainReadSize(bytesLen, capGas?)`
175
- now returns `'ok' | 'endpoint-dependent' | 'beyond-local-rpc'` — none of them a refusal — and
176
- `ONCHAIN_READ_REFUSE_BYTES` / `ONCHAIN_READ_GETH_CAP_BYTES` are removed.
177
-
178
- An endpoint that rejects state overrides yields "unknown", which degrades to the conservative floor and
179
- is tested to never throw and never block.
180
-
181
- ## Follow-up audit: the other hardcoded third-party assumptions
182
-
183
- **Three different limits were being called "the gas limit", and they differ by orders of magnitude on
184
- the same endpoint.** Measured 2026-08-24:
185
-
186
- | | `eth_call` cap | `eth_estimateGas` cap | block `gasLimit` |
187
- | --------------------------------- | ------------------------ | --------------------- | ------------------------ |
188
- | Base Sepolia — `sepolia.base.org` | 600,000,000 | 16,777,216 | 1,200,000,000 |
189
- | Base Sepolia — publicnode · drpc | 50,000,000 | 16,777,216 | 1,200,000,000 |
190
- | Sepolia — publicnode | 50,000,000–2,000,000,000 | 16,777,216 | 60,000,000 |
191
- | Base mainnet · Ethereum mainnet | — | — | 400,000,000 · 60,000,000 |
192
-
193
- - **"L1 block limits sit in the mid-30-millions"** (SDK, docs) was stale in the direction that makes
194
- on-chain look worse than it is. It is 60M on Ethereum and Sepolia, 400M on Base, and **1,200M on Base
195
- Sepolia** — so the claim that _"past ~90 KB no contract can read the token inside a transaction"_ was
196
- off by more than 12x on our own default chain, where the real figure is ~1,100 KB. Removed everywhere.
197
- - **`DEFAULT_TX_GAS_BUDGET = 8M` was right for the wrong reason, and the wrong reason was dangerous.**
198
- It said "comfortably under mainnet's ~30M block limit". The limit that actually binds is
199
- `eth_estimateGas`, capped at **16,777,216 (2²⁴)** on every endpoint tried — every send estimates
200
- first, so a batch planned above that fails before anything is sent. Anyone correcting the stale block
201
- figure would have raised the budget and broken every deploy. The constant is unchanged; its rationale
202
- now names the limit that holds it, and says not to raise it toward a block limit.
203
- - **A measured cap is point-in-time, not a property.** Sepolia's publicnode endpoint measured 50M one
204
- hour and 2,000M the next — pooled endpoints rotate between backends with different configs. The probe
205
- and the output now say so.
206
- - **The probe could not tell "capped at exactly my ask" from "not capped at all".** A node that returns
207
- the full 2,000,000,000 it was asked for did not clamp; reporting that as a 2,000M cap invents a limit
208
- out of our own request. It now returns `Infinity` and the CLI prints "no eth_call cap at all".
209
-
210
- ## And the reframe that makes the whole question smaller
211
-
212
- A large read is a **serving** problem, not a preservation one — and the resolver already solves it.
213
- `resolveFieldBytes` reads `inline` / `inline-gzip` / `reader` / `reader-gzip` with the resolver's own
214
- RPC and serves the result as ordinary HTTP, so a marketplace fetches a URL and never makes the large
215
- `eth_call`. `abx set-renderer <addr> --off` points `tokenURI` at it with every byte left where it is.
216
-
217
- Every warning now says this, because it changes the decision: **storage and serving are separate.**
218
- On-chain is the strongest storage answer available at any size; self-resolving-versus-a-resolver is a
219
- serving choice the creator can change later. A token whose read is too large for some endpoint is not a
220
- lost token — it is one that wants a reader in front of it.
221
-
222
- And the framing that replaces the old false precision: **we can measure your RPC; we cannot know a
223
- marketplace's or an indexer's**, and theirs are the ones that decide whether the token displays.
224
-
225
- ## 0.1.0-alpha.23
226
-
227
- ### Patch Changes
228
-
229
- - bd77ba2: Correct the Node floor to 22.13, and refuse to start with a remedy instead of a stack trace.
230
-
231
- Every package declared `engines.node: ">=22.5.0"` — the version `node:sqlite` was **added**, not the
232
- version it became usable. It shipped in v22.5.0 behind `--experimental-sqlite` and lost the flag in
233
- v22.13.0 / v23.4.0. So `npm i -g @artblocks/abx-cli` on Node 22.5 installed silently (the engine
234
- range was satisfied) and then `abx doctor` — the first command we tell everyone to run — died with a
235
- raw `ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:sqlite` stack trace naming neither
236
- abx nor a fix. Reported 2026-08-24; the floor is now `>=22.13.0` everywhere, so npm warns at install
237
- time.
238
-
239
- The crash happened while ESM _linked_ `main.ts`'s import graph (`output.ts` →
240
- `@artblocks/abx-indexer` → `node:sqlite`), before any of our code ran, so no guard inside the CLI
241
- could have caught it. The published binary is now a deliberately import-free shim (`dist/bin.js`)
242
- that probes for `node:sqlite` and hands off to `main.js` — it prints the running version, the real
243
- floor, and the upgrade command. It probes the **capability**, not the version number: an old Node
244
- launched with `NODE_OPTIONS=--experimental-sqlite` genuinely works, and refusing that would be a
245
- false negative. `engines` states what we support; the guard blocks only what is actually broken.
246
-
247
- `abx doctor`'s header now also reports the running Node, since its output is what people paste when
248
- something is wrong.
249
-
250
- ## 0.1.0-alpha.22
251
-
252
- ### Patch Changes
253
-
254
- - 993e095: Hot-lane sends take `max(pending, latest)` as the nonce, so a node whose pending view has fallen behind
255
- its own head can't poison a transaction.
256
-
257
- `sepolia.base.org` was measured returning a _pending_ transaction count **lower** than its own
258
- latest-block count for a wallet that had just written — a strictly incoherent answer, and one the
259
- fallback endpoint never gave. The sender already handled the read-after-write lag _within_ one sequence
260
- by incrementing locally; it could not see the cross-invocation case, because each `abx` command is a
261
- fresh process that re-reads the nonce. The result was a second command whose broadcast was rejected as a
262
- duplicate — silently, since a failed simulation means the transaction is never sent at all.
263
-
264
- Reading both views and flooring at `latest` is correct-or-better in every case: with real pending
265
- transactions `pending` is higher and wins.
266
-
267
- Reported in the 2026-08-24 field notes (hit independently on three of four projects).
268
-
269
- ## 0.1.0-alpha.21
270
-
271
- ### Minor Changes
272
-
273
- - c9f7aeb: Fold the burn, and make "canonically ABX v2" sayable — from abx-services' 2026-08-20 reply
274
- (`reviews/2026-08/services-reply-docs-23.md`). All off-chain: no contract changed, no address moved.
275
-
276
- **SDK — `foldSpine` now folds an ERC-721 burn, and `TokenState.minted` is replaced by `lifecycle`
277
- (BREAKING).** A burn wrote the zero address into `TokenState.owner` — where nothing downstream could
278
- tell it from a holder — and `minted` latched `true` at mint and was never recomputed, so a burned
279
- token reconstructed as live. `lifecycle: 'unminted' | 'live' | 'burned' | 'no-live-copies'` replaces it,
280
- and `owner` is `null` for a burned id rather than a sentinel. An enum rather than the `minted` +
281
- `burned` pair the report asked for: a boolean whose `false` has two meanings leaves the burn case one
282
- forgotten field away from rendering as "not yet minted", which is exactly what our own CLI did.
283
-
284
- **`'burned'` is terminal and ERC-721-only; an edition at zero live copies is `'no-live-copies'`.** The
285
- first cut of this enum shared `'burned'` across both standards, on the grounds that "one vocabulary on
286
- both lanes" was the honest shape. It wasn't: the correct _response_ to destruction differs by standard
287
- (a 721 id is gone forever and must `410`; an edition id can mint again and must not), so a shared word
288
- put every consumer one forgotten `contractType` branch away from serving `410 Gone` for a token the
289
- contract still resolves — and our own migration note told the first consumer to map the shared word
290
- straight onto their `410` sites. abx-services caught it in review before it published. Splitting the
291
- word moves the rule out of prose and into the type: **`lifecycle === 'burned'` is safe to treat as
292
- permanent on either standard, with no carve-out**, which let the reference resolver's `isEditionState`
293
- guard go away entirely. Pinned from both ends — no edition history can fold to `'burned'`, and the
294
- route asserts its own standard-blindness.
295
-
296
- **The two lanes now agree id-for-id.** The same review found `TokenRow.lifecycle` (head reads) reporting
297
- `'unknown'` for a fully-burned edition id while the fold reported `'burned'` for that same id — under a
298
- docstring claiming the two lanes used the same words. That is the sibling-drift class this repo treats
299
- as a bug (see `TokenState.maxSupply`'s note, the last time one field name carried two meanings). Both
300
- lanes now answer `'no-live-copies'` there: the fold _could_ distinguish never-minted from fully-burned
301
- and deliberately does not, because the distinction has no consumer. `'unknown'` survives for the one
302
- case where a head read genuinely cannot say — a 1/1 has no mint frontier, so a reverting `ownerOf`
303
- there is evidence of nothing.
304
-
305
- What the latched boolean was costing, all in our own tree: the effects harness re-rendered destroyed
306
- tokens on every sweep, forever; `onchain-uri`'s probe reads the LOWEST live id, so burning token 0 made
307
- a healthy collection's on-chain-URI lane report as **broken**; the resolver served metadata — and the
308
- pre-mint _warming placeholder_, i.e. "still loading" forever — for ids the contract disowns; `mintedCount`
309
- never went down; and `abx state` printed "not yet minted" for a token that had been destroyed.
310
-
311
- **SDK — `BurnConfigured` and `MaxRoyaltyBpsUpdated` fold into `ProjectState.burnable` /
312
- `.maxRoyaltyBps`.** Both events shipped in the ABI and in `SPINE_EVENT_DOC` and reached no field of
313
- state; the upgrade memo then told consumers the doc entry _was_ the fold, which it has never been (it
314
- supplies `register`/`what` on the event record, full stop). Both are tri-state: `null` means the spine
315
- never stated it — an implementation with no `burn` entrypoint and an unpublished ceiling — which is not
316
- `false`, and not 10%. The ceiling is deliberately not nested inside `royalty`, since clearing a royalty
317
- nulls that field while the ceiling stays binding on chain.
318
-
319
- **SDK — a fold-coverage guard, because this was the second instance in four days.** `DefaultMaxSupplySet`
320
- did the same thing on 2026-08-17. `SPINE_EVENT_NO_FOLD` now lists the events that deliberately reach no
321
- state _and why_ (a ping whose value is a head read, a factory's own log, a minter sibling, an allowance),
322
- and `spine-fold-coverage.test.ts` asserts every decodable event is either folded or excused. A new event
323
- is unfolded and unexcused until someone decides which.
324
-
325
- **SDK — a burned 1/1 no longer vanishes from `listTokens`.** The id range fell back to `totalSupply`
326
- (live: mints − burns) when a token type exposes no `nextTokenId`, so burning a 1/1's only token made the
327
- listing enumerate ZERO ids — `abx tokens` printed nothing at all, indistinguishable from a collection
328
- with no tokens. A 1/1's id space is `{0}` forever regardless of what is live. Found by burning a real
329
- token on Sepolia rather than by any fixture, which is the only way this one surfaces.
330
-
331
- **SDK — head reads can now say `burned`.** `nextTokenId` is a mint frontier that only rises, so an id
332
- below it whose `ownerOf` reverts was minted and destroyed. `TokenRow.lifecycle` (`'live'` / `'burned'` /
333
- `'unminted'` / `'unknown'`) and `TokenListing.burnedCount` (`nextTokenId − totalSupply`) drop out of that,
334
- with no event log. `'unknown'` stays a real member where the chain declines to say: a 1/1 has no frontier,
335
- and an edition's `supply == 0` cannot distinguish never-minted from fully-burned.
336
-
337
- **SDK — `burned` joins `ServiceErrorCode`, paired with `410 Gone`.** A remote token API had nothing
338
- honest to say about a destroyed id and was sending `410` with `code: 'not_registered'` — a statement
339
- about the _contract_. The rule behind the pairing is the general one: **a resolver answers what the
340
- contract's own URI getter answers.** A burned 721's `tokenURI` reverts `NonexistentToken`, so serving a
341
- document would contradict the contract; an ERC-1155's `uri(id)` has no existence gate and a zero-supply
342
- id can mint again, so **an edition never 410s** — it serves, with `supply: 0`.
343
-
344
- **SDK — `readCollectionPolicy`, `canonicalFactories`, `verifyProvenance`: protocol knowledge moves out
345
- of the CLI.** The CLI was declaring its own ABI fragments for `burnable`/`maxRoyaltyBps` and reading
346
- them itself in two places, so no other integrator could reach either fact. `canonicalFactories(chainId)`
347
- replaces two copies of the six-anchor enumeration inside `anchors.ts` — the list whose completeness _is_
348
- the trust model. `verifyCanonical` gains `opts.factories`, which **replaces** the manifest set rather
349
- than appending to it, so a multi-tenant operator's pinned allowlist can drive the gate without silently
350
- widening it.
351
-
352
- **SDK — anchor generations: "canonically ABX v2", not a bare `false`.** `verifyCanonical` reported
353
- "deployed by an ABX factory since replaced" and "deployed outside the toolkit entirely" identically —
354
- its own docstring admitted it — and the first is a perfectly good collection. `ANCHOR_GENERATIONS` keys
355
- each generation of the six trust anchors by the **on-chain core version** its clones report, and
356
- `verifyProvenance` returns `{canonical, generation: 'current' | 'prior' | null, coreVersion, factory,
357
- anchorsAnswered}`. The version is on the clone itself, so the answer is chain-verified rather than a
358
- manifest claim, and it keeps working after a factory retires. Retired generations never feed
359
- `verifyCanonical`: provenance is not trust — a superseded generation can predate a security remediation.
360
- Pre-launch testnet generations are deliberately not backfilled.
361
-
362
- **Redeploy process — a token-layer runtime change is a new anchor generation.** It bumps
363
- `AbxVersion.CORE_VERSION` and moves the SDK's `isCurrent*` probes in lockstep. That is now a checklist
364
- step (`contracts/README.md`, `CLAUDE.md`) _enforced by_ `deployments.test.ts`: recorded generations must
365
- have unique, increasing core versions and the newest must match the constant in `AbxVersion.sol`, so a
366
- batch cannot record a generation without bumping or bump without recording. The gap that prompted it: the
367
- 2026-08-20 batch added `burn()`, `burnable()`, `maxRoyaltyBps()` and `reduceMaxRoyaltyBps()` to the CORE
368
- base — burn is not an extension, so no extension version moved either — and left the constant at 2, so
369
- `abxVersion() == 2` cannot tell a burn-capable token from a pre-burn one.
370
-
371
- **CLI — the royalty ceiling is shown with its rate, and headroom is named.** A ceiling above the live
372
- rate is royalty the owner can add unilaterally, and a listing page never shows it. `abx state` prints the
373
- pair and, when there is headroom, says the useful thing: reducing the cap **to** the current rate is what
374
- turns "5% today" into "5%, provably, forever". (Named by abx-services as their `royalty-headroom` flag —
375
- a marketplace can compute it, so a creator should hear it from us first.)
376
-
377
- **Reference stack — the same fixes, so the reference is exemplary rather than the counter-example.**
378
- `mintedCount` keeps its name and counts LIVE ids, with `burnedCount` added only when non-zero (so a
379
- project with no burns serves byte-identical JSON, and a consumer's fixtures keep telling the truth);
380
- the indexer projection carries `lifecycle`; the effects status route and the render sweep skip destroyed
381
- ids; the dashboard badges a burned token instead of calling it minted; and `abx verify` / the migrate and
382
- re-point probes never choose a burned token as their subject.
383
-
384
- **CLI — `--send` is accepted on the deploy paths, and two lane flags is refused.** Every deploy's help
385
- advertises the signing lanes as `--send hot/env key · --sign wallet page · --unsigned print tx`, in 25
386
- places, and `--send` was the one of the three the deploy allowlist rejected: typing what the help showed
387
- produced `unrecognized flag(s): --send` from the guard whose whole purpose is catching flags that would
388
- be silently ignored — the guard firing on the tool's own documentation. Found by using the CLI as a cold
389
- reader of its own `--help` while setting up an on-chain test. While there, lane resolution moved from
390
- truthiness to **presence** (`--sign=` parses to `''`, which is falsy, so a caller who explicitly named
391
- the wallet lane was routed to the unattended env key — the one direction of that bug that costs
392
- something), and naming two lanes at once now refuses instead of resolving by an undocumented precedence.
393
-
394
- **Packaging — every published tarball ships its CHANGELOG.** `files` was `['dist', 'LICENSE']` on the
395
- SDK, indexer, storage, effects and token-api, so the only package whose release notes reached consumers
396
- was the CLI. A downstream team upgrading the SDK had no CHANGELOG in the tarball and diffed two `dist/`
397
- trees to find out what moved — then reported the release notes as missing, which cost a whole exchange
398
- to adjudicate. One line each.
399
-
400
- **Docs + spec.** The event spine gains a Burn section and the `MaxRoyaltyBpsUpdated` row, both with the
401
- fold rules an indexer relies on; three stale deploy-event-order lists are corrected; the remote-services
402
- error taxonomy gains the `410 burned` row and the edition carve-out; `owner-powers` explains headroom and
403
- burn permanence; and `indexing-notes` states plainly that documenting an event is not folding it.
404
-
405
- ## 0.1.0-alpha.20
406
-
407
- ### Patch Changes
408
-
409
- - 84ca9dd: Off-chain fixes from the 2026-08-19 production-readiness audit
410
- (`reviews/2026-08/production-readiness-audit.md`). All are off-chain — no contract changed and no
411
- address moved; the three NatSpec-precision items the audit also raised ride the next redeploy batch
412
- instead (`docs/10-backlog.md` → _Contracts — the next redeploy batch_, B47).
413
-
414
- **SDK — `HexColor` decode now masks to 24 bits, matching the chain (L-1).** `decodeScalarParam` for a
415
- `HexColor` value greater than `0xFFFFFF` returned `#1ffffff` (7+ hex digits); the on-chain
416
- `TokenDataLib.decodeScalar` masks with `& 0xffffff` and returns `#ffffff`. The governed write validates
417
- `v <= 0xFFFFFF`, but the raw owner setter does not, so an out-of-domain value can be stored and later
418
- governed by a `HexColor` schema — and the two serving planes would then decode it differently, splitting
419
- `inputsHash`. The SDK now masks identically. Pinned on both sides so it cannot drift again:
420
- `decodeScalarParam('HexColor', 0x1ffffffn) === '#ffffff'` (SDK) and the twin `test_DecodeScalars`
421
- assertion in `contracts/test/FieldRenderer.t.sol`.
422
-
423
- **SDK — gateway-prefix projection no longer trims (L-7).** `projectGatewayPrefix` dropped the `.trim()`
424
- on the on-chain `abx_gateway_*` value: the deployed `_gatewayPrefix` returns the stored bytes verbatim,
425
- so trimming here made the two planes disagree on a whitespace-padded prefix. The field store rejects an
426
- empty value, so a set field is always non-empty. Clean prefixes (every real one) are unaffected.
427
-
428
- **SDK — `permissionlessSalt` docstring hardened (L-3).** Now states explicitly: never use it for a
429
- pre-published or pre-funded address — a third party can occupy the predicted address first with their own
430
- `InitParams`. For any reserved address, use `saltFor` (its deployer-bound prefix is front-run-proof). No
431
- behavior change; the shipped CLI/skill already deploy through `saltFor`.
432
-
433
- **CLI — the edition schema advisory calls out a holder-writable `seed` by name (L-2).**
434
- `editionSchemaAdvisory` already warned that a `TokenOwner` param on an edition is shared per-id
435
- (last-writer-wins); it now adds a sharper note when that param is `seed`, because the normally-immutable
436
- generative seed becomes re-rollable by _any_ holder of the id — changing the artwork for every
437
- co-holder. The on-chain behavior is deliberately unchanged (the reassignable-seed feature is
438
- buyer-verifiable via `paramSchema("seed")`); the guardrail is this warning, not a contract restriction.
439
- A controller contract on the `Address` leg remains the way to govern it.
440
-
441
- - 84ca9dd: Finish the burn + royalty-cap surfacing, from the 2026-08-20 w6 agent sandbox sweep. All off-chain —
442
- no contract changed, no address moved; the on-chain features were verified working end to end (a cold
443
- agent deployed `--burnable --royalty-cap 42`, minted, burned, and proved the cap enforced on Sepolia).
444
- The sweep's convergent finding was that the two new **permanent** deploy-time decisions were invisible
445
- after you set them; this makes them visible everywhere they're decided or read, and adds the owner's
446
- missing reduce lever.
447
-
448
- - **New: `abx set-royalty-cap <address> --cap <0-10000>`** — the owner's reduce-only royalty-ceiling
449
- lever (`RoyaltyExtension.reduceMaxRoyaltyBps`), the "after" the deploy-time "reduce-only after"
450
- promise never had. Both ways the chain refuses are checked locally before signing: a value that
451
- wouldn't decrease (`RoyaltyCapNotReduced`) or that would drop below the live royalty rate
452
- (`RoyaltyCapBelowRoyalty`). SDK: `prepareReduceMaxRoyaltyBps`.
453
- - **The deploy readout now shows `royalty cap` and `burnable` on every lane** (1/1, series, code, and
454
- all three edition twins), whenever set. Critically it flags an **auto-raised** cap
455
- (`royalty cap 25% (auto-raised to fit the royalty)`): omitting `--royalty-cap` while setting a
456
- royalty above 10% silently raises the permanent ceiling to match, which was previously invisible.
457
- - **`abx state` now surfaces `royaltyCap` and `burnable`** (human + `--json`), the post-deploy
458
- verification surface, for both the 721 and edition readouts.
459
- - **The skill documents the royalty cap** — the identity decision (`decisions.md`), the confirm-
460
- identity step (`SKILL.md`), and a new `set-royalty-cap` owner-op row (`operating.md`); previously the
461
- reduce-only ceiling feature had zero skill coverage.
462
- - **Help/doc corrections:** `set-royalty --help` range `0-1000`→`0-10000` (stale from the hard-cap
463
- era); `deploy-series --help` now lists `--royalty-cap`/`--burnable`; `deploy --help` states the cap
464
- auto-raises; `abx inspect`'s trait-feasibility deep-dive points at public docs, not a repo path.
465
-
466
- ## 0.1.0-alpha.19
467
-
468
- ### Patch Changes
469
-
470
- - b1c333d: **Asking for a 15% royalty built a transaction that reverted on chain.** `RoyaltyExtension` caps every
471
- ABX token at **1000 bps (10%)** and reverts `RoyaltyTooHigh()` above it, and no token type overrides that.
472
- But client-side validation allowed the full ERC-2981 `0–10000` range — and the CLI's own error text
473
- offered `10000 = 100%` as a worked example. So a creator asking for an ordinary 15% got a fully built,
474
- fully signed transaction that failed on chain; on the wallet lane they approved it in their own wallet
475
- and paid the gas to find out. Both usage strings said `<0-10000>`, and so did two public docs.
476
-
477
- Now `MAX_ROYALTY_BPS` (SDK) mirrors the contract ceiling and both `prepareSetRoyalty` and the CLI's
478
- `parseRoyaltyBps` validate against it, so the refusal is instant, local and free, and it names the cap
479
- and the revert. Regression tests cover it from both sides — including `1500`, the value that used to
480
- sail through. Two existing tests had to be corrected: they asserted the 0–10000 bound, which is how this
481
- survived.
482
-
483
- Three more surfaces that contradicted themselves or the chain:
484
-
485
- - **`abx ping-uri --help` claimed "Permissionless by design … ANY signer may call it (no owner check)"
486
- four lines below its own `OWNER-ONLY` header** — and `Uri1155.pingURI` is `onlyOwner`. A non-owner
487
- following the paragraph built a reverting tx.
488
- - **`site/content/docs/reference/contracts.mdx`** described the pre-audit library architecture: it omitted
489
- `AbxMetadataLib` (the fourth delegatecalled library, and the one linked by **all six** token types, not
490
- three) and still said `SeriesCode` "rides the ceiling", where `deployments.mdx` — written after the same
491
- redeploy — says both code types now sit under EIP-170 with room to spare.
492
- - **`deployments.mdx`'s own `specVersion()` example printed `# → 10`** under a table declaring the renderer
493
- spec v11. And **`operate.mdx`'s self-correction had rotted**: it warned that `abx refresh`'s advisory was
494
- stale about editions and ERC-4906, but that advisory was fixed — the doc was now the stale one.
495
-
496
- Found by two read-only audits (29 doc files, 19 spec files) that verified every claim against contracts,
497
- compiled ABIs, SDK and live `--help` output before reporting. Both independently re-confirmed the
498
- no-burn property, the v11 gateway design, all ~26 canonical addresses, and the on-chain storage
499
- thresholds.
500
-
501
- - b1c333d: An agent using this skill built its own smart-contract system, and neither it nor the creator noticed
502
- that the collection had stopped being a canonical ABX clone. Canonicity is decided by which contract
503
- deployed a token and can never be added afterwards, so the fix is a gate before the first send plus a
504
- readout that makes the loss discoverable.
505
-
506
- **`abx state` now reports canonicity first.** It read owner, supply, royalty and renderer off any
507
- ERC-721-shaped address and said nothing about whether it was a factory clone — so a hand-rolled contract
508
- was described exactly as confidently as a canonical one, and the tool whose whole job is "what IS this
509
- contract?" left nothing behind for the creator to find. `verifyCanonical` (SDK) probes all six trust
510
- anchors' `isAbxClone` in one multicall and returns a true tri-state; a `false` prints what was lost, that
511
- marketplaces and the App Store recognize collections by that signal, and that the only remedy is a fresh
512
- deploy through the factory plus moving holders. `null` (no anchors on this chain, or an RPC failure) is
513
- never shown as a "no".
514
-
515
- **The skill gained the boundary rule it never had.** Its trust material lived on the _last line_ of the
516
- file, written for someone verifying a clone from outside, and the "don't hand-roll" rules that existed
517
- were narrow — preview pages, ABI writes, token queries, nothing about contracts. There is now a
518
- Read-first gate: if the ask seems to need something the deploy commands don't do, stop and put it to the
519
- creator in words, because building your own contracts is a legitimate choice a creator can make and
520
- making it _for_ them silently is not. Alongside it, the list of what the toolkit does **not** do — no
521
- burn (verified: zero `burn` entrypoints in `contracts/src`, previously a parenthetical clause inside a
522
- sentence about hooks), no secondary listings, no mainnet, fixed-price sales only, no post-deploy script
523
- replace — and the two extension points that keep a collection canonical (`set-minter`, param hooks), with
524
- the caveat that a `--transfer` hook is a **veto, not a trigger**: it can refuse a transfer but never cause
525
- a mint or a burn, so it cannot implement "combine two into one".
526
-
527
- Verified by two trap rooms — a creator asking for a burn-to-combine mechanic with "you've got my
528
- go-ahead, I don't need the technical parts". Both Sonnet and Haiku refused to build a contract, cited the
529
- canonicity loss, and returned options. The Sonnet room found the route that _preserves_ canonicity and
530
- offered it first.
531
-
532
- **SKILL.md was restructured** against its own "lean decision-tree router" norm, which it had drifted
533
- ~40% past: **68,449 → 55,392 characters (−20%)**, longest bullet 2,876 → 1,465, bullets over 600 chars
534
- 27 → 14, with depth moved into the reference files that already owned it (locks → `decisions.md`;
535
- install/`.env` → `setup.md`, which was a 44-line stub; data plane, App Store and deploy mechanics →
536
- `operating.md`). Also repaired: a **markdown table split in two** by a blockquote sitting between its
537
- rows, and **four dangling anchor links** (three pre-existing) — all 96 internal links now resolve.
538
- `setup.md` was retitled and reordered after a control room found the required install steps buried under
539
- a title that promised RPC troubleshooting.
540
-
541
- **Two contradictions the verification sweep caught**, both the same class as the rest of this week's
542
- fixes. `deploy-code --dry-run` printed `render/storage ✓ fs + --image-base` a few lines below
543
- `⚠ render storage home is fs … placeholder forever`, for the identical setup; the ✓ is legitimately
544
- scoped to whether the `--image-base` target is a valid mutable bucket (two tests pin that), so the row now
545
- states what it covers and points at the other line rather than appearing to overrule it. And **"not
546
- backfillable" was overstated** in three places — the truth, which `code-projects.md` had right all along,
547
- is that fixing a surface after deploy costs an owner-signed re-point plus a re-render on a collection that
548
- showed a placeholder in between. Expensive and worth avoiding; not impossible.
549
-
550
- ## 0.1.0-alpha.18
551
-
552
- ### Patch Changes
553
-
554
- - 77a6248: `abx deploy --bootstrap-factory` failed on the first run against any chain that did not already have
555
- a factory — and succeeded on the second.
556
-
557
- Every ABX singleton is deployed by CREATE2 through the keyless proxy, so the transaction is a **call
558
- to that proxy**, not a contract creation, and `receipt.contractAddress` is null by protocol. Two of
559
- the six factory bootstraps — the two oldest, the ERC-721 image ones — still read the address off the
560
- receipt, so they threw `Factory deploy produced no contract address` _after_ the factory had actually
561
- landed. Re-running then found the factory on chain and skipped the bootstrap entirely, which is why
562
- this read as flakiness rather than a bug. `deployRenderer`, the library legs, and every newer factory
563
- already computed the address; these two now do the same (`predictFactory()` / `predictSeriesFactory()`).
564
-
565
- The blast radius was every fresh chain: a private chain, a local anvil, a sandbox — and the mock
566
- provider fixture the hosted-services eval rooms are built on, which is how this surfaced. On a real
567
- testnet the factories already exist, so nothing that ships today was affected.
568
-
569
- Pinned by a regression test that fails on the old code two ways: behaviourally (a `send` that returns
570
- a receipt with no `contractAddress`, exactly like the proxy does, must still yield the deterministic
571
- address) and statically (no bootstrap in `deploy.ts` may derive an address from
572
- `receipt.contractAddress`, so a seventh factory cannot reintroduce it).
573
-
574
- - 77a6248: Fixes from the 2026-08-18 eight-room agent sweep of the content-addressed lanes (ipfs · arweave ·
575
- preferred gateways). The v11 gateway projection worked end to end on a live testnet round trip — bare
576
- CID on chain, one `set-gateway` tx, byte-identical content id before and after — but the surfaces
577
- around it lied, hid, or degraded in five ways that cold agents hit immediately.
578
-
579
- **Both `AbxGenerator` addresses in the manifest had a broken EIP-55 checksum.** The addresses were
580
- right; the casing was mangled in transcription. viem validates checksums, so
581
- `readContract`/`writeContract` against the recorded value threw `Address "0x2c1b7Cf6…" is invalid` —
582
- `abx verify` could not run its on-chain-URI check on **either** testnet. Corrected in
583
- `deployments.ts` and the docs mirror, with the code at both fixed addresses re-confirmed on chain
584
- (identical bytecode, as CREATE2 requires). The per-entry equality tests stayed green through all of
585
- it because they compared against the same mangled string, so the manifest now carries a **property
586
- test**: every recorded address must be a valid checksum.
587
-
588
- **A deploy narrated the upload gateway as if it were the served URL.** With
589
- `--onchain-uri --backend ipfs`, the progress line printed the backend's own locator
590
- (`https://gateway.pinata.cloud/ipfs/<cid>/0.png`) while the chain got a bare CID served from
591
- somewhere else entirely — in one run, from `ipfs.io`, because that is the floor and no preference was
592
- written. The line now shows the URL the token will actually carry, resolved through the same
593
- precedence that decides what gets written (`--ipfs-gateway`/`--arweave-gateway` → `--gateway` for the
594
- backend in use → env → floor), and says whether that prefix is the project's choice or the public
595
- default filling a silence.
596
-
597
- **`--ipfs-gateway` / `--arweave-gateway` were absent from every deploy command's `--help`.** They
598
- appeared only in `set-gateway --help` and the skill's reference pages, so an agent reading the CLI
599
- alone could not find the flags the CLI itself tells you to use. All three deploy commands now name
600
- them, with the floors (`https://ipfs.io/ipfs/`, `https://arweave.net/`) and the upload-vs-serving
601
- distinction stated where the decision is made.
602
-
603
- **`abx storage upload` handed `abx attach` a locator that silently downgraded the attachment.** It
604
- printed `abx attach <address> <key> https://gateway.pinata.cloud/ipfs/<cid>/file.png` — and `attach`
605
- picks the on-chain representation from the scheme, so an `https://` locator stores a plain `url` with
606
- the gateway host welded into the value: no `set-gateway` repoint, exactly the coupling the
607
- content-addressed representations exist to remove. Following the command's own printed next step was
608
- the wrong move — and the skill had promised the scheme form all along, so the doc was right and the
609
- command was wrong. It now hands back `ipfs://<cid>/file.png` / `ar://<txid>/file.png` (both in the prose
610
- hint and as `attachLocator` in `--json`), keeps the https URL as the browsable one, and says why they
611
- differ.
612
-
613
- **`.abx-self-host/` is now self-ignoring.** It holds this node's projection and, for the Arweave
614
- backend, a signing-capable key that carries prepaid upload credits. This repo gitignores that
615
- directory and the skill said so — but a _creator's_ repo does not, so a first `--backend arweave` run
616
- could leave a key staged for commit with nothing having said a word. The CLI writes a `.gitignore`
617
- containing `*` inside the directory before anything sensitive lands there: it ignores the whole tree
618
- regardless of the enclosing repo's config, needs no edit to a file we don't own, and never overwrites
619
- one that already exists.
620
-
621
- The **local-gateway warning** also stopped over-claiming. It said a `127.0.0.1` gateway means "the
622
- served image URL resolves only on THIS machine" — true when the gateway host was welded into the
623
- field, false now that the field holds a bare CID, and it aimed the creator at the wrong repair. It now
624
- names the damage that is still real: bytes pinned only to a local node cannot be retrieved by anyone,
625
- and no gateway can serve content that was never pinned publicly.
626
-
627
- Also: `abx storage status --json` no longer warns `unrecognized flag(s), ignored: --json` about a flag
628
- its own usage documents and that visibly works (a readout contradicting itself is worse than no
629
- readout), and the `--unsigned` epilogue names `abx add <address>` rather than `abx index <address>`,
630
- which fails on a node that never registered the project — the common case for a tx handed to someone
631
- else's signer.
632
-
633
- ## 0.1.0-alpha.17
634
-
635
- ### Minor Changes
636
-
637
- - c5b8ec3: `ipfs` and `arweave` metadata fields now resolve on both planes, through a gateway the collection
638
- chooses — renderer **spec v11**.
639
-
640
- These are the two representations that are content-addressed, the two where the locator IS the
641
- integrity hash, and the two this protocol tells creators to prefer. They were also the two the
642
- on-chain renderer could not serve. So `abx deploy --onchain-uri --backend ipfs|arweave` worked around
643
- it by baking a gateway HOST into a `url` field, which welded a hostname the creator could never
644
- migrate and made `abx_provenance` report `source: url` for bytes that live on IPFS.
645
-
646
- The fix separates the two facts that the baked URL had fused. The CID/txid stays in the field, as
647
- identity, under that field's own lock. The HTTPS prefix becomes a project-wide preference in two
648
- reserved collection-scope fields, `abx_gateway_ipfs` / `abx_gateway_arweave`, with `https://ipfs.io/ipfs/`
649
- and `https://arweave.net/` as floors. The renderer, the canonical generator, and the off-chain
650
- resolver all read those two fields and apply the same wrap, so a token served from the chain and from
651
- a resolver produces the same URL.
652
-
653
- A dead or slow gateway is now a **repoint**, not a rewrite:
654
-
655
- ```bash
656
- abx set-gateway 0xYourContract --ipfs https://your-dedicated.mypinata.cloud/ipfs/
657
- ```
658
-
659
- One transaction, every token, no re-upload — and it works on fields you have already locked, because
660
- the locked value is the CID and the gateway was never in it.
661
-
662
- **What changes for you**
663
-
664
- - `--onchain-uri --backend ipfs|arweave` writes the bare CID (or `cid/{id}.ext` for the O(1) directory)
665
- as an `ipfs`/`arweave` field instead of a gateway URL as `url`/`url-template`. `--backend cloud` is
666
- unchanged — an HTTPS CDN locator _is_ the address.
667
- - New `abx set-gateway <addr> [--ipfs <prefix>] [--arweave <prefix>]` (`none` clears to the public
668
- default). Two flags, so a project can pay for a dedicated IPFS gateway and leave Arweave public.
669
- - New `--ipfs-gateway` / `--arweave-gateway` on every deploy command. Plain `--gateway` (the
670
- storage/upload gateway) seeds the preference for the backend in use, so passing it still does what
671
- you meant. Nothing is written when no gateway is named — silence keeps the floor live.
672
- - `abx set-field` refuses the two gateway keys and points at `set-gateway`, which validates the
673
- prefix, the scope and the representation. Every way to set them by hand fails silently.
674
- - The `display.gateway` contract param is **superseded and no longer read**. It could name only one
675
- prefix for both schemes and existed only on code projects.
676
- - SDK: `projectGatewayPrefix`, `projectGatewayUrl`, `gatewayPrefixFrom`, `contentIdFromLocator`,
677
- `GATEWAY_FIELD`, `GATEWAY_FLOOR`. For a host running its own resolver, `ABX_IPFS_GATEWAY` /
678
- `ABX_ARWEAVE_GATEWAY` are now a **floor**: a project that stated a preference on chain gets that
679
- preference from every conforming resolver, and host config only fills a silence.
680
-
681
- **New canonical addresses** (both testnets; nothing else moved — no token implementation, factory, or
682
- `initialize` ABI changed):
683
-
684
- | Contract | Address |
685
- | -------------------------------- | -------------------------------------------- |
686
- | `AbxMetadataRenderer` (spec v11) | `0x85C1aE1F076d808fF7c1729F21B85038Fa16105E` |
687
- | `AbxGenerator` (Sepolia) | `0xb7104AdFa6Fb5615e46E2a681A2Ff043B08fADB5` |
688
- | `AbxGenerator` (Base Sepolia) | `0x2c1b7Cf6C54E4acBcB54FCc395f7Af88eB4fC8Ce` |
689
-
690
- Also fixes a crash on `abx deploy-code --copies`: an undefined `bytes` reference threw a
691
- `ReferenceError` at the end of the dry-run plan and, worse, at the confirmation prompt of a real
692
- deploy.
693
-
694
- - 5b29f53: Drop-in for every reconstruct caller; new exports for hosted/self-host watch loops so they stop
695
- reimplementing `eth_getLogs` chunking and a second per-project fetch.
696
-
697
- **`getLogsAdaptive` is public and accepts `Address | Address[]`.** Address-list chunking defaults to
698
- 1000 (ordinary RPC tier cap) via `addressBatch` — an RPC opinion, not a protocol constant; `0`
699
- disables splitting. Per-tick block windows stay with the watcher (the reference one still uses 5000).
700
-
701
- **`reconstructFromLogs(client, prior, logs, {toBlock})`** is the fold / head-read half of
702
- `reconstructIncremental` given logs the caller already holds. `toBlock` is the inclusive scan head
703
- those logs cover, so a capped window cannot stamp past the last block actually scanned.
704
- `reconstructIncremental` is now a getLogs + that function.
705
-
706
- **A Transfer no longer re-downloads every on-chain script chunk.** Chunk content has no log;
707
- `ScriptUpdated` is the ping. `applyHeadReads` keeps the prior digest unless that ping is in `fresh`
708
- (or the prior digest is missing). Same change helps the single-address path.
709
-
710
- The reference token-api watcher now calls `getLogsAdaptive` for the registered set and hands each
711
- touched project's logs to `reindex({logs, toBlock})`, so the watch tick is the only getLogs.
712
-
713
- ## 0.1.0-alpha.16
714
-
715
- ### Minor Changes
716
-
717
- - b5744a1: An RPC that has pruned its log history is now detected, named, and routed around
718
-
719
- A public endpoint that no longer holds old logs does not fail your scan — it answers `eth_getLogs`
720
- with `[]` and HTTP 200, which is a success. Everything downstream believed it:
721
-
722
- - **`abx doctor` graded it `best`.** The archive probe was
723
- `getLogs({address: zeroAddress, fromBlock: old, toBlock: old})` and counted any non-throw as archive
724
- access — but `zeroAddress` never emits logs, so an empty result is exactly what a _perfect_ archive
725
- node returns too. The assertion could not fail; it only ever caught endpoints that _error_ on old
726
- blocks. `ARCHIVE_DEPTH` was also 100k blocks, which on a 2-second chain is inside the retained window
727
- of the very endpoints that prune.
728
- - **The client never failed over.** The read transport is a viem `fallback` across every configured
729
- endpoint, and `fallback` rotates on an _error_. So every retry asked the same endpoint the same
730
- question, and a project whose logs sit ready on the SECOND configured endpoint reconstructed as
731
- empty.
732
- - **`abx add` then registered the project with no state at all**, after grinding half a million blocks,
733
- and suggested re-running `--full` "in a minute" — advice that can never work for a pruned index.
734
-
735
- Measured on the two default Base Sepolia endpoints, same address and same window: one returns zero
736
- logs for the project's entire history, the other returns them instantly. Ethereum Sepolia's keyless
737
- default endpoint shows the same shape past a few hundred thousand blocks.
738
-
739
- - **The probe asserts on something that must come back.** Blocks are retained by everyone; receipts
740
- and the log index are what get pruned. `probeHistoryAt` (new SDK export) reads the probe block, asks
741
- for one of its transactions' receipts, and — when that receipt carries a log — requires `getLogs` to
742
- return that same log. No hardcoded address, no fixture block. `ARCHIVE_DEPTH` is now 500k.
743
- It fails **safe**: a probe block with no transactions, or whose transactions logged nothing, keeps the
744
- old lenient verdict rather than inventing a failure.
745
- - **`abx doctor` names a reachable endpoint that can't serve history**, with the fault and the fix
746
- ("history pruned ~500000 blocks back … list a full-archive endpoint FIRST in `ABX_RPC_URLS_<CHAIN>`").
747
- Collapsing to the best endpoint hid this, and ordering is what decides who answers the scan.
748
- - **An empty scan now retries each endpoint on its own** before giving up, and says which one served
749
- the logs. Cheap by construction: each endpoint is first asked whether it holds history at _this
750
- project's_ deploy block — three calls — and only one that passes gets a scan.
751
- - **`SelfHostIndexer.reindex(address, {rpcUrl})`** pins a single endpoint instead of the pool, which is
752
- what makes that rotation expressible. Clients are pooled per (chain, endpoint), so the default path
753
- is unchanged.
754
- - The final failure message now separates the two cases it had merged: a project you _just_ deployed
755
- (transient, retry) from one that is not new (retention — check `abx doctor`).
756
-
757
- Found by direct probing during the 2026-08-17 agent sweep, after an `abx add` of a 12-day-old Base
758
- Sepolia edition reconstructed zero of its 18 events.
759
-
760
- ### Patch Changes
761
-
762
- - 9c287c0: `abx inspect` no longer lists trait keys when there is no real `abx.traits(` call.
763
-
764
- A string that _mentioned_ `abx.traits({foo: 1})` used to fill the Traits section while Runtime said
765
- "no abx.traits call". The key extractor now runs only when the call survives in the code view
766
- (comments stripped, strings blanked). Quoted keys inside a real call (`'Plant Count':`) still
767
- work. Terser `window.abx&&(abx.traits({…}),abx.done())` and an accessor at the end of a large file
768
- are pinned by tests.
769
-
770
- Reported in the 2026-08-18 tester batch (feedback 0a0f8f9c, 1253c563).
771
-
772
- - 9c287c0: On-chain script chunks no longer insert a newline in the middle of a token.
773
-
774
- The generator joins chunks with `'\n'`. A fixed 22 kB slice could split `mass` into `ma` + `ss`,
775
- producing a SyntaxError on chain while `abx verify` still went green. Template-mode deploys now
776
- split at an existing newline so that join is byte-identical to the source, refuse a 22 kB+ span
777
- with no newline, and parse the program before any gas.
778
-
779
- Reported in the 2026-08-18 tester batch (feedback 38455765, 038d7ed2, 8c256d9d, 50e53193).
780
-
781
- ## 0.1.0-alpha.15
782
-
783
- ### Minor Changes
784
-
785
- - 7fec2d7: An edition's per-id cap now folds from the log, so a capped edition stops reporting itself as open
786
-
787
- `DefaultMaxSupplySet` — added in the contract-audit release precisely so the log could tell an open
788
- edition from a capped one — reached the ABI and stopped there. `reconstruct.ts` had no fold case, so
789
- the collection-wide default never landed in `ProjectState`, and **the common shape was the broken
790
- one**: `--copies N` sets the cap at `initialize` and never calls `setMaxSupply`, so no id has a
791
- `MaxSupplyUpdated` of its own and every capped edition ever deployed folded as _uncapped_. The
792
- head-read lane (`listTokens`, which calls `maxSupply(id)`) said `N` the whole time — one field name,
793
- two answers, on the value that decides whether a buy button renders. Found by the abx-services team
794
- while upgrading their resolver/indexer to this release.
795
-
796
- - **`ProjectState.defaultMaxSupply`** — the collection-wide per-id default (`'0'` = open; `null` ⇒ not
797
- an edition). The 1155 analogue of `maxInvocations`.
798
- - **`TokenState.maxSupply` is now the _effective_ cap** — an id's own override, else that default —
799
- and so equals `maxSupply(id)` on chain and its head-read twin `TokenRow.maxSupply`. Previously it
800
- was the override alone, and absent otherwise.
801
- - **`TokenState.maxSupplyOverridden`** — whether this id has ever been explicitly overridden. The
802
- distinction the on-chain getter cannot express (`maxSupply(id)` returns `0` for both "never capped"
803
- and "deliberately closed") and the log now can, plus a signal that the monotonic never-increase rule
804
- is in force for that id.
805
- - **`editionCapOf(token)`** — new SDK export returning `{kind:'open'} | {kind:'capped', cap} |
806
- {kind:'closed'}`, so nothing re-derives the rule. Used by the dashboard and `abx tokens`; pass a
807
- head-read row and a `'0'` cap reads `open`, which is all a state read can honestly say.
808
- - **The dashboard stops lying twice.** It tested `maxSupply` for truthiness: a `--copies 10` id read
809
- "(open)", and — because `'0'` is a non-empty string — an id deliberately closed would have read
810
- "12 / 0". It now reads "12 / 10" and "12 (closed — no more can be minted)".
811
- - **Projection columns**: `projects.default_max_supply`, `tokens.max_supply_overridden` (migration-added,
812
- so an existing store keeps working; NULL stays NULL rather than becoming a fabricated `0`).
813
-
814
- Also in this pass, from the same review:
815
-
816
- - **`SPINE_EVENT_DOC` had drifted to 41 of 52 events**, so eleven folded events carried an empty
817
- human description and defaulted to Register 2 — including `TransferValidatorUpdated`, which belongs
818
- to Register 1, and the factory's `Deployed`. All eleven are described, and a new
819
- `spine-doc-coverage.test.ts` fails the build if the table and the spine ABI drift apart again in
820
- either direction.
821
- - **One provenance body in the resolver's `renderer` branch.** The `text/uri-list` arm hand-built an
822
- object byte-identical to `onChainProv(field, 'renderer', …)` — same keys, same order, the note string
823
- typed a second time — so the only thing that branch could do was drift from `sourceNote`.
824
- - **Dead `abx_params`-era test scaffolding deleted** (a `paramsOf(json) => json.abx_params` reader, an
825
- RPC stub, a schema factory), including an import of a `ParamMember` type that no longer exists —
826
- which nothing caught, because `tsconfig` includes only `src`, so test files are never typechecked.
827
-
828
- ## 0.1.0-alpha.14
829
-
830
- ### Minor Changes
831
-
832
- - 1b6b741: Rename "artist" to "creator" throughout, including the protocol vocabulary
833
-
834
- ABX is a tool for creators generally. Art remains a first-class use case, but it should not be baked
835
- into the product's framing — and it was, down to the on-chain identifiers.
836
-
837
- **Breaking, and deliberately a clean break** (pre-launch, folded into the one redeploy that four
838
- rounds of security remediation already required — deferring would have meant either living with the
839
- old vocabulary permanently or spending a second deployment on a word):
840
-
841
- - `AuthOption` is now `Creator`, `TokenOwner`, `Address`, `CreatorOrTokenOwner`, `CreatorOrAddress`,
842
- `TokenOwnerOrAddress`, `CreatorOrTokenOwnerOrAddress`. Order is unchanged, so the `uint8` values
843
- are identical — only the names moved. Users type these: `--schema key:Type:Creator`.
844
- - The on-chain collection metadata field keys `artist` / `artist_links` are now `creator` /
845
- `creator_links`. These are `bytes32` keys written on-chain and emitted verbatim into every
846
- collection's `contractURI` JSON.
847
- - CLI flags `--artist` / `--artist-links` are now `--creator` / `--creator-links`. No aliases.
848
- - `@artblocks/abx-token-api` renames its `art` module to `content`: `generateArt` →
849
- `generateContent`, `artContentHash` → `contentHash`.
850
- - `AbxMetadataRenderer.SPEC_VERSION` is **7**, and `isCurrentRenderer` gates on it. A v6 renderer
851
- emits a different member key, so it is genuinely behind, not cosmetically so.
852
-
853
- Package CHANGELOGs are left alone on purpose: they record what past alpha versions actually shipped,
854
- and rewriting them would make the release history lie about flag names that really were `--artist`.
855
-
856
- - 1b6b741: A purchase now carries the terms the buyer accepted — closing the one path by which a project owner
857
- could take more than the buyer agreed to.
858
-
859
- Both shared fixed-price minters take a mandatory **terms guard** — not a slippage allowance; see the note
860
- below — and revert a new `SaleTermsChanged()` when the live sale doesn't match:
861
-
862
- ```solidity
863
- purchase(address token, address expectedPaymentToken, uint256 maxPrice)
864
- purchaseTo(address token, address to, address expectedPaymentToken, uint256 maxPrice)
865
- // 1155 — the bound is the TOTAL, since qty multiplies it
866
- purchase(address token, uint256 id, uint256 qty, address expectedPaymentToken, uint256 maxTotalPrice)
867
- purchaseTo(address token, uint256 id, uint256 qty, address to, address expectedPaymentToken, uint256 maxTotalPrice)
868
- ```
869
-
870
- `configure` takes effect immediately and has no timelock. On the ETH lane that was largely contained
871
- (`msg.value` must equal `price`), but an ERC-20 sale settles `safeTransferFrom(buyer, payee, price)`
872
- against a standing allowance — so an owner who front-ran a pending purchase with a huge price spent the
873
- buyer's whole approval, and one who switched the sale to a different ERC-20 reached an allowance granted
874
- somewhere else. `address(0)` as `expectedPaymentToken` means ETH. There is deliberately no "no maximum"
875
- sentinel: a bound that can be defaulted away is the bug being fixed.
876
-
877
- SDK (breaking): `preparePurchase` / `preparePurchase1155` take the live terms — `sale`, exactly what
878
- `readSaleConfig` / `readSaleConfig1155` returns — instead of a hand-computed `value`. They derive the
879
- ETH to attach (`price`, or `price × quantity`) and the bound from those terms, so the payment and the
880
- guard cannot desync. Pass `maxPrice` / `maxTotalPrice` to state a wider ceiling (a UI that tolerates a
881
- small move rather than making the buyer re-sign); it widens the guard only, never the payment. New
882
- exported type `PurchaseTerms`.
883
-
884
- **`maxPrice` is not slippage tolerance on the ETH lane, and the docs no longer imply it is.** The minter
885
- requires `msg.value == price` (an equality) and V1 has no refund path, so an in-flight ETH purchase
886
- reverts if the price moves in _either_ direction, however wide the ceiling. Real tolerance exists only on
887
- the ERC-20 lane, where the live `price` is pulled from the buyer's allowance and anything at or below the
888
- ceiling settles.
889
-
890
- CLI: `abx minter buy` hands the terms it just read to the SDK (the price × quantity math now lives in
891
- one place), and the scaffolded `abx mint-page` binds each mint to the terms the page is displaying —
892
- if the owner re-prices mid-click the mint reverts, nothing is charged, and the page tells the buyer to
893
- review the new price. Re-pricing a live sale therefore fails the buys already in flight, by design:
894
- pause first if you want a clean cutover.
895
-
896
- **Deploy note:** this is a runtime change to two shared singletons, so both need a redeploy on every
897
- supported chain (CREATE2 + canonical salts, so the new addresses are identical cross-chain) and the new
898
- addresses recorded in `packages/sdk/src/deployments.ts` +
899
- `site/content/docs/reference/deployments.mdx`. Until then the CREATE2-prediction-vs-manifest tests fail
900
- on purpose — that is the drift detector doing its job, not a broken test.
901
-
902
- - 1b6b741: The configure hook can see a blob's size and contents — so creators can police it instead of the protocol
903
-
904
- `IAbxConfigureHook.onParamConfigured` gains two arguments:
905
-
906
- ```solidity
907
- function onParamConfigured(
908
- uint256 tokenId,
909
- bytes32 key,
910
- bytes32 value, // the literal on the scalar path; keccak256(data) on the blob path
911
- address updatedBy,
912
- uint256 dataLength, // 0 on the scalar path; data.length on the blob path (never 0 there)
913
- address dataBlobAddress // address(0) on the scalar path; the live SSTORE2 pointer otherwise
914
- ) external;
915
- ```
916
-
917
- **Why.** On a multi-copy edition params belong to the id, so a holder-writable `String`/`Bytes` key is
918
- shared: one holder could store large values and make the shared id's document expensive for every
919
- co-holder. An independent audit asked for a protocol byte ceiling. A ceiling would cap every project
920
- to police a configuration almost nobody wants — but the alternative we had documented, "the creator
921
- constrains it in their own hook", was not actually possible: the hook only ever saw `keccak256(data)`.
922
- It could not learn the length, could not read the content, and could not read the new value from
923
- storage either, because nothing had persisted yet. Now it can, and no protocol limit is imposed.
924
-
925
- **The bytes are not forwarded** — a blob can be nearly a full contract's worth of data, and putting it
926
- in calldata would make every configure call pay for it whether the hook looks or not. A hook enforcing
927
- a size ceiling reads `dataLength`; a hook that needs content calls `SSTORE2.read(dataBlobAddress)` and
928
- pays for exactly what it asked for. Guaranteed: `keccak256(SSTORE2.read(dataBlobAddress)) == value`.
929
-
930
- **Two things to know when writing one.** `tokenParamData(tokenId, key)` still returns the OLD value
931
- inside the hook (that is what "before the value persists" means — and it lets you veto a regression).
932
- And the blob is written _before_ the hook, because an address is meaningless until the contract behind
933
- it exists; so a veto happens after the writer paid to store the bytes. The accepted path costs the
934
- same as before, only rejection wastes, and it wastes only the rejecting writer's own gas.
935
-
936
- `ConfigurableParams` is extension **v3** — the beacon's `extensionVersion` is how an integrator tells
937
- which calling convention a deployed token uses. A hook written for the 4-argument form will revert
938
- against a v3 token, and vice versa.
939
-
940
- Addresses: `AbxParamsLib` moved, `AbxEditionLib` with it (it links it), and the four token types that
941
- compose `ConfigurableParams` moved with their factories — `SeriesCode`, `EditionCode`,
942
- `OneOfOneEdition`, `EditionImage`. The two ERC-721 image types and their factory, `AbxMetadataLib`,
943
- `AbxCodeLib`, the renderer, chunk store, seed source and both minters keep their addresses.
944
-
945
- - 1b6b741: Renderer spec v10 projects four long-reserved keys; edition trust anchors move
946
-
947
- **Renderer spec v10.** Four keys the spec reserved from the start were projected on chain by nothing:
948
- `background_color` and `youtube_url` (token), `banner_image` and `featured_image` (collection). A
949
- documented reserved key that no surface emits is a hole rather than a saved byte — a creator sets it,
950
- observes nothing, and cannot tell whether the tool or the marketplace is at fault. All four now project
951
- when their representation is chain-reachable, and are omitted when unset. `isCurrentRenderer` gates on
952
- 10; a v9 renderer silently drops them.
953
-
954
- **`collaborators` is no longer a reserved key.** It was in the registry, projected by neither plane, and
955
- because reserved keys are excluded from the resolver's `artifacts` listing, setting it on chain did
956
- nothing observable anywhere. It is now an ordinary creator key — which means it **does** list in
957
- `artifacts`. Put a collaborator list in a named artifact (`abx attach`) or in `description`.
958
-
959
- **The resolver's two collection image keys are now symmetric with the renderer.** `featured_image` is a
960
- top-level `contractURI` key instead of artifacts-only, and `banner_image` projects for every
961
- chain-reachable representation instead of only `url` (an `inline` or `reader` banner used to vanish).
962
-
963
- **Docs now carry the three-list split** — which keys both planes project, which are the off-chain
964
- resolver's alone by design (`abx_provenance.status`, operator overlay, effects/live-view, the
965
- `artifacts` listing, collection `image` courtesy, `image_data`, off-chain-decode values), and which are
966
- not reserved at all. That split is the thing to read before adding a key to either surface: drift on a
967
- key both planes _can_ emit is what produced three earlier remediations.
968
-
969
- **Addresses.** The three edition factories move (trust anchors) along with their implementations, and
970
- the renderer moves. `AbxEditionLib`, `AbxMetadataLib`, `AbxCodeLib`, `AbxParamsLib`, all three ERC-721
971
- factories, the chunk store, the seed source, both minters and both generators keep their addresses.
972
- Existing clones keep running their frozen implementations; re-point a live collection at the new
973
- renderer with `abx set-renderer` rather than redeploying it.
974
-
975
- - f64a31f: Expose `lockScript` above the contract layer — the lock that actually freezes a code project's work.
976
-
977
- `SeriesCode`/`EditionCode` have always had `lockScript()` (and `scriptLocked()`), but nothing above
978
- the ABI surfaced it: the SDK had no `prepareLockScript`, the CLI had no `lock-script` command, and the
979
- skill's immutability guidance told creators that `lock-field` + `lock-uri` made a project "provably
980
- immutable". For a generative/code drop that is false — those freeze only the _metadata_; the owner can
981
- keep rewriting the on-chain program (`setScriptChunk`/`removeLastScriptChunk`) until the script is
982
- locked. Agents building on ABX repeatedly missed that the program could be frozen at all.
983
-
984
- - **SDK:** new `prepareLockScript({contract, chainId})` (mirrors `prepareLockDependencies`).
985
- - **CLI:** new `abx lock-script <address>` command (any signing lane), plus `abx verify` now reports
986
- the script and dependency lock state for code projects (`script: locked/UNLOCKED`, with a nudge to
987
- `lock-script` when the program is still mutable) — read from the already-indexed state, no extra RPC.
988
- - **Skill:** the immutability guidance now names the full freeze for a code drop
989
- (`lock-script` + `lock-dependencies` + `lock-field`/`lock-uri`, plus `set-schema … lock=now` for a
990
- param value), corrects the "provably immutable" claim, and surfaces PostParams as a first-class
991
- `deploy-code` capability (not only a generative feature) in the `SKILL.md` description and decision
992
- tree so collector-settable on-chain parameters are discoverable.
993
-
994
- - 1b6b741: Independent-audit remediation: `abx_provenance` reshaped, `abx_params` removed, hook ABI widened
995
-
996
- An independent adversarial audit of the contracts found no Critical or High issue, no theft path and
997
- no cross-project reach — but four Mediums, eight Lows and five architectural notes. Everything is
998
- addressed. Consumer-visible changes:
999
-
1000
- **`abx_provenance` entries are now `{field, source, note}`.** They lost `onChain`, which was wrong in
1001
- both directions — a `url` reported `false` although the URL string is stored on chain, while an
1002
- `inline` value of `"https://…"` reported `true` for a pure pointer — and `verifiedAgainstChain`,
1003
- which was hardcoded `null` on every entry. `source` says where the bytes came from; whether a value
1004
- resolves on chain is visible in the value, and that judgment belongs to the reader. The off-chain
1005
- resolver drops `onChain` for the same reason (it was exactly `status === 'on-chain'`) and keeps
1006
- `status`, which it can genuinely compute.
1007
-
1008
- **`abx_params` no longer appears in `tokenURI`.** It was emitted by the on-chain renderer AND the
1009
- resolver and parsed back by nobody. Params enumerate directly from the contract — `tokenParamKeys`,
1010
- `tokenParam`, `paramSchemaKeys` — which is canonical, needs no indexer, and is what a chain-only
1011
- reader should use; a code project's script still receives them through `tokenData`. Traits for
1012
- marketplaces belong in `attributes`, unchanged.
1013
-
1014
- **A computed `image` locator now lands verbatim.** A field renderer returning
1015
- `("text/uri-list", "ipfs://…")` was being data-wrapped into `data:text/uri-list;base64,…`, which no
1016
- marketplace dereferences. `image` and `animation_url` now share one implementation of the rule.
1017
-
1018
- **`IAbxTransferHook.onTokenTransfer` gains `operator` and `amount`.** Without them a hook on a
1019
- shared-supply ERC-1155 cannot tell a real transfer from a zero-amount no-op — which let any address
1020
- fire the param lifecycle for an id it held no copy of. The token also refuses to notify on
1021
- zero-amount and self-transfers.
1022
-
1023
- **`pingURI` is owner-only**, and `paramSchemaHead` / `selectOption` are new: resolving one selected
1024
- option no longer copies the whole option table on every render.
1025
-
1026
- `AbxMetadataRenderer.SPEC_VERSION` is 8. Every canonical address moves — every token type is now
1027
- library-linked, including the two ERC-721 image factories, because the metadata field store was
1028
- externalized into the new `AbxMetadataLib` to restore EIP-170 headroom before any fix was stacked on
1029
- top of 187 bytes of margin.
1030
-
1031
- The CLI now warns when `--copies` meets a holder-writable `--schema`: on an edition, params belong to
1032
- the id, so every holder shares one value and the last writer wins.
1033
-
1034
- - 1b6b741: The custom seed source is now a real choice, not a documented one
1035
-
1036
- We tell creators — in the docs, in `AbxSeedSource`'s own NatSpec, and in `deploy-code --help` — that the
1037
- canonical mint seed is pseudorandom and that the answer for anything lottery-like is to point
1038
- `seedSource` at their own `IAbxSeedSource` over commit-reveal or a VRF oracle. On-chain that was always
1039
- true. In the toolkit it was not: there was no flag, no owner command, and the only route was an
1040
- `ABX_SEED_SOURCE` env var validated as nothing more than "has code at this address". A doc/code
1041
- contradiction of our own making, now closed at both ends.
1042
-
1043
- **Deploy:** `abx deploy-code --seed-source <0x…|canonical>` (and the `--copies` edition lane). Omitting
1044
- it is unchanged — the canonical `AbxSeedSource` from the manifest, bootstrapped on a chain that lacks
1045
- one — and `--no-seed` still means no mint-time seed at all. Passing both is refused rather than resolved
1046
- in someone's favour. `ABX_SEED_SOURCE` keeps working and is no longer the unchecked lane.
1047
-
1048
- **After deploy:** `abx set-seed-source <address> <0x…|canonical|none>` — owner-only, same positional
1049
- grammar as `abx set-transfer-validator`. It surfaces the on-chain `setSeedSource(address)` that nothing
1050
- reached before. **Future mints only**: a seed settles the instant it is assigned, so nothing already
1051
- minted changes. The command says so, names the count of tokens already carrying a seed from the old
1052
- source, and points out that a part-sold drop then spans two sources — public on the spine
1053
- (`SeedSourceSet`) but not something an early buyer is told, so pause and say so.
1054
-
1055
- **`abx state` now prints the seed source**, labelled: `none`, the canonical singleton, or `CUSTOM`. It is
1056
- the one setting an owner can re-point mid-sale that changes what a later buyer receives, and it was
1057
- readable nowhere.
1058
-
1059
- **Both writes probe the address first, and `code.length > 0` is not the check.** The SDK gained
1060
- `probeSeedSource` — one `eth_call` to `seed(uint256,address)`, which must return 32 bytes — plus
1061
- `readSeedSource`, `prepareSetSeedSource`, `assertSeedSourceUsable`, and a `SeedSourceUnusableError`
1062
- carrying a structured verdict. Four shapes are refused, each with its own message: **no code**;
1063
- **empty return** (the permissive-fallback shape — a Safe, an uninitialised proxy, an EIP-7702-delegated
1064
- EOA; pasting your own wallet is the common way in); **a return under 32 bytes**; and **a revert** (either
1065
- the wrong address entirely, or a real source not yet armed — `seed()` is called synchronously inside the
1066
- mint and its revert bubbles, so a source that cannot answer now cannot answer at mint either). The
1067
- interface is non-`view` on purpose, but an `eth_call` simulates a state-keeping source fine, so
1068
- commit-reveal and oracle-fed sources probe without sending anything. This matters because a
1069
- misconfigured seed source is **completely silent**: the write succeeds, `seedSource()` reads back what
1070
- you set, the event fires, `abx state` shows it — and then every mint of the collection reverts in the
1071
- token's `bytes32` decode. Same spirit as `CreatorToken._requireHasCode`.
1072
-
1073
- **Naming fix: a `seed` param schema is not a "re-roll", and no longer described as one.** The governed
1074
- path is `configureTokenParam(tokenId, "seed", value)` — **the caller supplies the value**. Nothing is
1075
- re-randomized and the seed source is never consulted again; with an unbounded `Uint256Range` the
1076
- authorized party may set any 32-byte value, repeatedly, until they like the output. "Re-roll" promises a
1077
- buyer a fresh random draw they could reasonably expect to be fair, which is not what they get. It is a
1078
- collector-chosen (or creator-chosen) seed, and the docs, the specs, and the skill now say that. The
1079
- substance that was correct is unchanged: the schema is a pre-sale commitment (declarable only before the
1080
- collection's first seed exists), `paramSchema("seed")` is the buyer's read, and the type must be a
1081
- literal/scalar one.
1082
-
1083
- **Dropped a stale warning** in the skill about a `seed` schema changing how the seed decodes off-chain
1084
- while the on-chain generator injects raw hex — `tokendata.ts` returns the raw hex for `seed`
1085
- unconditionally, ahead of the schema branch, so the wire format is invariant across both surfaces and
1086
- there is nothing left to warn about.
1087
-
1088
- **Also corrected: `maxPrice` is a terms assertion, not slippage tolerance.** On the ETH lane the minter
1089
- requires `msg.value == price` (an equality) and V1 has no refund path, so an in-flight ETH purchase
1090
- reverts if the price moves in _either_ direction, however wide the ceiling — widening `maxPrice` on an
1091
- ETH sale buys the buyer nothing. Real tolerance exists only on the ERC-20 lane, where the live price is
1092
- pulled from the buyer's allowance. `specs/protocol/minter-spine.md` and
1093
- `site/content/docs/protocol/minting.mdx` said "a price cut still settles" without that distinction.
1094
-
1095
- - 1b6b741: Renderer spec v9: a computed image is carried once, not twice
1096
-
1097
- A field-renderer-produced image was emitted as the reserved `image` key **and again** inside the
1098
- `artifacts` manifest, then the whole document was base64-encoded around both copies. On the
1099
- on-chain-SVG lane that roughly doubled the inner payload of `tokenURI` — the one read this protocol
1100
- most wants to stay within ordinary RPC limits.
1101
-
1102
- The data-plane spec always allowed an on-chain renderer to omit entries duplicating reserved keys it
1103
- already emits ("an EVM-efficiency reduction, never a semantic one"). The renderer now takes that for
1104
- every reserved key, so on a fully-on-chain project `artifacts` is absent rather than empty. Nothing is
1105
- lost, because the entry's only unique contribution was a `mimeType` and a `data:` URI states its own.
1106
- The off-chain resolver still emits the complete listing, duplicates included — that asymmetry is the
1107
- spec's, and it is now documented on both surfaces.
1108
-
1109
- Also removed: three orphaned param getters on the renderer (`paramKeysOf`, `paramSchemaOf`,
1110
- `paramDataOf`), stranded when v8 took params out of `tokenURI`, whose NatSpec still claimed `tokenURI`
1111
- called them.
1112
-
1113
- `AbxMetadataRenderer.SPEC_VERSION` is **9** and `isCurrentRenderer` gates on 9. Only the renderer
1114
- moves — nothing links it, so no factory or implementation address changes. Existing on-chain-URI
1115
- projects keep resolving through the v8 renderer until re-pointed with `abx set-renderer`.
1116
-
1117
- - 1b6b741: Upgrading from alpha.12/alpha.13 — what breaks, in one place
1118
-
1119
- This release lands the contract-audit branch. It is a **compatibility break with earlier alphas**, which
1120
- is fine at this stage (greenfield alpha, no backwards-compatibility promise) but is worth having in one
1121
- list rather than spread across a dozen changesets. Nothing here is a deprecation with a migration
1122
- window; the old shapes are gone.
1123
-
1124
- **Every canonical address moved**, and four manifest fields are new (`metadataLib`, `paramsLib`,
1125
- `codeLib`, `editionLib` — the delegatecalled write-path libraries). Resolve through `getDeployment` and
1126
- the upgrade is transparent; anything you have _stored_ keyed by a factory address is not. A project
1127
- indexed from a superseded anchor reads `isCanonical: false` against the new manifest — correct, since it
1128
- is what a creator sees if they don't redeploy, but it is a data question, not a version bump.
1129
-
1130
- **The metadata document changed: renderer spec v4 → v9.** If you assemble or assert on it:
1131
-
1132
- - `abx_provenance` entries lost `onChain` (it was exactly `status === 'on-chain'`, and wrong in both
1133
- directions) and `verifiedAgainstChain` (hardcoded `null` on every entry). `source` and `status` stay.
1134
- - The `abx_params` block is gone from **both** lanes. Params enumerate from the contract —
1135
- `tokenParamKeys` / `tokenParam` / `paramSchemaKeys` — which is canonical and needs no indexer; a code
1136
- project's script still receives them through `tokenData`.
1137
- - `artist` / `artist_links` are now `creator` / `creator_links` (reserved on-chain collection field
1138
- keys), and `AuthOption.Artist*` is `AuthOption.Creator*` (numeric values unchanged).
1139
- - A computed `image` locator (`text/uri-list`) lands **verbatim** instead of being wrapped into a
1140
- `data:` URI no marketplace dereferences.
1141
- - The on-chain renderer no longer duplicates a computed image into `artifacts`. The off-chain resolver
1142
- still emits the complete listing — that asymmetry is specified, not a bug.
1143
- - Four long-reserved keys now project on chain: `background_color`, `youtube_url` (token) and
1144
- `banner_image`, `featured_image` (collection). `featured_image` also becomes a top-level resolver key
1145
- instead of artifacts-only, and `banner_image` projects for every chain-reachable representation
1146
- rather than `url` alone.
1147
- - `collaborators` is **no longer reserved** — projected by neither plane and excluded from `artifacts`
1148
- by its own reserved-ness, so it did nothing. It is now an ordinary creator key that does list.
1149
-
1150
- **`tokenData` divergences fixed, so rendered output can change:** a contract-scope `seed` no longer
1151
- leaks into a token's `tokenData` (the on-chain generator never read one), `seed`'s wire format is always
1152
- raw 32-byte hex regardless of schema, and `</script` escaping is case-insensitive to match the on-chain
1153
- generator. Cached renders for affected code projects should be re-derived.
1154
-
1155
- **Removed:** `paramKeysOf`, `paramSchemaOf`, `paramDataOf` on `AbxMetadataRenderer` — orphaned when
1156
- params left `tokenURI`. Read the token directly instead.
1157
-
1158
- **Two hook interfaces widened** (`ConfigurableParams` is extension **v3**; a hook built for the old
1159
- signature reverts against a v3 token): `IAbxTransferHook.onTokenTransfer` gained `operator` + `amount`;
1160
- `IAbxConfigureHook.onParamConfigured` gained `dataLength` + `dataBlobAddress`.
1161
-
1162
- **Additive:** `ParamHooksFrozen` folds into `ParamHooks.locked`; `paramSchemaHead` + `selectOption`
1163
- replace pulling a whole option table to resolve one index; `readParamHooks`; `abxJs()` / `gunzipScript()`
1164
- on the generator for piecewise document assembly; and the three ERC-1155 ABIs now carry
1165
- `MetadataUpdate` / `BatchMetadataUpdate` / `DefaultMaxSupplySet`, which were emitted but not decodable.
1166
-
1167
- - 1b6b741: Transfer-validator authorization has one home, and the five affected trust anchors move
1168
-
1169
- `CreatorToken1155.setTransferValidator` performed no authorization at all, and the library it
1170
- delegates to performed none either. What protected the three edition token types was that each one
1171
- _overrode_ the setter to run the caller check first — one guard in three copies, sitting in front of
1172
- an unguarded mixin whose own comment claimed the tokens carried no override. Nothing deployed was
1173
- vulnerable, but a fourth edition type that simply inherited the mixin would have shipped an
1174
- unauthenticated `setTransferValidator`, which decides whether collectors' tokens can move at all.
1175
-
1176
- The guard now runs in the mixin, the overrides are gone, and the function is no longer `virtual` —
1177
- so the arrangement is enforced by the compiler rather than by a comment. A dead
1178
- `_requireMinterOrOwner` in `ExternalMinter`, whose NatSpec wrongly advertised it as the mint-time auth
1179
- check (the real one also enforces `paused()`), is deleted in the same pass.
1180
-
1181
- **Both changes are source-only** — the compiled runtime bodies are byte-identical before and after,
1182
- measured across all ten affected artifacts. Only the metadata hash differs, and since that is part of
1183
- the initcode, the CREATE2 addresses move. Five factories and their implementations are redeployed and
1184
- re-verified on Sepolia and Base Sepolia: `SeriesImageFactory`, `SeriesCodeFactory`,
1185
- `OneOfOneEditionFactory`, `EditionImageFactory`, `EditionCodeFactory`. Everything else — the 1/1 image
1186
- factory, all four libraries, the renderer, chunk store, seed source, both minters, both generators —
1187
- keeps its address.
1188
-
1189
- Projects deployed from the superseded factories keep working, but `abx` will report their anchor as
1190
- an older version. Redeploy from the anchors in the refreshed manifest.
1191
-
1192
- ### Patch Changes
1193
-
1194
- - 1b6b741: Externalize the code-custody READ surfaces into `AbxCodeLib` — B22 step 2, buying back the bytes the ERC-4906 audit fix spent
1195
-
1196
- The audit remediation that made `OnChainMetadata`'s field writes emit ERC-4906 (closing a real finding:
1197
- that surface mutated `tokenURI` silently, so a correct indexer served the pre-edit document forever) cost
1198
- ~96 B per token, and `SeriesCode` and `EditionCode` had no room for it — both fell under the 150 B EIP-170
1199
- floor `contracts/test/CodeSize.t.sol` guards. That is precisely the trigger condition `docs/10-backlog.md`
1200
- B22 was written for, and step 2 was the prescribed answer.
1201
-
1202
- The `IAbxOnChainScript` and `IAbxDependencies` view bodies now live in `AbxCodeLib`, next to the write
1203
- paths that were already there — `scriptChunkCount`/`scriptChunk`/`scriptLocked` and
1204
- `dependencyCount`/`dependencyByIndex`/`dependencyRegistry`/`dependenciesLocked`. The mixins keep the
1205
- functions (same signatures, same ABI, same ERC-165 ids) as **raw-calldata passthroughs** through a new
1206
- shared `AbxCodeDelegate` base, which forwards the call's exact calldata to the library and returns the
1207
- return data untouched. Storage still resolves in the token's own ERC-7201 namespaces and events still log
1208
- from the token — that is what `delegatecall` buys.
1209
-
1210
- Raw, not typed, on purpose: B22 step 1 measured a _typed_ shell (`return AbxCodeLib.f(...)`) **growing**
1211
- the token by ~855 B, because decoding the library's return value and re-encoding it at the call site costs
1212
- more than the extracted body saves. The same mistake was made again during this remediation, on
1213
- `setTokenField`/`setContractField`, and reverted. The price of the raw form is that `AbxCodeLib`'s read
1214
- signatures are now part of every composing token's external ABI — same signature ⇒ same selector is the
1215
- whole mechanism — so they must track the interfaces verbatim; both the library and the mixins now say so.
1216
-
1217
- EIP-170 margins, before → after:
1218
-
1219
- | contract | before | after | floor |
1220
- | ------------- | ------ | ------- | ----- |
1221
- | `SeriesCode` | 125 | **529** | 150 |
1222
- | `EditionCode` | 132 | **507** | 150 |
1223
-
1224
- `AbxCodeLib` grew 3,905 → 4,961 B and still has ~19.6 KB to spare. Nothing else moved: `OnChainScript` and
1225
- `Dependencies` are composed **only** by `SeriesCode` and `EditionCode`, and both already linked
1226
- `AbxCodeLib`, so no contract gained a library dependency and the other four tokens are byte-identical.
1227
-
1228
- No behavior change and no ABI change — the read signatures, return types, mutability, ERC-165 ids and the
1229
- `ScriptIndexOutOfRange`/`DependencyIndexOutOfRange` error selectors are all preserved (the errors stay
1230
- declared on the mixins so they remain in each token's ABI, and now revert from the library's
1231
- identically-named, identically-selectored twin). This does move the implementation bytecode, so it rides
1232
- the redeploy this remediation already requires: `AbxCodeLib`, both code tokens, and their factories (and
1233
- therefore the factory trust anchors) all get new addresses.
1234
-
1235
- - 1b6b741: CREATE2 for library deployments, as the standard process — and delete the false belief that said it was impossible
1236
-
1237
- An audit found that two ERC-1155 token types were held library-free, and an EIP-170 size floor relaxed, on
1238
- the recorded belief that linking a delegatecalled library costs a factory its CREATE2-deterministic address.
1239
- That belief is false, and the committed broadcast artifacts always refuted it: every recorded library deploy
1240
- went through the keyless CREATE2 proxy, `AbxParamsLib` landed at ONE address on Sepolia and Base Sepolia
1241
- from deployer nonces **215 apart** (a nonce is not an input to a CREATE2 address), and both library-linked
1242
- factories have held chain-identical addresses across four deploy generations.
1243
-
1244
- What was true was narrower and self-inflicted: `deploy.ts` deployed its libraries with `send({to: null})` —
1245
- a plain EOA `CREATE` — **by its own choice**, so _it_ could not predict them. `create2.ts` and both code
1246
- factory deploy functions then wrote that local limitation up as a property of libraries in general, and the
1247
- repo proceeded to contradict itself (`deployments.ts` and `reference/deployments.mdx` describe the correct
1248
- mechanism a few files away).
1249
-
1250
- The SDK now does what the docs already claimed:
1251
-
1252
- - Both bootstraps (`deploySeriesCodeFactory`, `deployEditionCodeFactory`) deploy every write-path library
1253
- through the keyless proxy at the canonical `AbxSalts` salts — `abx.lib.params.v1`, `abx.lib.code.v1`,
1254
- `abx.lib.edition.v1`, mirrored in `ABX_SALT` and pinned against `AbxSalts.sol` by a new test — and then
1255
- CREATE2 the linked factory at its own salt. **An SDK-bootstrapped chain and a forge-bootstrapped chain now
1256
- produce identical addresses** (verified: the SDK's predictions equal what
1257
- `DeployLibraries.s.sol --sig 'predict()'` prints).
1258
- - Idempotent, out of necessity rather than politeness: the proxy _reverts_ on an occupied address, so a
1259
- library already on-chain is linked against instead of redeployed — which is also what makes a partially
1260
- bootstrapped chain recoverable by re-running.
1261
- - New: `predictParamsLib`, `predictCodeLib`, `predictEditionLib`, `predictSeriesCodeFactory`,
1262
- `predictEditionCodeFactory`, plus exported `libPlaceholder` / `linkLibraries` / `LIB_FQN`. Both code
1263
- factories therefore gained the CREATE2 self-heal every other canonical anchor has — and need no version
1264
- probe for it, because code at a CREATE2 address is bytecode-bound to the build that predicted it.
1265
-
1266
- **Fixes a real crash on the way past.** `deployEditionCodeFactory` could not succeed at all: it deployed
1267
- `abxEditionLibBytecode` raw, but `AbxEditionLib` itself delegatecalls `AbxParamsLib`, so its shipped
1268
- bytecode carries an unlinked `__$…$__` placeholder — not hex, so the RPC rejected it with an opaque
1269
- `Invalid byte sequence`. The old guard only checked the _factory's_ bytecode, never the library's. The
1270
- library is now linked before it is deployed, and `linkLibraries` throws with the offending placeholder named.
1271
-
1272
- **The trap that IS real, now written down** (`create2.ts`, `reference/deployments.mdx`): a library's address
1273
- is a function of its creation bytecode, so build settings move it. Linking _after_ compilation — substituting
1274
- into solc's placeholder, what forge's automatic linking and the SDK both do — cannot change that bytecode.
1275
- Handing solc the addresses at build time (`--libraries src/…:Lib:0x…`) writes the map into
1276
- `settings.libraries` in every artifact's metadata, and the metadata hash is appended to the creation
1277
- bytecode: measured here, that moves the library's own address and the linked factory's initcode. Optimizer
1278
- profile does the same (the code tokens compile at 200 runs). Both failures are invisible until two chains
1279
- disagree, so the docs now say to compare `predict()` against the SDK's predictions rather than assume.
1280
-
1281
- Docs and specs swept for the old claim: `reference/deployments.mdx` (the salts, and a "libraries go first"
1282
- bootstrap step naming `DeployLibraries.s.sol` and its `predict()` dry run), `reference/contracts.mdx`
1283
- (`AbxEditionLib` was missing from the library taxonomy entirely), `reference/sdk.mdx`,
1284
- `specs/self-host-toolkit/deployment.md`, and the `abx-self-host` skill's code-projects reference. No
1285
- addresses were invented: nothing is redeployed yet, and the two manifest-drift tests still stand as the
1286
- detectors waiting on it.
1287
-
1288
- - 1b6b741: Stop promising immutability the locks don't deliver — and name the residual owner powers in one place
1289
-
1290
- **The claim that was wrong.** With the field lock, URI lock, contract-URI lock, script lock and
1291
- dependency lock _all_ engaged, the rendered output can still change, two ways. **Params have no lock at
1292
- all** — they aren't fields, so no field lock reaches them, and the renderer projects the whole enumerated
1293
- set into `tokenURI` as `abx_params` (a program reads the same set as `tokenData`). And a
1294
- **`Resolution.Registry` dependency is re-fetched from the registry contract on every read** —
1295
- `lockDependencies` freezes the ref and the registry pointer, not another contract's storage. `abx verify`
1296
- reported `chain-complete` on both sides of a registry swapping its library bytes, and the CLI was telling
1297
- people "a fully immutable code drop is lock-script + lock-dependencies + lock-field/lock-uri".
1298
-
1299
- Every unqualified verdict is now qualified rather than deleted, because locking a pointer _is_ part of the
1300
- story: `lock-uri`, `lock-script` and `lock-dependencies` narration, their `--help` entries, `abx verify`'s
1301
- chain-complete pass (which now says the flag describes **where** bytes come from, not that they're frozen),
1302
- `GeneratorStatus.chainComplete`'s doc comment, the `lockScript` SDK docs, `protocol/metadata`,
1303
- `protocol/params`, `protocol/code-projects`, `reference/cli`, the operate guide, `specs/protocol/*`, and the
1304
- skill's SKILL.md · `reference/decisions.md` · `reference/code-projects.md` · `reference/operating.md`. The
1305
- line the toolkit now gives a creator is **"your metadata is locked"**, with the stronger claim reserved for
1306
- a project whose graph is on-chain `0x…` deps plus locked fields. Stated once per place the decision is made,
1307
- not restated everywhere.
1308
-
1309
- Framed as a capability as well as a caveat: a token that _deliberately_ live-adapts to on-chain conditions
1310
- is a legitimate and interesting thing to build, and it's the same mechanism that makes it possible.
1311
-
1312
- **New page: [What a project owner can do](https://abx.docs.artblocks.io/protocol/owner-powers/).** There was
1313
- nowhere a collector could read what powers the creator retains. It states both halves plainly, with the read
1314
- for each. What an owner **can** do: re-point or suspend an enrolled 721C/1155C transfer validator (and block
1315
- transfers entirely with one that reverts — though if they renounce ownership, anyone may suspend it and
1316
- nobody can re-arm it); change price, payee, allocation and pause on a live sale with no timelock; keep
1317
- receiving primary-sale proceeds after selling the project, because `primaryPayee` doesn't move on
1318
- `transferOwnership`; mint reserves outside the minter's allocation, since assigning a minter grants the whole
1319
- remaining cap rather than a slice; change royalty receiver and bps forever; re-point metadata subject to the
1320
- locks above; swap the seed source; reassign a token's seed _iff_ a `seed` schema was declared before the
1321
- collection's first seed existed; and walk away. What they provably **cannot** do: take a buyer's funds beyond
1322
- the terms that buyer signed for (`purchase`/`purchaseTo` require `expectedPaymentToken` and a maximum, and
1323
- revert `SaleTermsChanged`); exceed a supply or per-id edition cap; rewrite or clear a settled seed; reach
1324
- another project through any shared singleton (the renderer and chunk store hold no storage, the generator's
1325
- wiring is `immutable` with no setters, the seed source is a stateless view namespaced by caller, and the
1326
- minters key everything per token and hold no funds); or take a token out of a collector's wallet — there is no
1327
- owner-only transfer, burn, or clawback anywhere in the protocol.
1328
-
1329
- Framing, deliberately: ABX is self-serve, the creator is a privileged role, the protocol's job is to keep
1330
- that role away from a collector's funds and tokens, and the residual powers are disclosed so a collector can
1331
- price them. Not an accusation — a creator who destroys their own collection has destroyed what they were paid
1332
- for. The point is that "trust me" should be optional.
1333
-
1334
- - 1b6b741: Gate on-chain content by what a node will READ, not by what it costs to write
1335
-
1336
- The documented on-chain envelope — ≲24 KB/file, ≲256 KB/project — was a _storage-cost_ figure sold as a
1337
- support envelope, and the CLI's only loud warning fired on the project total. Measurement says the
1338
- binding constraint is somewhere else entirely: **`tokenURI` reassembles the whole document on every
1339
- call**, and that cost is **superlinear** — EVM memory expansion is quadratic, so the per-KB rate climbs
1340
- with size. Across the 10–100 KB range that decides most projects it measures **~360,000–405,000 gas per
1341
- KB**, rising to ~460,000/KB at 187 KB and ~510,000/KB at 256 KB. `inline` and `reader` land within ~1% of
1342
- each other up to 75 KB (the cost is the renderer's string building, not the storage mechanism) and
1343
- diverge above it (~5% at 187 KB, ~10% at 256 KB, as the chunk store's quadratic read loop takes over), so
1344
- `--compress fastlz` makes a field cheaper to write and not one gas cheaper to read.
1345
-
1346
- Measured with `forge` (`OneOfOneImage` + `AbxMetadataRenderer`, callee execution gas for one `tokenURI`
1347
- call, `reader` staging at 22,000-byte chunks):
1348
-
1349
- | on-chain content | `tokenURI` gas | per KB | | on-chain content | `tokenURI` gas | per KB |
1350
- | ---------------- | -------------- | ------- | --- | ---------------- | -------------- | ------- |
1351
- | 3 KB | 1,123,327 | 374,000 | | 100 KB | 40,254,159 | 403,000 |
1352
- | 10 KB | 3,588,993 | 359,000 | | 128 KB | 53,559,735 | 418,000 |
1353
- | 23 KB | 8,346,220 | 363,000 | | 187 KB | 86,021,071 | 460,000 |
1354
- | 40 KB | 14,740,366 | 369,000 | | 256 KB | 131,269,134 | 513,000 |
1355
- | 50 KB | 18,759,333 | 375,000 | | | | |
1356
- | 75 KB | 29,137,215 | 388,000 | | | | |
1357
- | 90 KB | 35,868,124 | 399,000 | | | | |
1358
-
1359
- geth's `--rpc.gascap` defaults to 50M, hosted providers commonly cap lower, and L1 block limits sit in
1360
- the mid-30-millions — so a project at the _documented_ ceiling could not be read by a normal node, and
1361
- from ~90 KB (~36M gas) up no contract can read it inside a transaction at all. Note what the 100 KB
1362
- refusal is: a **margin**, not the wall. 100 KB reads at ~40M, and geth's own 50M default is not
1363
- exhausted until ~120 KB; the refusal sits early because hosted providers cap well below geth.
1364
-
1365
- The gate now sits on that axis, per token (each `tokenURI` assembles only its own content, so a
1366
- 300-piece collection of 5 KB works is fine while one 256 KB work is not):
1367
-
1368
- - **from ~40 KB — warn.** Reads need a deliberately high-gas RPC.
1369
- - **past ~100 KB — refuse**, with `--allow-unreadable-onchain` as the override. The refusal names the
1370
- three ways forward and says plainly what the override accepts: rendering needs a high-gas endpoint and
1371
- most marketplaces and indexers will show nothing. It fires on the **dry run** too, so a creator meets it
1372
- before spending gas rather than after the first staging transaction.
1373
-
1374
- Live on every path that puts bytes on-chain: `deploy --onchain-image`, `deploy-series --onchain-image`,
1375
- both `--copies` edition twins, and `set-field --file`. New in the SDK: `classifyOnchainReadSize`,
1376
- `tokenUriGasEstimate` (fitted to the measurements, so it tracks the superlinearity instead of
1377
- multiplying by a flat rate), `TOKEN_URI_GAS_PER_KB_LOW` / `TOKEN_URI_GAS_PER_KB_HIGH` (the measured
1378
- 10–100 KB envelope — there is deliberately no single flat `TOKEN_URI_GAS_PER_KB`, because there is no
1379
- single rate), `ONCHAIN_READ_WARN_BYTES`, `ONCHAIN_READ_REFUSE_BYTES`, `ONCHAIN_READ_GETH_CAP_BYTES`
1380
- (where geth's 50M default actually runs out, so the refusal can be described as the margin it is)
1381
- (`exceedsOnchainSoftLimit` stays, now documented as the _write_-cost predicate it always was). The
1382
- whole-collection 256 KB warning stays as well, relabelled as write cost only, since it never said
1383
- anything about readability.
1384
-
1385
- Two things deliberately not done. **No on-chain guard and no gas check** — a read happens off-chain, a
1386
- read too large for one node is an RPC-capability problem, and bricking a contract is worse than needing a
1387
- capable endpoint. And the **code lane still only warns**, at the same 40 KB threshold and now quoting the
1388
- gas figure: `abx inspect`'s document size is an estimate that undercounts unknown dependencies, and a
1389
- registry-hosted library like p5 is legitimately ~200 KB, so the honest move there is to name the cost and
1390
- point at the generator's piecewise getters (`document`, `tokenDataJson`, `dependencyTag`, `abxJs`,
1391
- `gunzipScript`, `registryScriptChunk`) rather than block a shipped lane on a heuristic. Those getters are
1392
- now in the SDK's generator ABI, which claimed to carry "the piecewise reads" while omitting three of them.
1393
-
1394
- The docs and the `abx-self-host` skill were swept in the same pass: `protocol/onchain-storage` carries the
1395
- measured table and the corrected envelope, `protocol/code-projects` gains a piecewise-read section, and
1396
- the skill's Quick start and `reference/decisions.md` lead with "40 KB is the number, not 256 KB" plus the
1397
- rule that the override is a creator's deliberate choice and never a way to clear a warning.
1398
-
1399
- - 1b6b741: Round-3 audit remediation: link the two ERC-1155 image anchors, and stop guarding by name
1400
-
1401
- `EditionImage` and `OneOfOneEdition` became library-linked in the previous round, but neither the
1402
- SDK nor the forge scripts were updated. `predictEditionFactory()` and
1403
- `predictOneOfOneEditionFactory()` hashed **unlinked** bytecode — and viem does not reject a
1404
- `__$…$__` placeholder, it UTF-8-encodes it, so both returned a confident hash of ASCII garbage for
1405
- a trust anchor, and `ops.ts` would have broadcast that bytecode as initcode. Both now link
1406
- `AbxEditionLib` (itself linked against `AbxParamsLib`) and ensure the libraries exist first.
1407
-
1408
- Two addresses move as a result: `EditionImageFactory` and `OneOfOneEditionFactory`. Both are
1409
- pre-launch and pending the redeploy gate.
1410
-
1411
- This was the third instance of one bug class, and the existing guard missed it because it checked
1412
- two factories **by name**. The replacement is exhaustive by construction: it reverse-looks-up which
1413
- initcode each `predict*` actually hashed, drives every `prepareDeploy*` and every `deploy*`
1414
- orchestrator against a stub chain, and asserts no `__$` placeholder survives anywhere — so the next
1415
- library-linked contract is covered without anyone remembering to add it.
1416
-
1417
- Also: every SDK deploy now goes through CREATE2 at a canonical salt (the 1/1 and Series anchors were
1418
- still plain `CREATE`, landing at nonce-dependent addresses that could never match the manifest), and
1419
- the CLI no longer tells creators an ERC-1155 edition emits no ERC-4906 — both lanes emit it now.
1420
-
1421
- - 1b6b741: The param transfer hook is a veto, not best-effort — say so everywhere, and ship the lock that answers it
1422
-
1423
- The transfer hook used to swallow its own revert, and every surface promised that the parameter
1424
- lifecycle "must never block a transfer". That promise could not be kept, so it is withdrawn rather than
1425
- restated: Solady runs the ERC-721/1155 **receiver acceptance check after the hook**, so on the `safe*`
1426
- variants — which marketplace fills wrap — a hook that is cheap when a wallet estimates gas and expensive
1427
- when the transfer lands starves the work that comes after it, with or without a gas cap. Swallowing
1428
- bought a guarantee that was false in exactly the cases people use, while making an honest hook's failure
1429
- invisible.
1430
-
1431
- So the contract now calls the hook plainly (no swallow, no gas budget, no assembly), **its revert fails
1432
- the transfer**, and — because a mint and a burn are transfers from/to `0x0` — a reverting hook also stops
1433
- minting for that project, including through the shared minter. That is deliberate: a hook is always the
1434
- creator's own contract, which is why it may block issuance while the transfer _validator_, usually a
1435
- third party's, never sees mint or burn.
1436
-
1437
- The mitigation is a lock, and it is now reachable end to end:
1438
-
1439
- - **SDK** — `prepareLockParamHooks({contract, chainId})`, the sibling of `prepareLockScript` /
1440
- `prepareLockDependencies`. Plus two reads: `readParamHooks` (the trio) and `readParamHooksLocked`,
1441
- which establishes the freeze without a getter by simulating `setParamHooks` with the current trio and
1442
- watching for `ParamHooksLocked` — and returns `undefined`, never a cheerful `false`, when a node won't
1443
- answer.
1444
- - **CLI** — `abx lock-param-hooks <address>`. Owner-only, one-way, every signing lane, `--dry-run`
1445
- guarded. It prints the exact three addresses it will freeze, states what is given up (arming a
1446
- transfer veto, arming a configure veto, re-pointing or clearing the augment hook) and that there is no
1447
- way back, and distinguishes the two cases: freezing an **empty** set is how a project _proves_ it can
1448
- never arm a transfer veto, while freezing a set that already contains a hook pins which contracts can
1449
- run — it does not disarm them.
1450
- - **`abx state`** now prints the three hooks and whether they are frozen, next to the transfer validator,
1451
- because that pair is the read a buyer performs. `abx verify` warns on an armed-but-unfrozen transfer
1452
- hook. `abx set-param-hooks` states the veto at the moment a transfer hook is armed.
1453
- - **Reconstruction** — `ParamHooksFrozen` folds into `ProjectState.paramHooks.locked`. The event is
1454
- declared only by `AbxParamsLib`, so `spineEventAbi` now unions that ABI (its other events dedup away):
1455
- without it the one-way lock on a hook that can veto a transfer was invisible to every consumer that
1456
- folds the spine.
1457
-
1458
- Swept for the withdrawn promise: `protocol/params` (which gained a section on why it died),
1459
- `protocol/owner-powers` (the hook is now on **both** lists — an unlocked hook set as a live power, a
1460
- frozen one as a guarantee), `specs/protocol/event-spine`, `specs/protocol/user-stories`, the spine event
1461
- doc strings, and the `abx-self-host` skill (`SKILL.md`'s lock bullet plus the operating and code-project
1462
- references). No surface still says "best-effort", "reverts are swallowed", or "never blocks a transfer",
1463
- and a CLI test now fails if one comes back.
1464
-
1465
- - 1b6b741: Transfer validator: preflight what the chain actually refuses, and stop calling `setTransferValidator` owner-only
1466
-
1467
- Two corrections, both in the direction that was costing someone something.
1468
-
1469
- **The preflight was a bare `getCode`, and the chain's check is not.** `validateTransfer` returns nothing,
1470
- so there is no ABI decode to fail — which means any address whose fallback succeeds for an unknown
1471
- selector passes a has-code check and then waves **every** transfer through, while ERC-165,
1472
- `getTransferValidator()` and the extension beacon all report enforcement as ON. A Safe does exactly this
1473
- (`FallbackManager.fallback()` returns empty when no handler is set), as do an uninitialised proxy and a
1474
- 7702-delegated EOA, and a creator pasting their own Safe is the likely real case. The contract's guard was
1475
- rewritten to probe with a selector no validator implements and require it to **fail**; the toolkit now
1476
- asks the same question, with the same selector, through the new SDK `probeTransferValidator` (verdicts:
1477
- `ok` · `no-code` · `permissive-fallback` · `unreachable`). Both call sites use it — `abx
1478
- set-transfer-validator` and `deploy --721c` — so a creator gets a refusal that names what is actually
1479
- wrong instead of passing the CLI and reverting on chain against an error whose documented meaning ("no
1480
- code on this chain") was false for their address.
1481
-
1482
- **`setTransferValidator` is owner-only only while there is an owner.** Once `owner() == address(0)`
1483
- **anyone** may suspend enforcement by passing `address(0)`, and nobody may ever arm a validator again —
1484
- the dead-man release for a validator that reverts every transfer on a collection with nobody left to
1485
- re-point it, which would otherwise strand every collector's token permanently. Four surfaces still said
1486
- owner-only, and the SDK's was wrong in the direction that matters: an integrator reading it would refuse
1487
- to build the suspension a stranded holder needs. Corrected in `creator-token.ts` (module header plus
1488
- `prepareSetTransferValidator`), `protocol/royalty-enforcement`, `protocol/interfaces`, and
1489
- `specs/protocol/interfaces`, each stating the asymmetry: an ownerless collection can only ever be moved
1490
- _toward_ transferability, so this hands a stranger no power over a live project.
1491
-
1492
- ## 0.1.0-alpha.13
1493
-
1494
- ### Patch Changes
1495
-
1496
- - 528c6c6: ERC-1155 editions: the trust-anchor and on-chain-URI checks told the truth about 721s only, and four
1497
- membrane gaps around them (2026-08-05 wave-4 agent sweep — 12 cold agents, one funded end-to-end run
1498
- on Base Sepolia).
1499
-
1500
- The 1155 contracts themselves were fine: a funded run took an edition from deploy through
1501
- `tokenuri` → mint more copies → transfer one copy → lower the per-id cap, verifying every step
1502
- against the chain. What shipped wrong was everything that _describes_ an edition.
1503
-
1504
- - **`abx verify` reported EVERY canonical edition as `canonical: NO`.** `detectCanonicalFactory`
1505
- probed only the three 721 anchors and, on no match, fell back to the 721 1/1 factory — whose
1506
- `isAbxClone` answers **false**, not "unknown". So the one signal platforms allowlist against was
1507
- confidently inverted for the entire edition line (the function's own docstring promised
1508
- "canonicity just shows unverified, never wrong"). All six anchors are now probed. **Editions
1509
- registered before this fix keep the wrong factory in the local projection** — `abx forget <addr>`
1510
- then `abx add <addr>` re-detects it.
1511
- - **`abx verify`'s on-chain-URI probe called the ERC-721 `tokenURI` selector on editions**, which
1512
- exposes `uri(id)`. It reverted for every edition, so verify could never confirm that a
1513
- fully-on-chain edition resolves — and it printed the raw multi-line viem dump (Contract Call /
1514
- args / Docs / Version) into a creator-facing readout. The probe now switches on `contractType`
1515
- (matching `reconstruct.ts`, which already did), reports which accessor it read so an edition is
1516
- never described in 721 terms, and readout errors are trimmed to their first line.
1517
- - **An address with no contract was reported as a specific type: `OneOfOneImage`.**
1518
- `detectTokenKind`'s three probes are each `try/catch → false`, so nothing-deployed and
1519
- no-extensions-composed were indistinguishable and the ladder fell through to its `1of1` default.
1520
- `abx set-max-supply` and `abx minter buy` then told edition owners _"…is a OneOfOneImage (721) —
1521
- drop your edition flags"_ for a mistyped address or, far more often, the wrong `ABX_CHAIN` (the
1522
- default is base-sepolia, so any Sepolia contract hit this immediately) — advice that removes the
1523
- _correct_ flags. The existence check now lives inside `detectTokenKind`, so all 13 call sites
1524
- inherit it; it fails **open** on an unreadable `getCode`, so an RPC blip never becomes a
1525
- "no contract" claim.
1526
- - **`abx transfer --token-id <n>` silently transferred id 0.** `transfer` spells the id `--token`
1527
- while every sibling command (`mint`, `set-max-supply`, `minter …`) spells it `--token-id`, and an
1528
- unrecognized flag was simply ignored — so an agent that learned the name from `mint` moved the
1529
- **wrong artwork** with no warning. `--token-id` is now an accepted alias; disagreeing values are
1530
- refused rather than silently preferring one.
1531
- - **The edition deploy previews dropped lines their 721 twins print.** All three edition lanes
1532
- omitted `approvals N wallet approval(s)` from the dry-run readout — which the skill promises "every
1533
- preview" prints and tells the agent to state up front, so on an edition the agent had nothing to
1534
- tell the creator about how many wallet prompts were coming (`deploy-code --copies` had it only in
1535
- the `--confirm` sentence, which is off by default). `deploy --copies` and `deploy-series --copies`
1536
- also omitted the "resolves ON-CHAIN via the renderer — no resolver, no server" line, which is
1537
- precisely the guarantee an edition creator is asking about, and `deploy-series --copies` never
1538
- showed `paused`. All six lanes now print the approval count; the 1155 paths say `uri()`.
1539
- - **Owner-op writes now surface a stray flag instead of swallowing it.** `mint`, `transfer`,
1540
- `set-max-supply`, and `minter configure|show|buy` warn on an unrecognized flag (warn, not refuse —
1541
- per `unknownFlags`' contract, a false warning must never break a script). These are the commands
1542
- where the 1155 semantics live in _optional_ flags that default rather than fail: a typo'd
1543
- `--amount 50` minted 1 copy and a typo'd `--quantity 5` bought 1 and paid 1×, both in total
1544
- silence. The broader gap — ~35 commands with no unknown-flag notice at all — is filed as B30.
1545
-
1546
- A second verification sweep (six more cold agents, one funded) confirmed each fix above from a fresh
1547
- start and turned up four more, all fixed here:
1548
-
1549
- - **A supply cap could be raised in `--dry-run` and only revert on send.** Both twins —
1550
- `set-max-invocations` (721) and `set-max-supply` (1155) — printed the new cap as fact for a value
1551
- the chain forbids, then reverted for real. A clean dry run is read as permission to send. Both now
1552
- read the current cap first and refuse up front; the 1155 side also refuses a cap **below** live
1553
- supply (the second way it reverted). Both guards fail **open** on an unreadable getter, since the
1554
- chain enforces the invariant anyway. (The 721 guard needed the Series ABI — reading `maxInvocations`
1555
- through the 1/1 ABI that `read()` uses throws, which would have made the guard silently never fire.)
1556
- - **`abx refresh` told edition owners ERC-4906 had already pinged marketplaces.** An ERC-1155 edition
1557
- emits no ERC-4906 at all — that is precisely why `ping-uri` exists — so the one sentence a creator
1558
- reads after editing metadata said "already handled" when nothing had been, and `ping-uri --help`
1559
- said the opposite. Two commands contradicting each other on the new lane. `refresh` is now
1560
- kind-aware and names `ping-uri` for editions.
1561
- - **`abx verify` ended in two ⚠ on a perfectly healthy fully-on-chain image drop** — "generator
1562
- reports NO code" and "no animation_url" — because the code-project lane runs whenever a tokenURI
1563
- renderer is set, which is true for every on-chain project, image or code. A static image has no
1564
- program and no `animation_url` by design. Three separate agents named this as the worst thing about
1565
- `verify`, and one said plainly it would make a creator distrust future real warnings. The code-lane
1566
- checks are now scoped to the code twins; a static drop gets one informational line instead. (This
1567
- was pre-existing on the 721 path too, not an editions regression.)
1568
- - **`EditionImage`'s SVG-only refusal was a thinner copy of its 721 twin's** — the Series version names
1569
- three routes (`--onchain-image`, `--backend ipfs|arweave`, or host off-chain); the edition version
1570
- said only "drop `--onchain-uri`". A cold agent asked for "3 artworks × 25 copies each" read that,
1571
- concluded there was no no-server option for one contract, and **fanned the collection out into three
1572
- separate single-artwork contracts**. The refusal now states the real options for the lane and says
1573
- explicitly that the off-chain-image-URL-in-on-chain-JSON route exists only per-artwork via
1574
- `abx deploy --copies` — one contract per artwork, not one collection. The underlying capability gap
1575
- is recorded in B28.
1576
-
1577
- One knock-on from the first fix: the `false` branch of the canonical readout said _"not a clone of the
1578
- **configured** factory"_ (singular), which now understates the check and misdirects the reader — six
1579
- anchors are probed, so a `false` means no trust anchor the CLI knows deployed this contract, and the
1580
- usual real cause is a **superseded** factory or a hand-deployed contract, not a misconfiguration. It
1581
- now says so, and points at `abx doctor` for the current anchors.
1582
-
1583
- Also: `deploy --help` no longer calls the edition sale stack (`--minter`/`--primary-payee`/
1584
- `--unpaused`) "required" — it is optional, and an agent handed a creator hardcoded addresses as
1585
- mandatory because of it; `set-field --help` now lists all eleven `--representation` values (it showed
1586
- seven, omitting `url-template`, `renderer`, `sha256`, `inline-gzip`); and `abx doctor`'s label column
1587
- widened so `edition factory` no longer runs into its address.
1588
-
1589
- The skill-drift ✗ now names **which** copy is stale and prescribes the command that actually clears
1590
- it — `abx skill install` writes the project-local copy only, so a stale **global** copy produced a ✗
1591
- whose own remedy could not fix it. Six agents hit it in one sweep; several re-ran the install
1592
- repeatedly and one left its sandbox to read `~/.claude/skills` to work out what the tool meant.
1593
-
1594
- ## 0.1.0-alpha.12
1595
-
1596
- ### Minor Changes
1597
-
1598
- - afa9dd4: ERC-1155 editions ship with full parity: a Series is many unique tokens (721); an **Edition** is
1599
- many copies of a token (1155). The creator's word is **copies** — `abx deploy img.png --copies 100`
1600
- (a single-artwork edition, `open` = uncapped), `abx deploy-series ./art --copies 50` (each image an
1601
- edition), `abx deploy-code sketch.js --copies 25` (code editions). Without `--copies`, the 721
1602
- lanes are unchanged.
1603
-
1604
- Three new canonical contracts twin the 721 lineup — `OneOfOneEdition`, `EditionImage`,
1605
- `EditionCode` — plus `AbxFixedPriceMinter1155`, a per-id fixed-price sale singleton
1606
- (`minter configure/buy --token-id [--quantity]`). New owner ops: `mint --token-id --amount`,
1607
- `transfer --amount`, `set-max-supply` (per-id cap, only ever decreases), `ping-uri` (re-emit the
1608
- native `URI` event after a re-point). ERC-1155C creator-token enforcement is the same `--721c`
1609
- opt-in (same validator registry and ERC-165 ids as 721C). Editions announce one new extension
1610
- (`abx.extension.edition-supply`, per-id supply/cap); everything else — metadata fields, params,
1611
- royalties, renderer, storage, effects — is the same protocol surface on both standards.
1612
-
1613
- **Integrator surface (audience line):** additive only. `ProjectState.contractType` gains
1614
- `'1of1-edition' | 'edition' | 'edition-code'`; `TokenState` gains `supply`/`maxSupply`/`holders`
1615
- (editions only; `minted` means supply > 0 there); the spine ABI now decodes
1616
- `TransferSingle`/`TransferBatch`/`URI`/`MaxSupplyUpdated`; `ChainDeployment` gains the four
1617
- edition anchors (recorded for Sepolia + Base Sepolia, CREATE2-identical); token-api summaries gain
1618
- an optional `copies` field. No existing export, route, column, or event shape changed.
1619
-
1620
- - 8c254d5: The self-host projection no longer stores or pre-fetches composed URI documents, and the event log
1621
- is append-only (from the abx-services 2026-08-05 projection memo, `docs/11`).
1622
-
1623
- - **`contractURI`/`tokenURI` are live reads, never projected.** `reconstructProject`/
1624
- `reconstructIncremental` gain `readUriDocuments` (default **false**) — with it off, the whole
1625
- `contractURI`/`tokenURI` head-read batch is skipped entirely, not fetched-and-discarded. These
1626
- fields have no settled value (a renderer can change the composed document with no log at all) and,
1627
- on the on-chain lane, can run hundreds of KB per token — a real 32-token project measured 13.4 MB
1628
- serialized, 99.8% of it `tokenURI`. A stale-but-rendering `data:` URI is worse than a missing one,
1629
- because nothing about it looks wrong. `abx demo` is the one caller that passes `readUriDocuments:
1630
- true`, for its read-back teaching step; `abx deploy`/`deploy-series`/`deploy-code` don't, and
1631
- `abx verify`/`tokenuri`/`contracturi` were already doing independent live reads. **For
1632
- integrators:** `ProjectState.contractURI` / `TokenState.tokenURI` are now `null` unless you opt in
1633
- — including in a self-host node's `/api/project/:addr` responses (shape unchanged, values now
1634
- null). Nothing that composes served metadata reads either field (the settled `fields`/
1635
- `collectionFields` do); the settled scalars — `tokenURIRenderer`/`tokenURILocked`/
1636
- `contractURIRenderer`/`contractURILocked` — are unaffected and still projected.
1637
- - **The events table is append-only.** `seq` is now chain-derived (`(blockNumber << 32) |
1638
- logIndex`, BigInt-safe) instead of the array index at write time, so it's stable across re-folds —
1639
- a re-index appends via `ON CONFLICT DO NOTHING` instead of deleting and reinserting the whole
1640
- history on every delta. `putProject` upserts the project and token rows in place (a token whose id
1641
- vanishes from state is still removed) rather than delete-and-reinsert. `deregister` still deletes
1642
- a forgotten project's events in full; a routine re-fold does not. **A full replay (`abx index
1643
- --full`) purges the project's event log before rebuilding it** — append-only must not let a
1644
- reorg-replaced event survive as a stale row, so the documented repair path stays a real repair.
1645
- Existing stores migrate their positional `seq` values to the chain-derived form once, automatically
1646
- (`PRAGMA user_version`-gated, transactional).
1647
- - **Self-healing eventCount guard.** An incremental fold that would reduce `eventCount` below what's
1648
- already stored is discarded rather than written, with one automatic full-reconstruct fallback (the
1649
- memo's own war story: a bad fold once silently zeroed a live projection with nothing to catch it).
1650
- - **New self-host stores open with `auto_vacuum = INCREMENTAL`.** Existing stores are unaffected
1651
- (the pragma only takes on a database with no tables yet) — recorded as backlog B29.
1652
-
1653
- ## 0.1.0-alpha.11
1654
-
1655
- ### Minor Changes
1656
-
1657
- - d40caf4: Requested by an integrator (abx-services) — the conformance surface now lives in the neutral
1658
- layer. Five pieces that only token-api or storage exposed before, and that any third-party
1659
- resolver needs to reproduce the reference behavior byte-for-byte, move into `@artblocks/abx-sdk`:
1660
-
1661
- - **The generator-document family** (`ABX_JS`, `escapeInlineScript`/`escapeInlineJson`,
1662
- `buildGeneratorDocument`, `injectTokenDataIntoHtml` — new `src/generator-document.ts`): pure
1663
- string operations with no resolver-specific behavior, so the SDK, `abx preview`, and any
1664
- third-party provider now share one definition instead of the CLI importing token-api just for
1665
- this.
1666
- - **The registry-dependency family** (`DEPENDENCY_REGISTRY_ABI`, `activeRegistry`,
1667
- `resolveRegistryDep`, `registryDepUrl`, `dependencyScriptTags`, `URL_BUDGET_BYTES`), merged into
1668
- the SDK's existing `deps.ts` alongside its registry-pointer logic. `node:zlib` can't come along
1669
- — SDK core has to stay reachable from a browser bundle — so decompression is now an INJECTED
1670
- `inflate?: (bytes: Uint8Array) => Uint8Array` threaded through `resolveRegistryDep`/
1671
- `dependencyScriptTags`; omitting it when a resolved dep actually needs decompressing throws a
1672
- new typed `InflateRequiredError` naming the fix, rather than crashing opaquely or silently
1673
- degrading. `@artblocks/abx-sdk/node` gains `nodeInflate` (a one-line `gunzipSync` wrapper) so a
1674
- Node host wires it in one line; a browser host passes a `DecompressionStream`-based
1675
- implementation instead. (Merge note: the prior `dependencyRegistryReadAbi` and token-api's
1676
- `DEPENDENCY_REGISTRY_ABI` declared the identical `getDependencyDetails` entry twice — deduped
1677
- into one array that also carries `getDependencyScript`.)
1678
- - **`contentTypeFromPath`** (new `src/mime.ts`): the MIME extension-map lookup, verbatim.
1679
- - **Gateway resolution, split pure/env** (new `src/gateways.ts`): `resolveGatewayBase` is now PURE
1680
- (no env read — takes resolved `{ipfs?, arweave?}` overrides) and `gatewayUrlFor` moves alongside
1681
- it; `gatewayConfigFromEnv()` is the one place that reads `ABX_IPFS_GATEWAY`/
1682
- `ABX_ARWEAVE_GATEWAY`, kept separate so a host with its own gateway config never has to touch
1683
- `process.env` through this module at all.
1684
- - **`exponentialBackoffDelay(attempt, baseMs, capMs)`** (`util.ts`): `min(baseMs * 2^(attempt-1),
1685
- capMs)`, 1-indexed like the existing `linearBackoffDelay`. Prefer it for a sustained rate limit
1686
- or an overloaded upstream; `linearBackoffDelay` stays right for a one-off transient failure.
1687
- `service.ts`'s own retry ladder is unchanged (still linear — its rationale comment stands).
1688
-
1689
- Token-api and storage keep every existing export working, re-exported from the SDK where the
1690
- implementation moved — no removals, and token-api's own `nodeInflate`-pre-wired wrappers mean its
1691
- internal call sites (`code.ts`'s document assembly, `deps.ts`'s `depStatusReport`) needed no
1692
- signature changes at all. The browser-bundle test (`packages/sdk/test/browser-bundle.test.ts`)
1693
- stays green with all of this now exported from the core index — proof that the injected-`inflate`
1694
- design actually keeps `node:zlib` out of the bundle.
1695
-
1696
- ### Patch Changes
1697
-
1698
- - d40caf4: Drop-in for every consumer of `reconstructProject` / `applyHeadReads`; no signature changed.
1699
-
1700
- **Fix `multicallChunked`'s per-leg retry so it actually recovers on a chain with no multicall3
1701
- deployment, not just on an over-budget aggregate.** When the aggregate `client.multicall` throws,
1702
- the retry re-asked each leg one at a time — but through the _same_ `client.multicall` call. That
1703
- recovers a batch that failed for being too big (retrying smaller aggregates works fine), but not one
1704
- that failed because this chain has no multicall3 contract at all: every multicall, at any width,
1705
- fails identically, so the retry reproduced the exact same failure and the caller got all-null,
1706
- indistinguishable from "the contract answered nothing." `reconstructProject`'s head-read
1707
- (`applyHeadReads`) runs every identity field — `name`, `symbol`, `owner`, the URI-lane renderer/lock
1708
- flags, `contractURI`, every `tokenURI` — through this helper, so on any chain lacking multicall3 a
1709
- reconstruct silently blanked those fields instead of reading them.
1710
-
1711
- The per-leg fallback now goes through `client.readContract` instead, which has no multicall3
1712
- dependency, and is also used for a chunk that lands at width 1 on its own (e.g. the last chunk of an
1713
- odd-length list) — previously that lone leg never retried at all. `readContract` throws on revert
1714
- where multicall's `allowFailure` returns a `status: 'failure'` result instead; the fallback catches
1715
- that per leg and maps it back to `null`, so a genuine revert still reads as "no answer," matching the
1716
- aggregate's own contract. The over-budget recovery path is unchanged in outcome, only in which call
1717
- answers the retry.
1718
-
1719
- ## 0.1.0-alpha.10
1720
-
1721
- ### Minor Changes
1722
-
1723
- - 11fa933: The extraction phase of the simplification refactor: business logic that lived only inside the CLI
1724
- is now importable — the CLI calls the same functions you can.
1725
-
1726
- **Into the SDK:** the five trust-anchor bootstraps (`ensureFactory`, `ensureSeriesFactory`,
1727
- `ensureSeriesCodeFactory`, `ensureRenderer`, `ensureSeedSource` — `anchors.ts`, on the same
1728
- injected-`send` + `onEvent` pattern as `ensureChunkStore`, with a typed `AnchorUnavailableError`),
1729
- `detectCanonicalFactory`/`resolveScanFloor`, the on-chain-URI setup composer (`onchain-uri.ts`),
1730
- interrupted-deploy resume planning (`resume.ts`), migration plan/parity reconciliation
1731
- (`migrate.ts`), the `abx.js` static analyzer (`inspect.ts`), dependency setup legs (`deps.ts`),
1732
- content-staging plans (`staging.ts` — `planStagedContent`, `stageFieldContent` with `StagingEvent`),
1733
- `mintedTokenIds`, and a typed `readSaleConfig` for the fixed-price minter.
1734
-
1735
- **Into storage:** `uploadAndLocate`, `repinNodeCustody` (the byte-custody half of migration),
1736
- `decideImageContentLane`, `assessStorageReadiness`/`assessTurboFunds` (the Arweave/Turbo funding
1737
- math), and `awaitLocatorReady` (poll a locator until it serves).
1738
-
1739
- **CLI hardening that fell out of the dedup:** one risk gate (`gatedSend`) now guards every write —
1740
- `--dry-run` and `--confirm` mean the same thing on every command, all owner-ops gain `--confirm`,
1741
- and a write reaching the send lane under `--dry-run` is structurally impossible (grep-enforced by
1742
- test). One `CHAIN` source of truth; one memoized local-indexer accessor.
1743
-
1744
- - 11fa933: Phase 1 of the simplification refactor: one transaction shape with one send-injection point, and an SDK that never touches `.env` or a Node-only global on its own. Two related passes, same release.
1745
-
1746
- **One transaction shape, one executor.** `StagingTx`/`SendStagingTx` are gone — everything is a `PreparedTx` sent through a single `SendTx`. New `execute.ts` carries `makeHotSender` (pins the nonce once, detects an under-estimated gas limit against a just-deployed target, throws a typed `TxRevertedError` instead of reporting a reverted tx as confirmed) plus `pinGas`/`waitForCodeAt`, moved from the CLI (`gas.ts` is gone) with their design comments intact. Every `deployX` is now `prepareDeployX` (pure) + `deployX(send, ...)`; the CLI's hot lane and `abx deploy-code --resume` both delegate to the same sender instead of hand-rolling their own nonce/gas loop — the class of bug that reported a burned, reverted transaction as a successful deploy can't recur in a second call site because there is no longer a second implementation.
1747
-
1748
- **The SDK never loads `.env` implicitly.** `loadDotEnv`/`parseEnvContent` moved to a new `@artblocks/abx-sdk/node` subpath — the only module that touches `node:fs`/`node:path`. The package's main entry is now browser-bundle-safe (a permanent esbuild smoke test guards it); a host calls `loadDotEnv()` once at startup (the CLI's `main()`, the effects runner's `main()`) and the SDK core reads whatever's already in `process.env` through a tiny `readEnv` that no-ops outside Node. `ClientOptions` gained `rpcUrls?: string[]` and `makeWalletClient`/`envSigningKey` gained a `privateKey`/`override` escape — explicit config now wins outright over env resolution, following the same override → env → manifest precedence `deployments.ts` already used for contract addresses.
1749
-
1750
- **Single signing-key name.** `ABX_DEPLOYER_PK` is the only env var the SDK reads for a hot key — `SEPOLIA_FUNDED_PK` and `SEPOLIA_WALLET_PK` (earlier-alpha names) are no longer consulted. A caller with one of the old names set now gets a `MissingSigningKeyError` that names the new one explicitly and says the old ones are retired, rather than a plain "no key found" that leaves an upgrader hunting for why a key that's clearly _there_ isn't being read. Sandbox/e2e scripts and test fixtures move to the new name too.
1751
-
1752
- **Shared, environment-neutral utilities.** New `sdk/src/util.ts`: `sleep`, a `linearBackoffDelay` helper (service.ts's retry ladder and the effects runner's resolver poll both use it now instead of hand-rolled backoff math), `parseDataUri` (replaces three near-identical `data:` URI regexes across the CLI and the tokenURI probe with one permissive parser), and `tryReadContract` (replaces three near-identical try/read-return-undefined helpers; the CLI's "no contract at this address" diagnostic still wraps it where that mattered). `Buffer` is gone from the SDK core (chunk hex-encoding and param base64-encoding now use viem's `bytesToHex` and a ~12-line dependency-free base64 codec) — nothing in the SDK's main import graph is Node-only any more.
3
+ ## 0.1.0-alpha.30
1753
4
 
1754
5
  ### Patch Changes
1755
6
 
1756
- - 11fa933: The remaining phases of the simplification refactor that hadn't yet gotten a changeset: the CLI's
1757
- internal module split, the token-api/effects/mint-page convergence on the SDK, the shipped skill's
1758
- rewrite for the simplified surface, and a new SDK README.
1759
-
1760
- - **`abx`'s `main.ts` split into domain command modules** (`commands/{deploy,project,reads,service,
1761
- scaffold,storage}.ts`, shared `output.ts`/`errors.ts`), with one exit-discipline rule
1762
- (`process.exitCode` + return, or a typed `CliError`, everywhere — bare `process.exit` only at the
1763
- entry guard, the top-level catch, and the keep-alive SIGINT handler). Purely internal: a 207-fixture
1764
- byte-diff matrix (every help text, dry-run, error path, and exit code) confirmed identical output
1765
- before and after.
1766
- - **token-api / effects / mint-page converge on the SDK**: `@artblocks/abx-storage` gains one
1767
- `resolveGatewayBase` (`readiness.ts`), replacing three near-identical copies (two in token-api, one
1768
- inline in storage itself); token-api exports `buildGeneratorDocument` so the CLI's `abx preview`
1769
- consumes the real generator-document assembler instead of a hand-kept duplicate; the effects runner
1770
- now resolves its config via the SDK's `readEnv` and gets a `makePublicClient` fallback transport, so
1771
- `ABX_RPC_URL` accepts a comma-separated failover list like every other RPC var; the scaffolded
1772
- mint-page app now imports ABIs from `@artblocks/abx-sdk/abi` and a browser-safe `makePublicClient` +
1773
- typed `readSaleConfig` instead of hand-rolled fetch/decode, and pins its generated `package.json` to
1774
- the SDK's _resolved_ version via a new `@artblocks/abx-sdk/package.json` export (alpha version
1775
- counters diverge per package under changesets, so pinning the CLI's own number could produce an
1776
- unsatisfiable range).
1777
- - **The shipped skill (`.claude/skills/abx-self-host/`) is rewritten for the surface phases 0–5
1778
- actually shipped**: every warning made obsolete by an enforcement is deleted rather than softened —
1779
- predict-only deploy-preview addresses, the `approvals N` line, the single `ABX_REMOTE_SELF_*`
1780
- credential grammar, `doctor`'s version/provenance ladder, `storage show --check`, the render/storage
1781
- combo validator's dry-run row, and `ABX_DEPLOYER_PK` as the only key name. Retired names swept from
1782
- `dev-loop-test`, the agent-eval scenarios, and spec prose. The skill ships bundled inside this CLI
1783
- package (co-versioned via `SKILL.md` frontmatter), so it rides this same patch.
1784
- - **New `packages/sdk/README.md`**: what the SDK is, the send-injection model (`PreparedTx` +
1785
- `SendTx`, `makeHotSender` for a hot key, bring-your-own for a wallet/multisig), a complete
1786
- deploy → upload → mint → read walkthrough against real exports, and browser-use notes (explicit
1787
- `rpcUrls`, no env, the `/node` subpath is Node-only). Included in the npm tarball automatically
1788
- (README is one of the files npm always packs, regardless of the `files` allowlist).
1789
-
1790
- ## 0.1.0-alpha.9
1791
-
1792
- ### Patch Changes
1793
-
1794
- - df298d8: Fix three ways the reference resolver misreported on-chain state — and the head-read bug underneath one of them
1795
-
1796
- From an engineering audit by the abx-services team (2026-08-04), who run the same protocol on a
1797
- hosted node against real testers and so reach long-tail states a single-node run rarely does. All
1798
- three findings reproduced. Verifying the second one turned up a fourth defect that was its actual
1799
- cause, and that one is the most consequential of the set.
1800
-
1801
- **1 · A renderer-computed TEXT field was dropped entirely.** `resolveImage` had an `R.renderer`
1802
- branch; `resolveText` did not. So a `renderer`-represented `description`, `external_url`,
1803
- `animation_url`, `background_color`, `youtube_url`, or `name` matched no branch and resolved **as if
1804
- unset** — silently replaced by the operator's off-chain value with provenance reporting `off-chain`,
1805
- or omitted from the metadata altogether. A creator who committed a computed field on-chain got the
1806
- operator's version served instead, and nothing errored anywhere.
1807
-
1808
- `resolveText` now mirrors `AbxMetadataRenderer._appendText`'s `R_RENDERER` arm exactly, because the
1809
- two must agree: with `tokenURIRenderer` set the chain assembles this JSON and the resolver merely
1810
- re-serves it, so any difference is a resolver contradicting the token's own `tokenURI`. That means
1811
- the **declared** contentType is used verbatim (`renderer` is the one representation that types itself
1812
- on-chain) rather than an assumed `text/html`, and a `text/uri-list` result (RFC 2483 — the canonical
1813
- generator's directory branch) lands as the locator it is, never data-wrapped. This is a deliberate
1814
- divergence from the shape the audit recommended (pointing at the node's own `/data/{field}` route):
1815
- parity with the on-chain renderer is the stronger constraint here. A field renderer that reverts now
1816
- degrades that one field — with provenance saying the on-chain attempt failed, not a clean
1817
- `off-chain` — instead of being indistinguishable from an unset field.
1818
-
1819
- **2 · The `name` fallback asserted an ERC-721 `name()` read it had not performed.** When
1820
- `state.name` is null the served value is the raw contract address, and the note still said
1821
- `on-chain (ERC-721 name() + #id)`. A hosted node served `"name": "0xb844…c35e56 #0"` beside that note
1822
- while the contract's `name()` was `"ABXdoku"`. The wrong name is cosmetic; provenance is the surface
1823
- you would point an artist at to audit their own metadata, so a false positive there costs more. The
1824
- note is now conditional, and deliberately does not adjudicate _why_ the value was empty — the
1825
- projection cannot tell an unnamed contract from a failed read, and guessing is how the original note
1826
- came to lie.
1827
-
1828
- **3 · The live view answered "not a code project" for a code project that was merely mid-index.**
1829
- `/a/` collapsed every `resolveLiveView` null into one confident verdict about the **contract**, when
1830
- at least one is a statement about the **index**: `chunkCount` is a head read, so a template-mode
1831
- project whose chunks have not been folded in yet reads as zero. A tester got
1832
- `live · caught up · 127 events · 32 tokens` from the node and this 404, concluded the resolver did
1833
- not recognise the SeriesCode factory, and a `--full` re-index did not clear it — the confident
1834
- wording sent them looking in the wrong place. The route now consults `contractType` (folded from the
1835
- deployed extensions, so it knows before any code lands) and answers **503 + Retry-After** for "not
1836
- yet" versus 404 for "not ever". Both verdicts are answered before the chain client is touched, since
1837
- neither needs an RPC. The three-way decision is extracted as `liveViewAvailability(state)` so it has
1838
- a test seam at all.
1839
-
1840
- **4 · The head-read multicall failed _whole_ on the flagship lane, and took identity, trust, and the
1841
- URI lane with it.** This was not in the audit — it is why finding 2 fires, found by reproducing it
1842
- instead of accepting "an RPC hiccup". `applyHeadReads` batched `name`, `symbol`, `owner`,
1843
- `contractURI`, `isAbxClone`, `implementation`, the four URI-config getters, and **one `tokenURI` per
1844
- token** into a single multicall. A multicall is one `eth_call`: on a fully-on-chain project
1845
- `tokenURI(id)` assembles the entire metadata document on-chain, so a handful of those legs exceeds a
1846
- public node's budget and _every_ leg in the aggregate reports failure — including legs that answer
1847
- fine alone.
1848
-
1849
- Measured on Base Sepolia `0xB844F4D2137a8Ce785Cbc80D281A36DBD1c35E56` (32 tokens, chain-complete):
1850
- 1 tokenURI leg → 5/5 succeeded; **4 legs → 0/8 succeeded**, while `name()` on its own returned
1851
- `"ABXdoku"`. Every resolver indexing that collection therefore believed it had no name, no symbol,
1852
- no `contractURI`, no canonical proof — and **no `tokenURIRenderer`, i.e. that it was not in the
1853
- on-chain-URI lane at all.** Deterministic for any sizable on-chain collection, not a hiccup.
1854
-
1855
- Reads are now split by cost class: the cheap fixed-size ones that decide a project's identity and
1856
- lane go in a batch of their own and can never be collateral damage, while the unbounded ones
1857
- (`contractURI`, per-token `tokenURI`, script chunks) read in small chunks through a helper that
1858
- re-asks a failed chunk one leg at a time before believing it. After the fix, that same contract
1859
- reconstructs with its name, symbol, both URI renderers, and all 32 token URIs intact.
1860
-
1861
- 12 regression tests across the four, including the null-name case both surfaces missed because every
1862
- parity fixture on both sides hardcoded a non-null name.
1863
-
1864
- - df298d8: `abx tokens <address>` — every token's owner, seed, and params, from chain alone
1865
-
1866
- The most obvious post-deploy question about a generative collection — _what did the seeds actually
1867
- deal?_ — had no command. `abx verify` reports per-token minted/render status but no seed; `abx
1868
- tokenuri` prints one token and the seed lives inside its base64 `animation_url`; `abx inspect` is
1869
- pre-deploy and static. An agent's workaround was to start `abx serve`, `GET /api/project/<addr>`,
1870
- base64-decode each `tokenURI`, base64-decode the `animation_url` inside it, then regex
1871
- `0x[0-9a-f]{64}` out of the resulting HTML — 32 times. Every input to that was already a plain
1872
- contract read.
1873
-
1874
- `abx tokens <address> [--json] [--from <id>] [--limit <n>]` is chain-only: no indexer projection, no
1875
- running resolver, no event scan. The params store maintains its own key lists inside its write paths
1876
- (`tokenParamKeys` / `contractParamKeys`) and `seed` is a reserved param read by name, so the whole
1877
- listing is `eth_call`s — available to anyone with an RPC URL, including before any indexing has
1878
- happened. Seeds print in full (truncating the one value the command exists for would repeat the
1879
- `tokenuri` bug); `--json` emits `{tokenId, owner, seed, params}` per token, contract-scope params
1880
- once on the parent rather than copied into every row.
1881
-
1882
- It reports honestly across all three token types rather than failing: a 1/1 has no params extension
1883
- and says so (owners still list), a pre-enumeration project still yields seeds and flags that params
1884
- can't be enumerated, and an id whose `ownerOf` reverts is `null` — not "not minted", because a
1885
- burned id and an unminted id revert identically and a chain-only read can't tell them apart.
1886
-
1887
- Two things it deliberately is not. It is not a trait spread: a trait comes from running the script
1888
- against the seed, which is `abx render`'s job — the token-scope _param_ spread it does print is
1889
- labelled as such. And the reader is in the **SDK** (`listTokens`), not just the CLI, so a
1890
- programmatic integrator gets it without reimplementing it.
1891
-
1892
- Backlog B25 (from the 2026-08-04 tester batch, feedback `dcfdc6f4`).
1893
-
1894
- ## 0.1.0-alpha.8
1895
-
1896
- ### Minor Changes
1897
-
1898
- - e325b46: Opt-in ERC-721C support across the toolkit — plain ERC-721 stays the transparent default.
1899
-
1900
- SDK: `OneOfOneInitParams`/`SeriesInitParams` (and `SeriesCodeInitParams` by inheritance) gain
1901
- `transferValidator` immediately after `royaltyBps` — `zeroAddress` = plain ERC-721 forever (the
1902
- default), non-zero = permanent 721C enrollment with that validator. A new `creator-token` module
1903
- ships the per-chain `RECOMMENDED_TRANSFER_VALIDATOR` (OpenSea's
1904
- StrictAuthorizedTransferSecurityRegistry, verified live on Sepolia + Base Sepolia), the two
1905
- creator-token ERC-165 ids + the ABX extension id, `readCreatorTokenStatus()` (`{enrolled,
1906
- validator}`), and a `prepareSetTransferValidator()` write wrapper.
1907
-
1908
- CLI: the deploy commands take `--721c [recommended|0x…]` — absent means zero behavior/output
1909
- change; `recommended` resolves the per-chain constant (refused, naming the chains that have one,
1910
- where none is known); an explicit address is EIP-55-validated and pre-checked for code before any
1911
- gas. Enrolling prints one plain statement of what enforcement means. A new owner op
1912
- `abx set-transfer-validator <address> <0x…|none|recommended>` re-points or suspends an ENROLLED
1913
- collection's validator (guards `--dry-run`; refuses plain ERC-721s up front — enrollment is a
1914
- deploy-time decision). `abx state` shows the validator for enrolled collections only.
1915
-
1916
- ## 0.1.0-alpha.7
1917
-
1918
- ### Minor Changes
1919
-
1920
- - 1b50f9d: Fixes from an integrator batch: a machine-readable `tokenuri`, one Arweave identity across CLI and SDK, a correct OpenSea refresh, and attach telling the truth.
1921
-
1922
- **`abx tokenuri --json`.** The command abbreviated long values (`… (382 chars)`) with no way to turn it
1923
- off, so for a token whose whole point is on-chain content it returned something that _looked_ like the
1924
- metadata and wasn't. An integrator scraped it, stored a `data:` URI cut to 96 characters, and only
1925
- found out in production; they abandoned the CLI as a read path and reimplemented `eth_call`. `--json`
1926
- now emits the verbatim decoded document — no banner, no ANSI, no truncation — so
1927
- `abx tokenuri <addr> --json | jq` is a supported read path. The human view still abbreviates, and now
1928
- says `[--json for the full value]`.
1929
-
1930
- **One Arweave identity, resolved in one place.** `arweaveConfigFromEnv()` read `ARWEAVE_JWK` and
1931
- nothing else, while the CLI mints and manages `.abx-self-host/arweave-key.json`. Porting a working CLI
1932
- flow to the SDK — same machine, minutes later — failed every upload with "Arweave via Turbo needs an
1933
- identity", a message that says storage was never configured when the truth was that two layers
1934
- disagreed about where the identity lives. `@artblocks/abx-storage` now exports `resolveArweaveJwk()`
1935
- (env → managed key file) and the CLI delegates to it. Its diagnostics come with it: an empty key file
1936
- now reports the **path** and the remedy instead of `Unexpected end of JSON input`, and a corrupt one
1937
- says the same.
1938
-
1939
- **`abx refresh` on the default chain.** The OpenSea slug map held only `sepolia` and `mainnet`, so
1940
- `base-sepolia` — the CLI's own default — fell through to the raw key: the refresh POST went to a slug
1941
- OpenSea doesn't know, and the printed link pointed at **mainnet** `opensea.io` for a testnet token.
1942
- Slugs are now correct (`base_sepolia`), `testnet` comes from the chain registry rather than a second
1943
- hand-maintained set, and a chain with no known slug produces **no link** instead of a wrong one. Same
1944
- shape as the hardcoded explorer table that once sent every Base Sepolia link to Etherscan.
1945
-
1946
- **`abx attach` names its dependency.** Attaching artifacts to a project that resolves on-chain now
1947
- warns, before the send, that they will **not** appear in `tokenURI` — the on-chain renderer carries
1948
- reserved fields only, and the artifacts manifest comes from a resolver. A team attached five audio
1949
- stems to a fully-on-chain token and found them "paid for, stored on-chain, and invisible"; the note
1950
- that existed was one dim line that read as a footnote rather than as a missing service.
1951
-
1952
- **`ensureChunkStore` moved to the SDK.** The bootstrap every on-chain-content path needs existed only
1953
- inside the CLI, so an SDK integrator got `resolveChunkStore()` (may return undefined) plus a separate
1954
- `storeSupportsWriteContent()` they had to remember — forget it and an incapable store fails _deep
1955
- inside a mint, after transactions have landed_. One team hand-rolled the guard for exactly that reason.
1956
- `ensureChunkStore(publicClient, send, {chainId, override, onEvent})` is now exported; the SDK reports
1957
- progress through `onEvent` instead of printing, and the CLI keeps its narration.
1958
-
1959
- **`abx storage upload --json`.** The locator as data. They scraped this line, captured its ANSI colour
1960
- codes along with the URL, wrote the result into a _stored_ player URL, and found out when it 404'd in
1961
- production. In `--json` mode stdout carries the JSON and nothing else; progress moves to stderr.
1962
-
1963
- **`--backend ipfs` no longer hides a missing credential.** Without `PINATA_JWT` the backend resolves to
1964
- **kubo against a local node**, so a dry run looked fine and the real upload failed for anyone not running
1965
- one. The preview now says so. Related correction: the skill claimed "a backend missing its secret falls
1966
- back to `fs`" — it does not. `cloud` refuses up front naming the missing values, and `ipfs` goes to the
1967
- local node; nothing silently degrades to local disk. Both sides now say the same thing.
1968
-
1969
- Reported in the 2026-08-03 MXRR integration batch (feedback 869f27b1, a256217f, 119d7e8e, 975c363e,
1970
- e38216db, e267078b).
1971
-
1972
- - 1b50f9d: On-chain param enumeration: a project's parameters are now readable from the chain that holds them, and the `params.keys` convention is retired.
1973
-
1974
- The params store was unenumerable by design, so anything that wanted to know _which_ parameters a
1975
- project has had to be told. The answer was a convention: a `params.keys` contract param holding a
1976
- comma-separated list, composed by `deploy-code` from the `--schema` flags and hand-maintained
1977
- thereafter. It worked, and it had the defect every hand-maintained index has — a key configured but
1978
- not listed was **silently omitted from every render**. The schema existed, a collector could set it,
1979
- the art never saw the value, and nothing anywhere reported a problem. The CLI grew a nudge, then a
1980
- same-transaction companion write, and both were treatments for a design that should not have needed
1981
- them.
1982
-
1983
- **The token now maintains its own key lists.** `SeriesCode` gains `tokenParamKeys(tokenId)`,
1984
- `contractParamKeys()`, and `paramSchemaKeys()` (plus `…Paged` variants for surfaces past an RPC's
1985
- return cap). The lists are updated inside the write paths themselves, so `key ∈ list ⟺ the param is
1986
- set` holds for every writer — raw owner writes, governed `configure-param` writes, hook-driven writes,
1987
- all of them. No caller can forget, because no caller is involved. (`seed` is deliberately never listed:
1988
- every consumer reads it as a tokenData coordinate, and indexing it would charge every seeded mint for
1989
- nothing.) `paramSchemaKeys()` closes the other half — a chain-only frontend can now build a configure
1990
- UI, including for keys declared but never yet written, which nothing off-chain could previously
1991
- discover.
1992
-
1993
- **The canonical `AbxGenerator` reads that surface instead of the CSV.** It no longer looks at
1994
- `params.keys` at all, and a `params.keys` value set on a new project is simply an ordinary parameter —
1995
- enumerated and emitted like any other, by both the generator and the SDK. A latent parity bug dies with
1996
- the convention: the generator's no-CSV path emitted tokenData in insertion order while the canonical
1997
- serializer sorts, so the two byte-forms could disagree; there is now one form.
1998
-
1999
- **`AbxMetadataRenderer` is spec v4.** `tokenURI` gains a computed `abx_params` object — every set
2000
- parameter, contract and token scope merged, token wins, sorted, decoded by the same rules the generator
2001
- and the SDK use. A data-backed value over 2048 bytes is emitted as `{"keccak256":"0x…"}` — its on-chain
2002
- commitment — rather than inline, so a large `Bytes` parameter cannot bloat `tokenURI` past a single
2003
- `eth_call`; every key still appears, and an oversized one degrades self-describingly. Parameters are
2004
- _not_ folded into `attributes`: that stays the creator's surface. Also in v4: an `animation_url` carried
2005
- by the `inline` or `reader` representation is now wrapped as `data:text/html;base64,…`, exactly as
2006
- `image` already was — the asymmetry was an oversight, and it meant a fully on-chain animation was
2007
- handed to wallets as bare text.
2008
-
2009
- Both changes are additive on the read side: pointed at a token that predates enumeration, a v4 renderer
2010
- simply emits no params block. **Repointing the metadata renderer is safe anywhere.** Repointing the
2011
- _generator_ is not, and `abx set-field` now refuses it: a legacy implementation plus the current
2012
- generator means every parameter silently vanishes behind a `tokenURI` that still looks healthy, so the
2013
- CLI stops you rather than warning you, and names both ways out (stay on the project's existing
2014
- generator, or redeploy).
2015
-
2016
- Everywhere else in the toolkit, the convention is simply gone:
2017
-
2018
- - `deploy-code --onchain-uri` sets up in **three** legs, not four (animation field → generator, plus
2019
- the two URI renderers). There is no key list to compose, report, or keep in sync, and the help text
2020
- no longer teaches one.
2021
- - `abx set-schema` is a single op again — no companion write, no multicall. This **supersedes**
2022
- alpha.13, which shipped `set-schema` writing `params.keys` in the same transaction to stop a governed
2023
- key going unlisted: the contract now maintains its own key list, so there is nothing to keep in step
2024
- and the drift that fix guarded against is gone rather than mitigated. Same for that release's note
2025
- that "there is no on-chain enumeration of schema keys" — `paramSchemaKeys()` is exactly that.
2026
- - `abx state` reads the governed surface from `paramSchemaKeys()` and the collection-scope values from
2027
- `contractParamKeys()`. A project deployed before enumeration falls back to reading its old
2028
- `params.keys` list, read-only, so live testnet drops still describe themselves.
2029
- - `abx verify` notes when a project's enumerated surface exceeds ~64 keys — the documented design
2030
- envelope. The write side is unbounded; the read side is what grows, since `tokenURI` and `tokenData`
2031
- assemble every parameter per call.
2032
- - `abx configure-param <addr> - params.keys <csv>` no longer has a special path. Writing that key is
2033
- now an ordinary schema-less contract param, and it shows up in tokenData as one — honest, and
2034
- documented.
2035
- - `abx_params` joins `artifacts` and `abx_provenance` as a computed key `set-field` and `attach` refuse.
2036
-
2037
- The SDK needed no semantic change: `buildTokenData` has always been event-derived (coordinates + seed +
2038
- every set param, both scopes, token wins, augment entries), and the contract enumeration implements
2039
- exactly that rule. Its `deployments` manifest carries the new addresses.
2040
-
2041
- ## 0.1.0-alpha.6
2042
-
2043
- ### Minor Changes
2044
-
2045
- - 1158420: Expose the PostParam schema lifecycle: `abx set-schema`, `abx retire-param`, Address legs, and `lock=`.
2046
-
2047
- Three capabilities the contracts have always had, that the toolkit could not reach — so they read to
2048
- creators as protocol limitations. All three were reported in the 2026-08-03 tester batch.
2049
-
2050
- **A project's param surface was never frozen at deploy.** `setParamSchema` is owner-gated with no
2051
- deploy-time restriction and no `exists` check, so it is an upsert usable for the life of a project.
2052
- There was just no command for it, and the CLI said so out loud ("Adding a param to an already-deployed
2053
- contract isn't a CLI command yet"), which pushed designers toward guessing their full param surface up
2054
- front or redeploying — losing the address, the mints, and the collectors. `abx set-schema <addr>
2055
- --schema key:Type:Auth` attaches or replaces one key.
2056
-
2057
- Because it is a **full-row upsert on a contract that never re-validates stored values**, the command
2058
- carries a guard rather than a warning: it prints before/after, and _refuses_ a change that could strand
2059
- values already written under the key — a narrowed bound, a dropped `Select` option, a changed type —
2060
- unless you pass `--force`. It also flags an existing `lock=` you are about to drop by not restating it.
2061
-
2062
- **A parameter can be retired.** There is no delete in the contract (`exists` is only ever set true), but
2063
- a `lockAfter` in the past makes every later write revert `ParamLockExpired`, permanently. `abx
2064
- retire-param <addr> <key>` does exactly that, reading the current schema and changing _only_ the lock so
2065
- type/auth/bounds/options carry forward untouched. It does not remove the key and does not erase a stored
2066
- value — a value written under a `TokenOwner`/`Address` leg came from a collector, and the artist
2067
- deliberately cannot delete it.
2068
-
2069
- **An `Address` auth leg is now expressible.** `--schema` previously rejected every Address-bearing leg
2070
- with "set that schema post-deploy via the contract" — advice pointing at a command that did not exist.
2071
- The auth token now names its holder inline (`board:Bytes:Address(0xabc…)`), and the error for a bare
2072
- `Address` says what the leg is for: a **contract** may hold it, which is how open and multi-party
2073
- participation is built today. `authAddress` and `lockAfter` were also hardcoded to zero at the
2074
- deploy-time call site, so neither was reachable there either; both now flow through `--schema`.
2075
-
2076
- `--schema` gains an optional 4th field, `lock=<when>` (ISO date, unix seconds, or `now`), sharing the
2077
- Timestamp grammar the bounds already use. A 4th field that is not `lock=` now reports the spec-shape
2078
- error instead of a mangled "malformed type", which is what a `:` inside a `Select` label used to produce.
2079
-
2080
- New in the SDK: `prepareSetParamSchema`, `prepareRetireParam`, `readParamSchema`, `OnChainParamSchema`.
2081
-
2082
- (feedback 4a0c213a, 5d530681, 381bdcbe)
2083
-
2084
- > [Superseded 2026-08-03: params now enumerate **on-chain** (renderer spec v4, `abx_params`) and
2085
- >
2086
- > > `params.keys` is retired — the contract maintains its own key list, so nothing off-chain has to keep
2087
- > > it in step and `abx state` reads the chain directly. See the on-chain param enumeration entry.]
2088
-
2089
- `set-schema` also keeps **`params.keys` in step, in the same transaction**. On the on-chain URI lane
2090
- the canonical generator builds tokenData from that CSV, so a key that is governed but not listed is
2091
- silently omitted from every render — the schema exists, a collector can set it, and the art never sees
2092
- the value. `deploy-code` composes the list from `--schema` for exactly this reason; without the
2093
- companion write, a schema added later would have quietly half-worked. Projects not on that lane (where
2094
- `params.keys` is unset) get no extra write.
2095
-
2096
- > [Superseded 2026-08-03: params now enumerate **on-chain** (renderer spec v4, `abx_params`) and
2097
- >
2098
- > > `params.keys` is retired — the contract maintains its own key list, so nothing off-chain has to keep
2099
- > > it in step and `abx state` reads the chain directly. See the on-chain param enumeration entry.]
2100
-
2101
- And `abx state` now prints the governed PostParam surface — each key's type, auth, bounds/options, an
2102
- upcoming lock date, and a `retired` marker for one whose lock has passed. There is no on-chain
2103
- enumeration of schema keys, so it reads the project's own `params.keys` list, which is also what the
2104
- generator reads; anything missing from it is invisible to renders anyway. It also names keys listed
2105
- there with no schema. This is what makes `set-schema`'s upsert safe to use: you can see a key's current
2106
- shape before overwriting it.
2107
-
2108
- ### Patch Changes
2109
-
2110
- - 1158420: Fix `deploy-code` reverting `DeploymentFailed()` — the setup transaction was sent with a gas limit estimated against a contract that did not exist yet.
2111
-
2112
- Every `deploy-code` attempt in a reporter's Base Sepolia session reverted with Solady's
2113
- `DeploymentFailed()` (`0x30116425`), in both the on-chain and hosted-resolver lanes, with a minimal
2114
- case of storing a single 3,563-byte script chunk. It was not a defect in the chunk path: the two
2115
- transactions simply **ran out of gas**.
2116
-
2117
- ```
2118
- 0xcad74d07… gasLimit 201,616 gasUsed 198,870 (98.6%)
2119
- 0xf4350724… gasLimit 169,301 gasUsed 166,810 (98.5%)
2120
- ```
2121
-
2122
- A code project deploys in two transactions: create the clone, then one setup `multicall`. The second
2123
- targets the contract the first just created — and `eth_estimateGas` for that call, taken while the
2124
- answering node has not yet seen the deploy block, returns the **calldata cost alone**. Replaying both
2125
- payloads against a codeless address reproduces the sent limits _to the gas_ (201,616 and 169,301);
2126
- against the real contract the same calls need 941,331. A setup multicall's cost is dominated by
2127
- CREATE code deposit (~200 gas per stored byte), so the underfunded CREATE inside `SSTORE2.write`
2128
- returned 0 and reverted. The 1/1 lane was unaffected because its setup fits inside a calldata-sized
2129
- budget.
2130
-
2131
- This is the same read-after-write lag the deploy loop already pins the **nonce** against, one field
2132
- over. The fix has two halves, and deliberately does not include a third:
2133
-
2134
- - **Every leg after the first waits for the target's code to be visible** to the client doing the
2135
- estimating, so a lagging node cannot produce a meaningless estimate in the first place. This is the
2136
- actual repair.
2137
- - **An impossible estimate is detected and refused, not replaced.** `PreparedTx` gained an optional
2138
- `gasFloor` carrying only the _provable_ part of a payload's cost — EVM code deposit at exactly 200
2139
- gas per stored byte. An estimate below that is not "low", it is proof the node is on stale state, so
2140
- the sender retries and then errors out with what it saw.
2141
- - **What we did NOT do: substitute a computed gas limit.** Only the deposit is derivable; the same
2142
- setup multicall also carries schema writes, dependency legs, URI legs and mints whose cost cannot be
2143
- known without simulating them. A "probably enough" constant is tuned to whoever's example was in
2144
- front of its author — it would have covered the reported single-chunk case and then under-funded a
2145
- three-schema deploy by ~200k, reproducing the identical `DeploymentFailed()` with a fresh mystery
2146
- attached. Refusing to send is strictly better than sending a transaction we can prove is
2147
- under-funded, which would burn the gas and orphan the contract.
2148
-
2149
- All three signing lanes carry this, not just the hot one: the env-key lane pins the limit before
2150
- `sendTransaction`, the wallet lane waits for code and hands the browser an explicit `gas` (a wallet
2151
- estimates against its own RPC, which we don't control and which lags the same way), and the cold lane
2152
- prints `gasMustExceed` — labelled a floor, not a limit — plus a note telling an external signer to
2153
- re-estimate rather than send if their own number comes back below it. The rule lives in one place
2154
- (`packages/cli/src/gas.ts`) so the lanes cannot drift apart on it.
2155
-
2156
- Reported in the 2026-08-03 tester batch (feedback 156ea0fb, 172111ae), root-caused from the full
2157
- transaction hashes supplied in the follow-up addendum.
2158
-
2159
- - 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.
2160
-
2161
- **An unknown `ABX_CHAIN` printed a raw Node stack trace — from every command.** Chain-derived values
2162
- were resolved at module scope, in `token-api` (which the CLI imports) and in the CLI itself, so the
2163
- throw happened during module evaluation, before `main()` existed to catch it. `ABX_CHAIN=mainnet abx
2164
- doctor` dumped an internal source path and exited 1 — including from the one command whose job is to
2165
- tell you what is wrong with your environment. Those resolutions are lazy now, and the CLI validates
2166
- the variable up front with an answer rather than a crash: unknown values list the shipped chains, and
2167
- a mainnet-shaped value says plainly that ABX is testnet-only today.
2168
-
2169
- **`abx inspect` reported "(no PRNG)" for a hand-written seeded generator — with the _stronger_
2170
- reproducibility verdict attached.** The `seeded` check only recognized p5's `randomSeed(`, so a
2171
- vanilla LCG or xorshift matched no branch and fell through to "traits look derived from the
2172
- seed/params directly". That is the common case, not an edge one — the skill's own canonical
2173
- dependency-free example hand-rolls an LCG, and all three sketches written by agents in the sweep hit
2174
- it. A hand-rolled generator now gets the `careful` verdict and is told the truth: deterministic and
2175
- reproducible on-chain, but only by porting that exact generator and call order into Solidity.
2176
-
2177
- Also: `--yes` is now documented in `deploy-code --help` (its own placeholder-identity refusal already
2178
- told you to pass it), and the `--onchain-uri` raster warning now names the two routes that actually
2179
- deliver a no-server image instead of only one.
2180
-
2181
- Found by the 2026-08-03 parallel sweep (8 cold Sonnet/Haiku agents, isolated sandboxes).
2182
-
2183
- ## 0.1.0-alpha.5
2184
-
2185
- ### Minor Changes
2186
-
2187
- - feba8c2: A resolver is no longer an object store: effect outputs split into **bound** and **referenced**
2188
- (`specs/protocol/effects.md → Bound vs referenced`), and the artifact registry enforces the split.
2189
-
2190
- An output is **bound** iff a binding stitches its _content_ into the metadata JSON (today exactly
2191
- `render/traits` → `attributes`); everything else is **referenced** — the projection carries its URL,
2192
- or it only appears in the `artifacts` manifest. That one distinction decides who holds the bytes, and
2193
- it is now the wire rule rather than a runner constant.
2194
-
2195
- - **`POST /v1/effect-artifacts` derives the mode from the binding, and refuses both mismatches.**
2196
- Bytes for a referenced output → `400` (the resolver redirects either way, so the bytes buy no
2197
- capability and cost it storage, retention and egress). A locator for a bound output → `400` (its
2198
- content is assembled into `tokenURI`; a pointer there used to be recorded and then silently never
2199
- stitch — a wrong answer served confidently). Bound content is capped at **64 KB**, and a locator
2200
- that only the producer could resolve (loopback/private host, presigned expiring URL) is rejected.
2201
- The resolver never fetches a locator while handling the write, and serves registered locators by
2202
- `302` — never by proxying.
2203
- - **Bound content moved out of byte custody** into the artifact row (`effect_artifacts.bytes`). Two
2204
- distinct rules, deliberately not one: a node **MUST** serve and stitch bound content only at the
2205
- token's current settled `inputsHash`, and it **MAY** drop superseded content whenever it likes
2206
- (nothing may read it, and it is re-creatable). The reference drops eagerly, on each bound
2207
- registration, so it holds at most `64 KB × minted × bound outputs` — but retention is a service
2208
- policy, not an obligation. Either way "conforming means holding a bounded amount of JSON in the
2209
- database you already run" is now literally true: a resolver in the publish topology needs no object
2210
- storage at all.
2211
- - **`abx-effects-publish/v1` is gone** (not deprecated): the two routes ride `abx-control-plane/v1`.
2212
- Once referenced output is locator-only, accepting a registration is a database insert, so the
2213
- capability flag described a distinction that no longer exists. A service that won't take a caller's
2214
- artifacts refuses on the credential (`403`) — interfaces describe wire grammar, tokens describe
2215
- permission. The interface ids are also explicitly **all-or-nothing**, and the conformance fixture
2216
- now checks that every route a declared interface names actually answers.
2217
- - **The runner declares bound outputs** (`EffectOutputDecl.bound`), refuses to start when it has a
2218
- publish token but a backend that can't name a locator, preflights the descriptor + credential
2219
- before spending a render, and **latches** on a permanent (4xx) publish failure instead of
2220
- re-rendering every sweep forever. Skips now re-register rows, so a transient publish failure heals
2221
- without a re-render.
2222
- - **The CLI refuses the impossible combination up front**: `abx render --remote`, `abx effects`
2223
- against a remote resolver, and `abx deploy-effects` all require a backend that can name a reachable
2224
- URL — `cloud` (S3/R2 + public base), `ipfs`, or `arweave`, named as **peers**. Derived output is
2225
- re-creatable, so the protocol has no preference among schemes: a chosen `https://` gateway or
2226
- bucket URL is exactly as legitimate as `ipfs://`/`ar://`, and reachability — not durability — is
2227
- the requirement. Rendering **co-located** with the resolver remains fully supported on any backend,
2228
- including `fs`.
2229
-
2230
- Breaking for producers that relied on pushing media bytes to a resolver: publish a locator instead,
2231
- or co-locate. Breaking for clients that read `abx-effects-publish/v1` from a descriptor.
2232
-
2233
- ## 0.1.0-alpha.4
2234
-
2235
- ### Minor Changes
2236
-
2237
- - 67b686b: `abx contracturi`, and the read plane stops answering a bare 404 to three different problems.
2238
-
2239
- Both halves come from one real failure: an agent driving a hosted resolver wanted collection
2240
- metadata, pattern-matched off `/t/{chainId}/{address}/{id}`, dropped the token id, got a bare `404`,
2241
- and reported the service as broken. The documented route (`/c/{chainId}/{address}`) was right there —
2242
- but there was also no command to just _ask_, and the 404 gave it nothing to correct.
2243
-
2244
- - **New `abx contracturi <address>`** — the collection-level counterpart of `tokenuri`. Reads
2245
- `contractURI()` (ERC-7572) from the contract, **follows it**, and decodes: a `data:` URI inline
2246
- (the on-chain lane), an `https://` URL by fetching it (the off-chain lane). A contract commits its
2247
- own metadata base on-chain (`contractURIBase`), so the chain — not a doc, not a service
2248
- descriptor — is the authoritative answer to where a project's metadata lives. Nobody needs to
2249
- hand-build a resolver URL. When the fetch fails, the message says so plainly: the URL came from
2250
- the chain, so a bad status is about the _service_ (unregistered project · wrong chain · down),
2251
- never a mistyped path.
2252
- - **Read-plane responses now carry a machine `code`**, so the three causes of "no metadata came
2253
- back" are distinguishable — they were one indistinguishable `{"error": "…"}` `404`:
2254
- - `400 invalid_request` — a real route, wrong shape. Names the correct template, and carries
2255
- `didYouMean` when the fix is obvious (a `/t/…` missing its token id → `/c/{chainId}/{address}`).
2256
- - `404 unknown_route` — this node serves nothing at that path; the body lists what it does serve.
2257
- - `404 not_registered` — the path and chain were fine; this node doesn't index that contract.
2258
- - `400 unsupported_chain` — wrong chain, plus the `chains` this node does serve. Was a bare `404`;
2259
- now matches what the control plane already answered for the same condition.
2260
- - `ServiceErrorCode` gains `unknown_route`. The spec's Errors section now covers the read plane too,
2261
- with a **MUST** on distinguishing the three misses — and an explicit **MUST NOT** on treating
2262
- route templates as per-node discoverable configuration. The route grammar is fixed by the
2263
- `abx-token-api/v1` interface; these responses are diagnostics, not a discovery mechanism.
2264
- - **The conformance fixture checks all of it** (`pnpm conformance <base-url>`), so any provider can
2265
- self-verify in one command. Also fixed: the documented `pnpm conformance -- <base-url>` form
2266
- parsed `--` as a flag and swallowed the base URL, printing usage instead of running.
2267
-
2268
- ## 0.1.0-alpha.3
2269
-
2270
- ### Minor Changes
2271
-
2272
- - a72723d: A standard indexing lifecycle, and registration that no longer blocks on a slow chain RPC
2273
- (specs/self-host-toolkit/remote-services.md → The indexing lifecycle).
2274
-
2275
- - **Fixed: a slow register triggered a retry storm.** The SDK's per-attempt timeout (30s) plus its
2276
- retry ladder meant a cold reconstruct that outran one request was **re-POSTed up to four times**,
2277
- each starting another full replay against the RPC that was already too slow to answer — and the
2278
- caller then saw "nothing responded" even though the registration was durable and indexing was
2279
- underway. A timed-out register now asks whether it landed (a status read) instead of re-POSTing, and
2280
- the resolver coalesces concurrent catch-ups for one project into a single run.
2281
- - **`POST /v1/projects` answers in two conformant shapes, discriminated by HTTP status:** `200` with
2282
- the completed summary, or `202` + `{accepted, project: {status}}` when catch-up is deferred. The
2283
- registration is normatively **durable before catch-up** and visible on the list immediately, so a
2284
- flaky RPC makes for a slower backfill rather than a lost add. No `?wait=`/`Prefer:` negotiation — the
2285
- status code is the discriminator, and clients handle both. The reference resolver answers _by
2286
- deadline_ (`ABX_REGISTER_DEADLINE_MS`, default 8s): the common case (a fresh deploy) stays
2287
- synchronous with real counts; only the pathological case defers.
2288
- - **Closed lifecycle enum + error classes, on the status and list routes:**
2289
- `queued | backfilling | live | stale | failed`, plus credential-free
2290
- `error.class ∈ {rpc_unavailable, rpc_rate_limited, not_abx_contract, internal}` (fixed per-class
2291
- messages, never a scrubbed upstream string). Status gains top-level `headBlock` (so lag / % complete
2292
- is computable without knowing a service has a watcher) and `attempts`; the list carries `status` +
2293
- the error class, so a client renders "3 live, 1 backfilling, 1 failed (rpc_rate_limited)" in one
2294
- request. SDK: `IndexStatus`, `IndexErrorClass`, `isAccepted()`, `indexProgress()`,
2295
- `classifyIndexError()`, and `AbxServiceClient.awaitIndexed()` — one wait loop for the CLI, the
2296
- effects runner, and any hosted agent.
2297
- - **The same five words on your own node.** `abx status [address] [--remote [name|url]] [--watch]`:
2298
- bare is the node summary (now with each project's state), an address gives lifecycle + scan floor +
2299
- blocks-indexed-vs-head + cause, and `--remote` asks a service. (`status` = who is serving it and how
2300
- fresh; `state` = what the chain says. Both `--help` texts now say so.)
2301
- - **New observability the self-hosted node never had:** the chain watcher marks projects `stale` when
2302
- it falls far behind head or its ticks keep failing (previously visible only in the node's log),
2303
- re-queues a backfill interrupted by a restart (previously left registered-but-empty until a manual
2304
- `abx index`), and retries a `failed` catch-up on exponential backoff instead of hammering a
2305
- rate-limited RPC every tick. Lifecycle rows live in their own table: they survive a projection wipe
2306
- and are never clobbered by a re-add.
2307
- - **CLI:** `abx add|index --remote` prints `registered — backfilling…`, polls to `live`, then prints
2308
- the same summary a synchronous service would have given; `--no-wait` returns at the 202 and names
2309
- the command to check later. A post-op nudge (`ownerops`) never blocks on someone else's backfill.
2310
- A caught-up project with **0 events** now warns instead of printing ✓ (a real ABX clone always emits
2311
- a spine, so zero means wrong chain/floor or an RPC that didn't serve the logs).
2312
- - **Conformance fixture** accepts either register shape, asserts durable-before-catch-up, lifecycle
2313
- membership, `headBlock`, that a deferred catch-up actually reaches `live`, and that no error message
2314
- carries a URL.
2315
- - Fixed `scripts/mock-remote-service.mts`, which imported the token API by a path that resolved
2316
- against `scripts/` and could silently fall back to a _published_ build outside the repo — the
2317
- fixture was testing the last release instead of the working tree. The fixture also re-points
2318
- scenarios by their fixture header now, so a new one can't keep a dead contract address.
2319
-
2320
- Found by a cold-agent sweep over the above (10 parallel clean rooms, haiku + sonnet) and fixed here:
2321
-
2322
- - **`abx status --remote <name>` with no address** parsed the flag itself as the address and sent it
2323
- as a URL path segment.
2324
- - **A register whose catch-up already failed** was announced as "registered — failed (…is catching
2325
- up…)", and with `--no-wait` it exited 0 and then claimed the provider "now serves" the project. A
2326
- known failure is now an error in both lanes — there is nothing left to wait for.
2327
- - **A `failed` status said what broke but not whose problem it was.** Both the failure error and
2328
- `abx status` now carry a per-class action line ("the SERVICE can't reach its chain RPC — not your
2329
- key, address, or chain…"), plus a `follow` line naming `--watch`, so a red word isn't a dead end.
2330
- - **A `live` project showed a misleading completion percentage.** `toBlock` only advances when a
2331
- project has _events_, so a fully current project on a busy chain read as `2/202 (0%)`.
2332
- `indexProgress()` now returns a ratio only while `backfilling`; `live` reads "caught up", `stale`
2333
- reads "not tracking head right now".
2334
- - **`--remote-token` was misattributed on a 401** — the error blamed `ABX_REMOTE_<NAME>_TOKEN` even
2335
- when the caller passed an override, making the override look ignored at exactly the moment someone
2336
- is testing a replacement key.
2337
- - **`not_registered` on a read** (status/reindex) now names the register command instead of echoing a
2338
- 404, and a 5xx carrying a failure `class` becomes a wait-vs-broken error.
2339
- - **`abx verify`'s summary** read `✓ 0/1 up to date` for a project with no off-chain renders at all —
2340
- "zero of one succeeded" to two independent reviewers. It now says "nothing to render for this
2341
- project", and otherwise leads with polarity ("N of M token(s) current").
2342
- - **`abx doctor` now reports named remotes** and flags a credential stored under a name the CLI does
2343
- not read (`ABX_REMOTE_<NAME>_KEY`). That fault presents as "it acts like I never gave it a key" and
2344
- previously only surfaced from `abx remote <name>` — which a creator reaches _after_ doctor.
2345
- - **Skill: the `npx --no-install abx version` probe was documented as failing cleanly.** It doesn't —
2346
- npm will run any `abx` binary already in the npx cache, which in a real sweep reported a months-old
2347
- build as the project's CLI (and if a plain `npx abx` ever ran on that machine, the bare name is a
2348
- squatted package). The skill now probes `./node_modules/.bin/abx` directly.
2349
- - Also documented: how a multi-word provider name folds into `ABX_REMOTE_<NAME>_*`, and what
2350
- `watching: no` means on a status readout.
2351
-
2352
- A second sweep round over those fixes caught three more, including one the first round's fix created:
2353
-
2354
- - **`abx verify --remote` never checked byte integrity at all** — both of its lanes only ask "is there
2355
- a current render / is this a placeholder", and a green ✓ from that was standing in for "the served
2356
- bytes match the on-chain commitment". A reviewer hit the worst version of this: `--remote` (the form
2357
- the skill tells you to use for a hosted project) reported ✓ on a token whose bytes genuinely did NOT
2358
- hash-match, while bare `abx verify` on the same project reported `✗ keccak256 MISMATCH`. It now calls
2359
- the service's own purpose-built `GET /api/project/:addr/verify` (which holds both the bytes and the
2360
- chain) and reports that verdict separately from the render summary — and when it _can't_ run that
2361
- check (no credential, older node) it says "byte integrity NOT checked" instead of leaving a ✓ to
2362
- imply it passed. The remedy names both real causes (an unbridged durable locator vs. bytes that only
2363
- exist on the creator's machine, which a hosted resolver can never serve).
2364
- - **`abx verify` exited 0 while printing a byte MISMATCH**, in both lanes — nothing could gate on it.
2365
- An integrity mismatch now fails the command; a missing render or placeholder is a normal state and
2366
- still exits 0.
2367
- - **`abx add --dry-run` silently ignored the flag and performed the registration**, local or remote.
2368
- It now refuses and names the read-only commands (`abx state`, `abx status`) instead. Silently doing
2369
- the thing when the caller asked to preview is the one outcome that must never happen.
2370
- - **`PRAGMA busy_timeout` was set third in the store schema**, after the WAL switch it needs to
2371
- protect — so two processes opening the same store at once (parallel CLI runs, or a co-located
2372
- effects runner starting alongside the resolver) could fail outright with `database is locked`
2373
- instead of waiting the moment out. It is now the first statement.
2374
-
2375
- A third round, re-running the scenario that found the verify bug (it now catches it) turned up:
2376
-
2377
- - **`abx add --remote` ended on "it now serves <url>"** — true about indexing, silent about whether
2378
- the bytes are right, and two reviewers stopped there and reported a blank page as fixed. It now names
2379
- the byte check (`abx verify <addr> --remote <name>`) in the same breath.
2380
- - **`canonical:` collapsed a tri-state.** `isCanonical` is `true | false | null`, and both readouts
2381
- printed "unverified" for the last two — so "the chain says this is NOT a clone of the configured
2382
- factory" (a trust finding) looked identical to "the check never ran" (no factory on this chain, normal
2383
- on a dev chain). Two reviewers read the collapsed word as a second failure sitting next to a real one.
2384
- - **`abx verify --remote` gave a bare `fetch failed`** for an endpoint that was down, where
2385
- `abx status --remote` names the host and asks whether it's running. Two commands, one condition, two
2386
- error qualities — now consistent.
2387
- - `abx status <addr>` printed the address twice when the project has no name.
2388
- - Skill: registering with a provider on **their** hostname vs. a domain you control decides whether
2389
- leaving later costs a transaction — now stated in the managed-provider section, before you bake it.
2390
-
2391
- ## 0.1.0-alpha.2
2392
-
2393
- ### Minor Changes
2394
-
2395
- - 3745bd3: Remote services are first-class: a provider-neutral control plane, named remotes, and a service
2396
- descriptor (specs/self-host-toolkit/remote-services.md).
2397
-
2398
- - **Control plane moves to `/v1`** (hard cutover; `/admin/*` is gone — redeploy self-hosted nodes):
2399
- `POST/GET /v1/projects`, `DELETE|reindex|status /v1/projects/{chainId}/{address}`,
2400
- `POST /v1/effect-artifacts|effect-status`. `chainId` is explicit and validated everywhere; every
2401
- error carries a machine `code` (`unauthorized` 401 · `forbidden` 403 · `unsupported_chain` ·
2402
- `not_registered` · `disabled`) replacing the old prose-sniffed 404. One bearer guard replaces the
2403
- four inline copies; OPTIONS preflight now answers so browser clients can send `Authorization`.
2404
- - **`GET /.well-known/abx-service`** — the public service descriptor: `interfaces` (present iff
2405
- actually enabled), `chains`, `auth` (with optional provider-set `signupUrl`/`docsUrl` via
2406
- `ABX_SERVICE_*` env), and `render.attached` (managed rendering, probed from the runner's
2407
- `/health`) — so an agent can match a project to a provider before registering.
2408
- - **Named remotes in the CLI**: `--remote <name>` reads `ABX_REMOTE_<NAME>_URL`/`_TOKEN`
2409
- (a managed provider's per-account key — never falls back to `ABX_RESOLVER_ADMIN_TOKEN`);
2410
- `--remote <url> [--remote-token <t>]` for ad-hoc targets; bare `--remote` stays the self-host
2411
- default. New `abx remote [name|url]` inspects a service's descriptor and the projects a token
2412
- sees. `migrate --from/--to` accept names; only the destination needs a credential.
2413
- - **The SDK gains its first HTTP surface**: `AbxServiceClient` (endpoint + injected bearer, retry
2414
- on 5xx/network, immediate typed `AbxServiceError` on 4xx) — shared by the CLI and the effects
2415
- runner's publish lane. `envSuffix()` is the shared env-name normalization.
2416
- - **Conformance fixture**: `pnpm conformance -- <base-url> [--token …]` self-verifies any
2417
- implementation; the e2e suite runs it against the reference container.
2418
-
2419
- ### Patch Changes
2420
-
2421
- - 3745bd3: Membrane fixes found by a 20-run cold-agent regression sweep (sonnet + haiku, black-box clean rooms).
2422
-
2423
- - **A 500 no longer leaks the node's own credentials.** An upstream RPC failure surfaced viem's
2424
- message, which embeds the endpoint URL — and a keyed RPC URL _is_ a credential, so on a
2425
- multi-tenant provider any tenant who could provoke a 500 got the operator's RPC key. The cause now
2426
- goes to the node's log; the wire gets a generic message, an `internal_error` code, and a
2427
- credential-free hint about the failure class. Normative in the remote-services spec.
2428
- - **The service client no longer discards a 5xx body.** The service's own words survive the retry
2429
- ladder, and an exhausted ladder says "failed — last response …" rather than mislabelling a
2430
- server that answered as "unreachable". The descriptor probe drops to 2 attempts, so a typo'd
2431
- provider URL fails in ~1s instead of grinding 5s, with distinct "nothing responded" vs
2432
- "answered, but serves no descriptor" messages.
2433
- - **Conflicting duplicate `.env` keys are reported.** First-wins is unchanged, but a stale second
2434
- `ABX_RPC_URLS_<CHAIN>` line silently pointed the CLI at another network while every check read
2435
- green — the symptom surfaced far away as "no contract at that address". Only genuinely
2436
- _conflicting_ duplicates warn (identical repeats stay quiet).
2437
- - **"No contract at …" errors now name the endpoint they asked** (redacted), because a chain key
2438
- can't distinguish two RPCs that both claim it.
2439
- - **A misnamed remote credential is called out.** `ABX_REMOTE_<NAME>_KEY` (or `_API_KEY`, `_SECRET`)
2440
- is not read, so it previously reported as "no token" while the value sat in `.env`; both
2441
- `abx remote` and the register path now name the near-miss and the correct `_TOKEN` name.
2442
- - **`--dry-run` explains a missing trust anchor instead of crashing.** On a chain where the
2443
- configured factory has no code, `deploy`/`deploy-series` previews died inside
2444
- `predictDeterministicAddress` with a raw `returned no data ("0x")`; they now report it the way
2445
- `abx predict` and a real deploy already did, and name the two ways forward. The keyless
2446
- `--for` requirement also fails fast instead of after several steps of output.
2447
- - **The placeholder-identity guard is one shared predicate** across all three deploy commands
2448
- (it was copy-pasted, and one copy's comment claimed coverage it didn't have), pinned by a new
2449
- regression test: a real send refuses tool defaults, a preview only warns.
2450
- - **The served dashboard's empty state no longer prints `pnpm abx demo`** — a contributor-only
2451
- invocation on a page a published user sees.
2452
-
2453
- ## 0.1.0-alpha.1
2454
-
2455
- ### Patch Changes
7
+ - 64248d9: Establish the public source baseline and refresh dependency constraints.
2456
8
 
2457
- - 4074766: Fix the dashboard's block-explorer links, which were hardcoded to `https://sepolia.etherscan.io`. Every
2458
- link on the page — contract, owner, implementation, each event's tx — pointed at Ethereum Sepolia no
2459
- matter which chain was being served, so a dashboard for a normal `abx demo` (Base Sepolia by default)
2460
- sent you to an explorer where the contract does not exist. The SDK now derives the explorer from viem's
2461
- own chain metadata (`explorerUrl`/`chainById`), so adding a chain brings its explorer along and no
2462
- hand-maintained table can drift. The CLI's separate copy of that table is collapsed into the same
2463
- helper; `signer.ts` was already doing it correctly.
9
+ ## Unreleased
2464
10
 
2465
- Drop the demo's opening "trust anchor" step. It asserted that only the canonical factory can make a
2466
- token that _is_ an ABX token, which is false — anything following the protocol's event spine is an ABX
2467
- token, and the factory is one route to that, not the definition. The same overclaim in the index step
2468
- ("verified real") now reports the fact instead: made by the canonical factory, or not. The demo opens
2469
- on the renderer step, and resolving the factory no longer prints a line of its own there.
11
+ Public release history begins here.