@artblocks/abx-cli 0.1.0-alpha.39 → 0.1.0-alpha.40

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 (63) hide show
  1. package/CHANGELOG.md +9 -4079
  2. package/dist/bin.d.ts +1 -2
  3. package/dist/bin.d.ts.map +1 -1
  4. package/dist/bin.js +1 -2
  5. package/dist/bin.js.map +1 -1
  6. package/dist/commands/deploy.d.ts.map +1 -1
  7. package/dist/commands/deploy.js +40 -45
  8. package/dist/commands/deploy.js.map +1 -1
  9. package/dist/commands/project.d.ts.map +1 -1
  10. package/dist/commands/project.js +16 -22
  11. package/dist/commands/project.js.map +1 -1
  12. package/dist/commands/reads.d.ts +1 -1
  13. package/dist/commands/reads.js +4 -4
  14. package/dist/commands/reads.js.map +1 -1
  15. package/dist/commands/scaffold.d.ts.map +1 -1
  16. package/dist/commands/scaffold.js +8 -16
  17. package/dist/commands/scaffold.js.map +1 -1
  18. package/dist/commands/service.d.ts.map +1 -1
  19. package/dist/commands/service.js +10 -16
  20. package/dist/commands/service.js.map +1 -1
  21. package/dist/commands/storage.js +1 -1
  22. package/dist/commands/storage.js.map +1 -1
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +2 -3
  25. package/dist/config.js.map +1 -1
  26. package/dist/conformance.d.ts.map +1 -1
  27. package/dist/conformance.js +7 -10
  28. package/dist/conformance.js.map +1 -1
  29. package/dist/errors.d.ts +2 -2
  30. package/dist/errors.js +2 -2
  31. package/dist/flag-allowlists.d.ts +1 -1
  32. package/dist/flag-allowlists.js +1 -1
  33. package/dist/flags.js +1 -1
  34. package/dist/flags.js.map +1 -1
  35. package/dist/main.js +6 -8
  36. package/dist/main.js.map +1 -1
  37. package/dist/output.d.ts +1 -1
  38. package/dist/output.d.ts.map +1 -1
  39. package/dist/output.js +4 -5
  40. package/dist/output.js.map +1 -1
  41. package/dist/ownerops.d.ts +4 -7
  42. package/dist/ownerops.d.ts.map +1 -1
  43. package/dist/ownerops.js +13 -19
  44. package/dist/ownerops.js.map +1 -1
  45. package/dist/provision.d.ts +3 -13
  46. package/dist/provision.d.ts.map +1 -1
  47. package/dist/provision.js +3 -13
  48. package/dist/provision.js.map +1 -1
  49. package/dist/remote.d.ts +1 -1
  50. package/dist/remote.js +3 -3
  51. package/dist/remote.js.map +1 -1
  52. package/dist/riskgate.d.ts +1 -1
  53. package/dist/riskgate.d.ts.map +1 -1
  54. package/dist/riskgate.js +3 -4
  55. package/dist/riskgate.js.map +1 -1
  56. package/dist/served.js +3 -3
  57. package/dist/update-check.d.ts +1 -4
  58. package/dist/update-check.d.ts.map +1 -1
  59. package/dist/update-check.js +1 -4
  60. package/dist/update-check.js.map +1 -1
  61. package/package.json +10 -10
  62. package/skill/SKILL.md +1 -1
  63. package/skill/reference/services.md +3 -5
package/CHANGELOG.md CHANGED
@@ -1,4086 +1,16 @@
1
1
  # @artblocks/abx-cli
2
2
 
3
- ## 0.1.0-alpha.39
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
- ### Patch Changes
42
-
43
- - Updated dependencies [c198237]
44
- - @artblocks/abx-sdk@0.1.0-alpha.29
45
- - @artblocks/abx-indexer@0.1.0-alpha.30
46
- - @artblocks/abx-storage@0.1.0-alpha.29
47
- - @artblocks/abx-token-api@0.1.0-alpha.32
48
-
49
- ## 0.1.0-alpha.38
50
-
51
- ### Patch Changes
52
-
53
- - d4b1de8: Fixes from the 2026-08-27 w7 agent sandbox sweep — a co-located `abx effects` bug that silently
54
- defeated auto-rendering, and four help-text truthfulness gaps.
55
-
56
- - **`abx effects --resolver-port <n>`** — the "no manual `abx render`" pipeline could silently never
57
- render for two compounding reasons, both hit live on Sepolia in the sweep (real deploy, real mint,
58
- two confirmed `configure-param` txs, zero renders landed): co-located mode had no way to say which
59
- port the local `abx serve` was actually on (it assumed the default, 8787), and a leftover
60
- `ABX_REMOTE_SELF_TOKEN` — auto-saved to `.env` by any earlier `deploy-resolver`/`deploy-effects` run
61
- in the same project — silently flipped a later, unrelated `abx effects` into remote/control-plane
62
- mode against a bare `abx serve` that never implements it, producing a 404 whose own suggested fix
63
- ("check the token") was a dead end. `--resolver-port` fixes the first outright and, by declaring
64
- co-location explicitly, ignores the stale token instead of being ambushed by it. When the same
65
- combination shows up _without_ the flag (an existing loopback `ABX_RESOLVER_URL` + a stale token),
66
- `abx effects` now warns up front with the actual fix, rather than failing silently at the first
67
- publish attempt. The decision logic is split into `resolveEffectsTarget()` (`commands/service.ts`),
68
- unit-tested directly (`test/effects-target.test.ts`) without needing a chain client or HTTP server.
69
- - **`--dry-run`'s "no signer known" hint no longer suggests a flag most commands don't have.** It used
70
- to say `pass --for 0x.. (or set a signing key)` unconditionally — but `--for` is only accepted by
71
- `deploy`/`predict`/`doctor`/`storage balance`; every owner-op write (`configure-param`, `set-field`,
72
- `lock-field`, …) funnels through the same shared `gatedSend`/`simulateDryRun` choke point and doesn't
73
- accept it. The hint now leads with what's universally true (set a signing key, or `--sign`) and only
74
- mentions `--for` as a caveat for the commands that take it.
75
- - **`abx help lock-field` / `abx help lock-uri` now list `--dry-run`.** Both flags already worked
76
- (confirmed on a live Sepolia contract in the sweep); neither `--help` output said so, so the exact
77
- preview a creator most wants before an irreversible freeze was undiscoverable without already
78
- knowing to try it.
79
- - **`abx help set-field` / `abx help attach` now state that every write pings ERC-4906** — the on-chain
80
- behavior (`MetadataUpdate(tokenId)` per token-scope write, `BatchMetadataUpdate` for `--collection`)
81
- was already correct and load-bearing (marketplaces rely on it to self-refresh after a metadata edit),
82
- but the only place ERC-4906 was named anywhere in the discoverable surface was a side remark in
83
- `abx help ping-uri` about a different, edition-only command.
84
-
85
- Also corrects this repo's own `dev-loop-test` skill, which claimed `.env` duplicate keys are
86
- last-wins — the sweep proved the real CLI is first-wins (confirmed live: a duplicate
87
- `ABX_RPC_URLS_SEPOLIA` silently routed every read to the wrong RPC, ignoring an intentionally later
88
- override).
89
-
90
- ## 0.1.0-alpha.37
91
-
92
- ### Patch Changes
93
-
94
- - c5e0d84: The skill and CLI now name the extension and setup paths agents were missing: a shared on-chain
95
- program with a per-token `Bytes` payload, `abx set-param-hooks`, and Foundry's stricter private-key
96
- prefix requirement.
97
-
98
- The routing table in the bundled skill, `reference/code.md`, and `abx capabilities` all list
99
- `deploy-code --script` + `<key>:Bytes:Creator` as a native lane — not an escape hatch — so an agent
100
- does not conclude the shape is out of scope and hand-roll Solidity. They now say truthfully that the
101
- payload is configured after mint with `configure-param --file`; `mint` has no payload flag. Top-level
102
- `abx help` lists `set-param-hooks` (the only command that wires a custom hook), and the `--schema`
103
- description in `deploy-code --help` points at it for mint-time snapshot / veto / live derivation. Setup guidance
104
- also distinguishes the CLI's optional `0x` private-key prefix from Foundry `vm.envUint`'s required one.
105
-
106
- Reported in the 2026-08-27 tester batch (feedback 599722e5, d12929f5, ff28d128, 3d4eda3f).
107
-
108
- - c5e0d84: Published package `homepage` and `bugs` fields now point at docs.abx.io instead of a private GitHub
109
- repo that 404s. File reports with `abx feedback`; `npm bugs` no longer names a repository the public
110
- cannot open.
111
-
112
- Reported in the 2026-08-27 tester batch (feedback 40142396).
113
-
114
- - c5e0d84: Read commands no longer collapse "the RPC is down" into "this isn't an ABX contract."
115
-
116
- `abx state` now refuses an ignored singular `ABX_RPC_URL`, probes chain reachability, and retains the
117
- errors from its required getter reads. A dead endpoint, a node that answers block/code queries but
118
- rate-limits `eth_call`, a codeless address, and code without ABX getters now receive distinct answers;
119
- an RPC failure is never stated as a contract verdict. `abx add`'s deploy-block discovery no longer
120
- guesses "a non-archive RPC?" when the same node `abx doctor` just certified — the failure can also be
121
- rate-limiting or a refused historical `getCode`. The `deploy-code` Surfaces block still flags an
122
- orphaned collection-wide thumbnail, and now names the per-token `set-field --field image` hatch (raw
123
- SVG bytes, not a data-URI) instead of claiming you cannot backfill at all. `submit-app` now confirms
124
- only the on-chain update and tells users to verify hosted catalog copy separately instead of promising
125
- an unverified refresh on the provider's "next read."
126
-
127
- Reported in the 2026-08-27 tester batch (feedback ae7cc4a8, bd75dc5f, 89315be8, 1f3c0992, c2101d6f).
128
-
129
- - Updated dependencies [c5e0d84]
130
- - Updated dependencies [c5e0d84]
131
- - @artblocks/abx-sdk@0.1.0-alpha.28
132
- - @artblocks/abx-indexer@0.1.0-alpha.29
133
- - @artblocks/abx-storage@0.1.0-alpha.28
134
- - @artblocks/abx-token-api@0.1.0-alpha.31
135
-
136
- ## 0.1.0-alpha.36
137
-
138
- ### Minor Changes
139
-
140
- - baa7d17: Add `abx auth logout` for first-party and named OAuth remotes. The command discovers the provider's
141
- RFC 7009 revocation endpoint, revokes the current API key, and only then removes the matching local
142
- `.env` assignment; provider failures keep the credential available for a safe retry. The bundled
143
- skill and public docs also teach agents to yield immediately after device-login handoff output and
144
- resume the same polling process instead of starting duplicate login or retry loops. Normal key
145
- rotation now points through logout so `login --force` does not silently orphan an active key.
146
-
147
- ## 0.1.0-alpha.35
148
-
149
- ### Minor Changes
150
-
151
- - 4072f0c: Add `abx auth login`, an OAuth 2.0 Device Authorization Grant flow for first-party and named remote
152
- services. Human email/OTP approval stays in the browser; the CLI polls according to RFC 8628 and
153
- writes the resulting bearer key to a Git-ignored `.env` without printing it. The bundled ABX skill
154
- now recommends this agent-safe flow and keeps manual signup only as a recovery path. The SDK's
155
- service-descriptor documentation likewise distinguishes provider recovery URLs from standard OAuth
156
- discovery.
157
-
158
- ### Patch Changes
159
-
160
- - 4072f0c: `deploy-code --dry-run` now treats a resolver-declared attached renderer as the owner of rendered stills and traits, without contradicting that managed path with local runner, backend, or `fs` warnings.
161
-
162
- The managed lane now prints one truthful verification path instead of asking creators to stand up infrastructure the provider already supplies. Reported during the 2026-08-26 fresh onboarding test (feedback `5b7c3f10-e08f-4fb8-85de-ec509f4f33f6`).
163
-
164
- - Updated dependencies [4072f0c]
165
- - @artblocks/abx-sdk@0.1.0-alpha.27
166
- - @artblocks/abx-indexer@0.1.0-alpha.28
167
- - @artblocks/abx-storage@0.1.0-alpha.27
168
- - @artblocks/abx-token-api@0.1.0-alpha.30
169
-
170
- ## 0.1.0-alpha.34
171
-
172
- ### Minor Changes
173
-
174
- - b182f23: Drop-in for existing SDK and CLI consumers; adds optional first-party service and feedback surfaces.
175
-
176
- Add the built-in `abx` remote at `https://services.abx.io`, authenticated by an email-verified free
177
- `ABX_SERVICES_API_KEY`, so projects no longer need to copy a service URL or signup handoff into agent
178
- instructions. The current key includes hosted resolver and managed-rendering access; the docs state
179
- that hosted infrastructure may become paid and preserve the provider-neutral/self-hosted exit route.
180
-
181
- Add `abx feedback`: core protocol/contract/CLI/SDK/skill/docs reports go to the ABX team by default,
182
- while `--remote <provider>` uses the provider-standard `abx-service-feedback/v1` interface. Reports
183
- are structured, preview-only until the human re-runs with `--yes`, and queryable with `--mine`. The
184
- SDK exports the interface id, feedback types, and discovery/submit/history client methods.
185
- CLI help enumerates every closed feedback field so an agent can construct a valid report without a
186
- failed probe.
187
-
188
- Move the public documentation links to `docs.abx.io` and teach the shipped skill the verified signup,
189
- credential boundary, two feedback targets, explicit consent gate, and current hosted-service terms.
190
-
191
- ### Patch Changes
192
-
193
- - Updated dependencies [b182f23]
194
- - @artblocks/abx-sdk@0.1.0-alpha.26
195
- - @artblocks/abx-indexer@0.1.0-alpha.27
196
- - @artblocks/abx-storage@0.1.0-alpha.26
197
- - @artblocks/abx-token-api@0.1.0-alpha.29
198
-
199
- ## 0.1.0-alpha.33
200
-
201
- ### Minor Changes
202
-
203
- - 8f2f78a: Rename and globally refactor the shipped agent skill from `abx-self-host` to `abx` now that managed
204
- remote hosting, no-host on-chain projects, deployment, and owner operations are all first-class
205
- lanes. The installer archives recognized legacy skill copies outside the discovery tree before
206
- installing the new name, preserving user customizations while preventing duplicate triggers. The
207
- runtime `.abx-self-host/` data directory is unchanged. Scoped install help names the automatic
208
- legacy archive behavior, and `doctor` prints every legacy copy path before recommending migration.
209
-
210
- Replace the accumulated custody-first guide with a surfaces-first lifecycle covering collection
211
- shape, runtime, metadata/image/animation/traits, custody, resolution, rendering, authority,
212
- mutability, confirmation, verification, and state-driven recovery. The always-loaded router and
213
- references are substantially smaller, contradictions are removed, and integrity tests now enforce
214
- Agent Skills metadata limits, router size, reference navigation, known stale claims, and Codex UI
215
- metadata.
216
-
217
- Add `abx capabilities [--json]`, a typed product-boundary contract for native deployment lanes,
218
- canonical extension seams, irreversible choices, and unsupported cases. CLI help and tests consume
219
- the same contract, including the current EditionCode renderer/dependency support and static-edition
220
- on-chain-image support. Root help now exposes `ABX_CHAIN`, and param-hook help matches the actual
221
- SeriesCode/EditionCode contract surface.
222
-
223
- Redact configured credential-bearing RPC URLs at the CLI's top-level error boundary so upstream
224
- provider errors cannot echo API keys into terminal or agent transcripts.
225
-
226
- ### Patch Changes
227
-
228
- - Updated dependencies [8f2f78a]
229
- - @artblocks/abx-sdk@0.1.0-alpha.25
230
- - @artblocks/abx-indexer@0.1.0-alpha.26
231
- - @artblocks/abx-storage@0.1.0-alpha.25
232
- - @artblocks/abx-token-api@0.1.0-alpha.28
233
-
234
- ## 0.1.0-alpha.32
235
-
236
- ### Patch Changes
237
-
238
- - 61503c3: On-chain content: measure the RPC instead of guessing, and refuse no size.
239
-
240
- `deploy --onchain-image`, `deploy-series --onchain-image` and `set-field --file` refused content past
241
- **100 KB per token**. That threshold was reasoned from geth's 50M `--rpc.gascap` default minus a
242
- margin, on the stated theory that "hosted providers commonly cap well below geth's default".
243
-
244
- Measured 2026-08-24, with a state-override `eth_call` that reports the gas a node actually provisions:
245
-
246
- | Endpoint | `eth_call` cap | Content it serves |
247
- | ------------------------------------------------------------------ | --------------- | ----------------- |
248
- | **`sepolia.base.org`** — the default endpoint of our default chain | **600,000,000** | **~729 KB** |
249
- | `base-sepolia.publicnode.com` · `base-sepolia.drpc.org` | 50,000,000 | ~117 KB |
250
- | `ethereum-sepolia.publicnode.com` | 50,000,000 | ~117 KB |
251
-
252
- Providers do not commonly cap lower — **50M is the floor**, and the chain we ship as the default serves
253
- twelve times that. So the refusal sat _below every endpoint that could be found_: it blocked content all
254
- of them could read, and was 7x too strict on the default chain. A creator who wanted a 312 KB image
255
- permanently on-chain was told no by a number about somebody else's infrastructure.
256
-
257
- **There is now no refusal at any size**, and `--allow-unreadable-onchain` is gone with it (it existed
258
- only to skip the refusal). In its place the toolkit states both costs and measures the one that varies:
259
-
260
- - **Write** is chunked into 22,000-byte SSTORE2 transactions, so no block gas limit binds it at any
261
- size. It is purely money at ~200 gas/byte, and the CLI now says so instead of implying a ceiling.
262
- - **Read** is one `eth_call`, and whether it succeeds belongs to whoever's endpoint is asking. Below
263
- ~117 KB (~50M gas) every endpoint measured serves it, and the CLI prints a plain note. Above that it
264
- probes the RPC you are actually on — no transaction, no key, one call — and reports its real
265
- allowance beside the estimate.
266
-
267
- ```
268
- ⚠ big.png is 312KB on-chain — reading tokenURI costs ~172M gas, which is past the ~50M gas floor
269
- that every endpoint serves.
270
- Your RPC allows ~600M gas (sepolia.base.org), so it reads for you — up to ~729KB. A 50M-capped
271
- provider stops at ~117KB, and marketplaces or indexers on one will see a REVERT rather than your
272
- token. Writing is unaffected: ~64M gas, chunked, no block-limit issue. This is the
273
- permanence-vs-reach trade — --backend arweave is the other side of it.
274
- ```
275
-
276
- The distinction the output keeps making, because it is the one that matters: **your RPC is not the
277
- marketplace's RPC.** A measured cap proves _you_ can read the token, never that OpenSea can. Past even
278
- your own endpoint's cap the CLI says that plainly — and still does not stop you, because bytes someone
279
- wants permanent are their call, not ours.
280
-
281
- New in the SDK: `probeEthCallGasCap` / `probeBestEthCallGasCap` (in `probe.ts`, alongside the existing
282
- getLogs capability probe that already argued this exact principle — measure the endpoint, don't trust a
283
- table), `ETH_CALL_GAS_FLOOR`, and `readableBytesAtGas`. `classifyOnchainReadSize(bytesLen, capGas?)`
284
- now returns `'ok' | 'endpoint-dependent' | 'beyond-local-rpc'` — none of them a refusal — and
285
- `ONCHAIN_READ_REFUSE_BYTES` / `ONCHAIN_READ_GETH_CAP_BYTES` are removed.
286
-
287
- An endpoint that rejects state overrides yields "unknown", which degrades to the conservative floor and
288
- is tested to never throw and never block.
289
-
290
- ## Follow-up audit: the other hardcoded third-party assumptions
291
-
292
- **Three different limits were being called "the gas limit", and they differ by orders of magnitude on
293
- the same endpoint.** Measured 2026-08-24:
294
-
295
- | | `eth_call` cap | `eth_estimateGas` cap | block `gasLimit` |
296
- | --------------------------------- | ------------------------ | --------------------- | ------------------------ |
297
- | Base Sepolia — `sepolia.base.org` | 600,000,000 | 16,777,216 | 1,200,000,000 |
298
- | Base Sepolia — publicnode · drpc | 50,000,000 | 16,777,216 | 1,200,000,000 |
299
- | Sepolia — publicnode | 50,000,000–2,000,000,000 | 16,777,216 | 60,000,000 |
300
- | Base mainnet · Ethereum mainnet | — | — | 400,000,000 · 60,000,000 |
301
-
302
- - **"L1 block limits sit in the mid-30-millions"** (SDK, docs) was stale in the direction that makes
303
- on-chain look worse than it is. It is 60M on Ethereum and Sepolia, 400M on Base, and **1,200M on Base
304
- Sepolia** — so the claim that _"past ~90 KB no contract can read the token inside a transaction"_ was
305
- off by more than 12x on our own default chain, where the real figure is ~1,100 KB. Removed everywhere.
306
- - **`DEFAULT_TX_GAS_BUDGET = 8M` was right for the wrong reason, and the wrong reason was dangerous.**
307
- It said "comfortably under mainnet's ~30M block limit". The limit that actually binds is
308
- `eth_estimateGas`, capped at **16,777,216 (2²⁴)** on every endpoint tried — every send estimates
309
- first, so a batch planned above that fails before anything is sent. Anyone correcting the stale block
310
- figure would have raised the budget and broken every deploy. The constant is unchanged; its rationale
311
- now names the limit that holds it, and says not to raise it toward a block limit.
312
- - **A measured cap is point-in-time, not a property.** Sepolia's publicnode endpoint measured 50M one
313
- hour and 2,000M the next — pooled endpoints rotate between backends with different configs. The probe
314
- and the output now say so.
315
- - **The probe could not tell "capped at exactly my ask" from "not capped at all".** A node that returns
316
- the full 2,000,000,000 it was asked for did not clamp; reporting that as a 2,000M cap invents a limit
317
- out of our own request. It now returns `Infinity` and the CLI prints "no eth_call cap at all".
318
-
319
- ## And the reframe that makes the whole question smaller
320
-
321
- A large read is a **serving** problem, not a preservation one — and the resolver already solves it.
322
- `resolveFieldBytes` reads `inline` / `inline-gzip` / `reader` / `reader-gzip` with the resolver's own
323
- RPC and serves the result as ordinary HTTP, so a marketplace fetches a URL and never makes the large
324
- `eth_call`. `abx set-renderer <addr> --off` points `tokenURI` at it with every byte left where it is.
325
-
326
- Every warning now says this, because it changes the decision: **storage and serving are separate.**
327
- On-chain is the strongest storage answer available at any size; self-resolving-versus-a-resolver is a
328
- serving choice the creator can change later. A token whose read is too large for some endpoint is not a
329
- lost token — it is one that wants a reader in front of it.
330
-
331
- And the framing that replaces the old false precision: **we can measure your RPC; we cannot know a
332
- marketplace's or an indexer's**, and theirs are the ones that decide whether the token displays.
333
-
334
- - 61503c3: Skill: stop agents declining capabilities ABX has, and teach the four extension seams up front.
335
-
336
- Three field reports in one week share a shape — an agent told a creator "ABX can't do that" about
337
- something ABX does:
338
-
339
- - a **77 KB PNG fully on-chain** was called "not practical" and the creator was routed to another
340
- protocol's on-chain filesystem. It ships today: the CLI warns once, ~16M gas to write and ~31M to
341
- read. 100 KB is the refusal; 40 KB is only where reads stop being comfortable.
342
- - a **transfer-restriction + mutable-state** mechanic was declared impossible, then built in an
343
- afternoon once the creator said the word "hooks".
344
- - a bespoke **non-canonical token contract** was written for a mechanic two stock hooks cover,
345
- permanently forfeiting the `isAbxClone` provenance that is the reason to deploy here at all.
346
-
347
- None of those agents lacked information. Each answered from the **command list**, and the command list
348
- is a set of recommended routes, not the edge of the protocol — so falling off the end of a routing
349
- table read as a "no". Another warning paragraph would not have helped; the skill already had one.
350
-
351
- What changed, structurally:
352
-
353
- - **A new `reference/capabilities.md`** — the inverse of the decision tree. It answers "can ABX do X?"
354
- instead of "what do I run?": the four seams, the three hooks in depth, a mechanic→seam lookup table,
355
- the on-chain size bands as _costs_ rather than walls, and a short, bounded **genuine no-list** — which
356
- is what makes "don't say no" safe to follow.
357
- - **A gate on the utterance, in Read first.** There is no CLI choke point for a wrong answer given in
358
- chat, so the enforcement analogue is a rule that fires on the words themselves: before _not possible_,
359
- _not practical_, _you'd need a different_, or the name of another protocol, check the seams and the
360
- no-list. If it is on neither, it is possible — answer with the cost. And never route a creator to
361
- another protocol for something ABX does.
362
- - **The three hooks are taught as power, not just as liability.** Every prior mention sat in the
363
- lock/disclosure register ("a standing power over whether a collector can sell"), and the **augment
364
- hook did not appear in SKILL.md at all**. The honest claim is now stated where it is read: ABX runs
365
- your arbitrary Solidity at write time (`--configure`, vetoes the write), transfer time (`--transfer`,
366
- vetoes the move, sees mints and burns) and read time (`--augment`, adds and overrides `tokenData` keys
367
- from anything a `view` can reach, for no gas on the token and no ~24 KB blob ceiling).
368
- - **The deploy-time precondition is surfaced before the deploy.** Hooks exist only on
369
- `SeriesCode`/`EditionCode`, the contract type is fixed forever, and that fact previously lived in one
370
- parenthetical deep in a reference file. A creator who hints at _any_ rule should be deployed with
371
- `deploy-code` even when the art is a static image — this is the mechanism that corners an agent into
372
- writing a custom contract later.
373
- - **"What this toolkit does NOT do" was itself teaching false impossibility.** It listed auctions and
374
- allowlists next to mainnet. Now split: a genuine no-list, and a "not a flag, but here is the seam"
375
- list. Auctions, allowlists, raffles, ERC-20 pricing, soulbinding, vesting, escrow, redemption and
376
- oracle-fed metadata are all seams.
377
- - **The skill `description` now routes capability questions here.** It listed only deploy/operate verbs,
378
- so "can I make it soulbound?" or "can I run an auction?" never loaded the skill — the outermost
379
- membrane was the first place the answer went wrong.
380
- - **Size guidance reframed as a cost band** in all three places an agent meets it, with the 40–100 KB
381
- band named as _warns once and ships_.
382
-
383
- Also fixed, found while checking: **five dead links in `reference/decisions.md`** (sibling references
384
- written as `reference/hosting.md` from inside `reference/`, resolving to `reference/reference/…`), and a
385
- link from `reference/code-projects.md` into `docs/research/` that is dead for every installed user,
386
- since `abx skill install` copies only the skill directory. A new `skill-integrity.test.ts` pins both —
387
- every reference file is reachable from SKILL.md, and every relative link resolves.
388
-
389
- - Updated dependencies [61503c3]
390
- - @artblocks/abx-sdk@0.1.0-alpha.24
391
- - @artblocks/abx-indexer@0.1.0-alpha.25
392
- - @artblocks/abx-storage@0.1.0-alpha.24
393
- - @artblocks/abx-token-api@0.1.0-alpha.27
394
-
395
- ## 0.1.0-alpha.31
396
-
397
- ### Patch Changes
398
-
399
- - bd77ba2: Correct the Node floor to 22.13, and refuse to start with a remedy instead of a stack trace.
400
-
401
- Every package declared `engines.node: ">=22.5.0"` — the version `node:sqlite` was **added**, not the
402
- version it became usable. It shipped in v22.5.0 behind `--experimental-sqlite` and lost the flag in
403
- v22.13.0 / v23.4.0. So `npm i -g @artblocks/abx-cli` on Node 22.5 installed silently (the engine
404
- range was satisfied) and then `abx doctor` — the first command we tell everyone to run — died with a
405
- raw `ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:sqlite` stack trace naming neither
406
- abx nor a fix. Reported 2026-08-24; the floor is now `>=22.13.0` everywhere, so npm warns at install
407
- time.
408
-
409
- The crash happened while ESM _linked_ `main.ts`'s import graph (`output.ts` →
410
- `@artblocks/abx-indexer` → `node:sqlite`), before any of our code ran, so no guard inside the CLI
411
- could have caught it. The published binary is now a deliberately import-free shim (`dist/bin.js`)
412
- that probes for `node:sqlite` and hands off to `main.js` — it prints the running version, the real
413
- floor, and the upgrade command. It probes the **capability**, not the version number: an old Node
414
- launched with `NODE_OPTIONS=--experimental-sqlite` genuinely works, and refusing that would be a
415
- false negative. `engines` states what we support; the guard blocks only what is actually broken.
416
-
417
- `abx doctor`'s header now also reports the running Node, since its output is what people paste when
418
- something is wrong.
419
-
420
- - Updated dependencies [bd77ba2]
421
- - @artblocks/abx-indexer@0.1.0-alpha.24
422
- - @artblocks/abx-token-api@0.1.0-alpha.26
423
- - @artblocks/abx-storage@0.1.0-alpha.23
424
- - @artblocks/abx-sdk@0.1.0-alpha.23
425
-
426
- ## 0.1.0-alpha.30
427
-
428
- ### Minor Changes
429
-
430
- - 993e095: `--dry-run` now **simulates** the transaction instead of only describing it.
431
-
432
- Every write command routes through one risk gate, so all of them gained this at once. A dry run
433
- `eth_call`s the prepared transaction against current state and adds one line:
434
-
435
- ```
436
- simulation ✓ would succeed (eth_call against current state)
437
- simulation ✗ would REVERT — FieldLocked()
438
- sending this now would burn gas and change nothing.
439
- ```
440
-
441
- This matters most for a **multicall** — `attach`'s batched pairs, `deploy-code`'s setup — because a
442
- multicall is one transaction, so simulating it exercises the whole sequence atomically, against real
443
- state, with no fork involved.
444
-
445
- It reports **unknown**, never a false green, whenever it cannot prove anything: no signer to simulate
446
- as, a contract deploy, a target with no code yet, or an unreachable node.
447
-
448
- Revert reasons decode to the contract's own error name (`RoyaltyTooHigh()`) where the node returns
449
- revert data. Several public endpoints return none — in that case the output says so plainly and names
450
- what a bare revert usually is, rather than viem's "reverted for an unknown reason", which reads like a
451
- decoding failure on our side.
452
-
453
- - 993e095: `deploy-code --copies` now supports `--image-renderer` / `--attributes-renderer` — a **true fully
454
- on-chain ERC-1155 edition**.
455
-
456
- ```bash
457
- abx deploy-code --copies open --image-renderer 0x… --attributes-renderer 0x… --onchain-uri \
458
- --name "…" --symbol …
459
- ```
460
-
461
- Open supply, minted over time by anyone you let mint, image and traits computed by your Solidity
462
- renderers, `uri(id)` assembled on chain. No resolver, no render runner, no bucket, nothing to keep
463
- running — the same zero-infrastructure lane the ERC-721 side has had, on the edition twin.
464
-
465
- - `--script` is now **optional** on this command. A renderer-only edition stores no program and has no
466
- `animation_url`; passing both a script and renderers composes (an on-chain live view _and_ an
467
- on-chain still), exactly as on the 721 lane.
468
- - The same guard applies as at 721 deploy time: a renderer address with no code is refused, because
469
- pointing a field at one reverts every `uri()` in the collection.
470
- - `--image-base` is still refused with `--copies` — it needs the effect runner to write a still per id
471
- — and now says so, and names the lane that does work.
472
-
473
- This was never a contract limit: `EditionCode` composes the same `OnChainMetadata` extension and URI
474
- renderer slots as `SeriesCode`. Only the CLI flags were unported, and a reporter who asked the natural
475
- question — "can I get a real on-chain-rendered 1155 with no server?" — read the blanket refusal as a
476
- protocol answer and shipped an ERC-721 workaround instead. `contracts/test/EditionOnChainRender.t.sol`
477
- now pins the claim end to end.
478
-
479
- Reported in the 2026-08-24 field notes.
480
-
481
- ### Patch Changes
482
-
483
- - 993e095: `abx doctor` checks whether each RPC endpoint agrees with itself about your wallet's nonce.
484
-
485
- `pending` is by definition at least `latest` on a coherent node. An endpoint that hasn't caught up
486
- with its own head answers lower — measured on `sepolia.base.org` right after a confirmed write — and
487
- the next command's first send re-uses a spent nonce. `doctor` reported that exact endpoint healthy in
488
- that exact condition.
489
-
490
- Sends are already protected (they floor at `max(pending, latest)`); this names the endpoint instead of
491
- silently routing around it.
492
-
493
- The check needs a wallet that has actually sent something — a fresh wallet reads 0 everywhere and
494
- proves nothing — so on that path it prints `nonce coherence: unknown` rather than passing on no
495
- evidence. A check that goes green without a signal is the same false green as an RPC answering `[]`
496
- because it pruned its logs.
497
-
498
- - 993e095: `deploy-code`, `deploy-code --copies` and `deploy-series` now state their permanent decisions on a real
499
- send, not only under `--dry-run`.
500
-
501
- The supply cap and the royalty rate — including the `⚠ default` marker when you didn't choose one —
502
- were printed in the deploy _plan_, which only runs for `--dry-run`. A creator going straight to `--send`
503
- learned afterwards, from `abx state`, that their collection carries a 5% royalty to the deploying wallet.
504
- That is a real economic default to discover after the fact on a demo nobody meant to monetize. The
505
- success readout now carries the same line the plan does.
506
-
507
- The default itself is unchanged: 5% to the creator is the right default for a launch, and a lane-specific
508
- 0% would silently strip royalties from projects deployed the same way for real.
509
-
510
- Reported in the 2026-08-24 field notes.
511
-
512
- - 993e095: `abx set-field --representation renderer` no longer writes a value the chain cannot decode.
513
-
514
- A `renderer` field is stored as `abi.encode(address)` and the on-chain metadata renderer decodes it
515
- before it can compute the field. `--value` was written to chain verbatim, so passing a plain 20-byte
516
- address — the same shape `deploy-code --image-renderer` takes — landed 20 bytes where 32 were needed,
517
- and **every `tokenURI` in the collection reverted**. It read like a protocol bug rather than a bad
518
- write: `contractField("image")` still returned the right representation and a right-looking address,
519
- and re-pointing at the renderer the collection was deployed with didn't fix it (that write had the
520
- same wrong shape).
521
-
522
- `set-field` now normalizes and validates the structured representations before sending:
523
-
524
- - `--representation renderer` accepts the field renderer's plain `0x` address and abi-encodes it, or
525
- an already-canonical 32-byte word. Any other length — or a 32-byte word with non-zero high bytes —
526
- is refused with a message that says what to pass.
527
- - `--representation reader` / `reader-gzip` must be `abi.encode(address reader, address pointer)`;
528
- anything else is refused and points at `--file`, which encodes it correctly for you.
529
- - A `renderer` value pointing at an address with **no code** is refused, the same guard
530
- `deploy-code --image-renderer` already applied at deploy time.
531
- - After a field write lands, `set-field` calls `tokenURI` once and says whether it still resolves —
532
- so a renderer that reverts for its own reasons is reported against the write that exposed it,
533
- with the recovery command.
534
-
535
- Already-broken collections are recoverable, and were never permanently damaged: re-run the same
536
- `set-field` on the current CLI (the field is only frozen by an explicit `abx lock-field`).
537
-
538
- Reported in the 2026-08-24 field notes, from four projects shipped on Base Sepolia.
539
-
540
- - Updated dependencies [549a32d]
541
- - Updated dependencies [993e095]
542
- - @artblocks/abx-token-api@0.1.0-alpha.25
543
- - @artblocks/abx-sdk@0.1.0-alpha.22
544
- - @artblocks/abx-indexer@0.1.0-alpha.23
545
- - @artblocks/abx-storage@0.1.0-alpha.22
546
-
547
- ## 0.1.0-alpha.29
548
-
549
- ### Minor Changes
550
-
551
- - c9f7aeb: Fold the burn, and make "canonically ABX v2" sayable — from abx-services' 2026-08-20 reply
552
- (`reviews/2026-08/services-reply-docs-23.md`). All off-chain: no contract changed, no address moved.
553
-
554
- **SDK — `foldSpine` now folds an ERC-721 burn, and `TokenState.minted` is replaced by `lifecycle`
555
- (BREAKING).** A burn wrote the zero address into `TokenState.owner` — where nothing downstream could
556
- tell it from a holder — and `minted` latched `true` at mint and was never recomputed, so a burned
557
- token reconstructed as live. `lifecycle: 'unminted' | 'live' | 'burned' | 'no-live-copies'` replaces it,
558
- and `owner` is `null` for a burned id rather than a sentinel. An enum rather than the `minted` +
559
- `burned` pair the report asked for: a boolean whose `false` has two meanings leaves the burn case one
560
- forgotten field away from rendering as "not yet minted", which is exactly what our own CLI did.
561
-
562
- **`'burned'` is terminal and ERC-721-only; an edition at zero live copies is `'no-live-copies'`.** The
563
- first cut of this enum shared `'burned'` across both standards, on the grounds that "one vocabulary on
564
- both lanes" was the honest shape. It wasn't: the correct _response_ to destruction differs by standard
565
- (a 721 id is gone forever and must `410`; an edition id can mint again and must not), so a shared word
566
- put every consumer one forgotten `contractType` branch away from serving `410 Gone` for a token the
567
- contract still resolves — and our own migration note told the first consumer to map the shared word
568
- straight onto their `410` sites. abx-services caught it in review before it published. Splitting the
569
- word moves the rule out of prose and into the type: **`lifecycle === 'burned'` is safe to treat as
570
- permanent on either standard, with no carve-out**, which let the reference resolver's `isEditionState`
571
- guard go away entirely. Pinned from both ends — no edition history can fold to `'burned'`, and the
572
- route asserts its own standard-blindness.
573
-
574
- **The two lanes now agree id-for-id.** The same review found `TokenRow.lifecycle` (head reads) reporting
575
- `'unknown'` for a fully-burned edition id while the fold reported `'burned'` for that same id — under a
576
- docstring claiming the two lanes used the same words. That is the sibling-drift class this repo treats
577
- as a bug (see `TokenState.maxSupply`'s note, the last time one field name carried two meanings). Both
578
- lanes now answer `'no-live-copies'` there: the fold _could_ distinguish never-minted from fully-burned
579
- and deliberately does not, because the distinction has no consumer. `'unknown'` survives for the one
580
- case where a head read genuinely cannot say — a 1/1 has no mint frontier, so a reverting `ownerOf`
581
- there is evidence of nothing.
582
-
583
- What the latched boolean was costing, all in our own tree: the effects harness re-rendered destroyed
584
- tokens on every sweep, forever; `onchain-uri`'s probe reads the LOWEST live id, so burning token 0 made
585
- a healthy collection's on-chain-URI lane report as **broken**; the resolver served metadata — and the
586
- pre-mint _warming placeholder_, i.e. "still loading" forever — for ids the contract disowns; `mintedCount`
587
- never went down; and `abx state` printed "not yet minted" for a token that had been destroyed.
588
-
589
- **SDK — `BurnConfigured` and `MaxRoyaltyBpsUpdated` fold into `ProjectState.burnable` /
590
- `.maxRoyaltyBps`.** Both events shipped in the ABI and in `SPINE_EVENT_DOC` and reached no field of
591
- state; the upgrade memo then told consumers the doc entry _was_ the fold, which it has never been (it
592
- supplies `register`/`what` on the event record, full stop). Both are tri-state: `null` means the spine
593
- never stated it — an implementation with no `burn` entrypoint and an unpublished ceiling — which is not
594
- `false`, and not 10%. The ceiling is deliberately not nested inside `royalty`, since clearing a royalty
595
- nulls that field while the ceiling stays binding on chain.
596
-
597
- **SDK — a fold-coverage guard, because this was the second instance in four days.** `DefaultMaxSupplySet`
598
- did the same thing on 2026-08-17. `SPINE_EVENT_NO_FOLD` now lists the events that deliberately reach no
599
- state _and why_ (a ping whose value is a head read, a factory's own log, a minter sibling, an allowance),
600
- and `spine-fold-coverage.test.ts` asserts every decodable event is either folded or excused. A new event
601
- is unfolded and unexcused until someone decides which.
602
-
603
- **SDK — a burned 1/1 no longer vanishes from `listTokens`.** The id range fell back to `totalSupply`
604
- (live: mints − burns) when a token type exposes no `nextTokenId`, so burning a 1/1's only token made the
605
- listing enumerate ZERO ids — `abx tokens` printed nothing at all, indistinguishable from a collection
606
- with no tokens. A 1/1's id space is `{0}` forever regardless of what is live. Found by burning a real
607
- token on Sepolia rather than by any fixture, which is the only way this one surfaces.
608
-
609
- **SDK — head reads can now say `burned`.** `nextTokenId` is a mint frontier that only rises, so an id
610
- below it whose `ownerOf` reverts was minted and destroyed. `TokenRow.lifecycle` (`'live'` / `'burned'` /
611
- `'unminted'` / `'unknown'`) and `TokenListing.burnedCount` (`nextTokenId − totalSupply`) drop out of that,
612
- with no event log. `'unknown'` stays a real member where the chain declines to say: a 1/1 has no frontier,
613
- and an edition's `supply == 0` cannot distinguish never-minted from fully-burned.
614
-
615
- **SDK — `burned` joins `ServiceErrorCode`, paired with `410 Gone`.** A remote token API had nothing
616
- honest to say about a destroyed id and was sending `410` with `code: 'not_registered'` — a statement
617
- about the _contract_. The rule behind the pairing is the general one: **a resolver answers what the
618
- contract's own URI getter answers.** A burned 721's `tokenURI` reverts `NonexistentToken`, so serving a
619
- document would contradict the contract; an ERC-1155's `uri(id)` has no existence gate and a zero-supply
620
- id can mint again, so **an edition never 410s** — it serves, with `supply: 0`.
621
-
622
- **SDK — `readCollectionPolicy`, `canonicalFactories`, `verifyProvenance`: protocol knowledge moves out
623
- of the CLI.** The CLI was declaring its own ABI fragments for `burnable`/`maxRoyaltyBps` and reading
624
- them itself in two places, so no other integrator could reach either fact. `canonicalFactories(chainId)`
625
- replaces two copies of the six-anchor enumeration inside `anchors.ts` — the list whose completeness _is_
626
- the trust model. `verifyCanonical` gains `opts.factories`, which **replaces** the manifest set rather
627
- than appending to it, so a multi-tenant operator's pinned allowlist can drive the gate without silently
628
- widening it.
629
-
630
- **SDK — anchor generations: "canonically ABX v2", not a bare `false`.** `verifyCanonical` reported
631
- "deployed by an ABX factory since replaced" and "deployed outside the toolkit entirely" identically —
632
- its own docstring admitted it — and the first is a perfectly good collection. `ANCHOR_GENERATIONS` keys
633
- each generation of the six trust anchors by the **on-chain core version** its clones report, and
634
- `verifyProvenance` returns `{canonical, generation: 'current' | 'prior' | null, coreVersion, factory,
635
- anchorsAnswered}`. The version is on the clone itself, so the answer is chain-verified rather than a
636
- manifest claim, and it keeps working after a factory retires. Retired generations never feed
637
- `verifyCanonical`: provenance is not trust — a superseded generation can predate a security remediation.
638
- Pre-launch testnet generations are deliberately not backfilled.
639
-
640
- **Redeploy process — a token-layer runtime change is a new anchor generation.** It bumps
641
- `AbxVersion.CORE_VERSION` and moves the SDK's `isCurrent*` probes in lockstep. That is now a checklist
642
- step (`contracts/README.md`, `CLAUDE.md`) _enforced by_ `deployments.test.ts`: recorded generations must
643
- have unique, increasing core versions and the newest must match the constant in `AbxVersion.sol`, so a
644
- batch cannot record a generation without bumping or bump without recording. The gap that prompted it: the
645
- 2026-08-20 batch added `burn()`, `burnable()`, `maxRoyaltyBps()` and `reduceMaxRoyaltyBps()` to the CORE
646
- base — burn is not an extension, so no extension version moved either — and left the constant at 2, so
647
- `abxVersion() == 2` cannot tell a burn-capable token from a pre-burn one.
648
-
649
- **CLI — the royalty ceiling is shown with its rate, and headroom is named.** A ceiling above the live
650
- rate is royalty the owner can add unilaterally, and a listing page never shows it. `abx state` prints the
651
- pair and, when there is headroom, says the useful thing: reducing the cap **to** the current rate is what
652
- turns "5% today" into "5%, provably, forever". (Named by abx-services as their `royalty-headroom` flag —
653
- a marketplace can compute it, so a creator should hear it from us first.)
654
-
655
- **Reference stack — the same fixes, so the reference is exemplary rather than the counter-example.**
656
- `mintedCount` keeps its name and counts LIVE ids, with `burnedCount` added only when non-zero (so a
657
- project with no burns serves byte-identical JSON, and a consumer's fixtures keep telling the truth);
658
- the indexer projection carries `lifecycle`; the effects status route and the render sweep skip destroyed
659
- ids; the dashboard badges a burned token instead of calling it minted; and `abx verify` / the migrate and
660
- re-point probes never choose a burned token as their subject.
661
-
662
- **CLI — `--send` is accepted on the deploy paths, and two lane flags is refused.** Every deploy's help
663
- advertises the signing lanes as `--send hot/env key · --sign wallet page · --unsigned print tx`, in 25
664
- places, and `--send` was the one of the three the deploy allowlist rejected: typing what the help showed
665
- produced `unrecognized flag(s): --send` from the guard whose whole purpose is catching flags that would
666
- be silently ignored — the guard firing on the tool's own documentation. Found by using the CLI as a cold
667
- reader of its own `--help` while setting up an on-chain test. While there, lane resolution moved from
668
- truthiness to **presence** (`--sign=` parses to `''`, which is falsy, so a caller who explicitly named
669
- the wallet lane was routed to the unattended env key — the one direction of that bug that costs
670
- something), and naming two lanes at once now refuses instead of resolving by an undocumented precedence.
671
-
672
- **Packaging — every published tarball ships its CHANGELOG.** `files` was `['dist', 'LICENSE']` on the
673
- SDK, indexer, storage, effects and token-api, so the only package whose release notes reached consumers
674
- was the CLI. A downstream team upgrading the SDK had no CHANGELOG in the tarball and diffed two `dist/`
675
- trees to find out what moved — then reported the release notes as missing, which cost a whole exchange
676
- to adjudicate. One line each.
677
-
678
- **Docs + spec.** The event spine gains a Burn section and the `MaxRoyaltyBpsUpdated` row, both with the
679
- fold rules an indexer relies on; three stale deploy-event-order lists are corrected; the remote-services
680
- error taxonomy gains the `410 burned` row and the edition carve-out; `owner-powers` explains headroom and
681
- burn permanence; and `indexing-notes` states plainly that documenting an event is not folding it.
682
-
683
- ### Patch Changes
684
-
685
- - Updated dependencies [c9f7aeb]
686
- - @artblocks/abx-sdk@0.1.0-alpha.21
687
- - @artblocks/abx-storage@0.1.0-alpha.21
688
- - @artblocks/abx-indexer@0.1.0-alpha.22
689
- - @artblocks/abx-token-api@0.1.0-alpha.24
690
-
691
- ## 0.1.0-alpha.28
692
-
693
- ### Patch Changes
694
-
695
- - 84ca9dd: Off-chain fixes from the 2026-08-19 production-readiness audit
696
- (`reviews/2026-08/production-readiness-audit.md`). All are off-chain — no contract changed and no
697
- address moved; the three NatSpec-precision items the audit also raised ride the next redeploy batch
698
- instead (`docs/10-backlog.md` → _Contracts — the next redeploy batch_, B47).
699
-
700
- **SDK — `HexColor` decode now masks to 24 bits, matching the chain (L-1).** `decodeScalarParam` for a
701
- `HexColor` value greater than `0xFFFFFF` returned `#1ffffff` (7+ hex digits); the on-chain
702
- `TokenDataLib.decodeScalar` masks with `& 0xffffff` and returns `#ffffff`. The governed write validates
703
- `v <= 0xFFFFFF`, but the raw owner setter does not, so an out-of-domain value can be stored and later
704
- governed by a `HexColor` schema — and the two serving planes would then decode it differently, splitting
705
- `inputsHash`. The SDK now masks identically. Pinned on both sides so it cannot drift again:
706
- `decodeScalarParam('HexColor', 0x1ffffffn) === '#ffffff'` (SDK) and the twin `test_DecodeScalars`
707
- assertion in `contracts/test/FieldRenderer.t.sol`.
708
-
709
- **SDK — gateway-prefix projection no longer trims (L-7).** `projectGatewayPrefix` dropped the `.trim()`
710
- on the on-chain `abx_gateway_*` value: the deployed `_gatewayPrefix` returns the stored bytes verbatim,
711
- so trimming here made the two planes disagree on a whitespace-padded prefix. The field store rejects an
712
- empty value, so a set field is always non-empty. Clean prefixes (every real one) are unaffected.
713
-
714
- **SDK — `permissionlessSalt` docstring hardened (L-3).** Now states explicitly: never use it for a
715
- pre-published or pre-funded address — a third party can occupy the predicted address first with their own
716
- `InitParams`. For any reserved address, use `saltFor` (its deployer-bound prefix is front-run-proof). No
717
- behavior change; the shipped CLI/skill already deploy through `saltFor`.
718
-
719
- **CLI — the edition schema advisory calls out a holder-writable `seed` by name (L-2).**
720
- `editionSchemaAdvisory` already warned that a `TokenOwner` param on an edition is shared per-id
721
- (last-writer-wins); it now adds a sharper note when that param is `seed`, because the normally-immutable
722
- generative seed becomes re-rollable by _any_ holder of the id — changing the artwork for every
723
- co-holder. The on-chain behavior is deliberately unchanged (the reassignable-seed feature is
724
- buyer-verifiable via `paramSchema("seed")`); the guardrail is this warning, not a contract restriction.
725
- A controller contract on the `Address` leg remains the way to govern it.
726
-
727
- - 84ca9dd: Finish the burn + royalty-cap surfacing, from the 2026-08-20 w6 agent sandbox sweep. All off-chain —
728
- no contract changed, no address moved; the on-chain features were verified working end to end (a cold
729
- agent deployed `--burnable --royalty-cap 42`, minted, burned, and proved the cap enforced on Sepolia).
730
- The sweep's convergent finding was that the two new **permanent** deploy-time decisions were invisible
731
- after you set them; this makes them visible everywhere they're decided or read, and adds the owner's
732
- missing reduce lever.
733
-
734
- - **New: `abx set-royalty-cap <address> --cap <0-10000>`** — the owner's reduce-only royalty-ceiling
735
- lever (`RoyaltyExtension.reduceMaxRoyaltyBps`), the "after" the deploy-time "reduce-only after"
736
- promise never had. Both ways the chain refuses are checked locally before signing: a value that
737
- wouldn't decrease (`RoyaltyCapNotReduced`) or that would drop below the live royalty rate
738
- (`RoyaltyCapBelowRoyalty`). SDK: `prepareReduceMaxRoyaltyBps`.
739
- - **The deploy readout now shows `royalty cap` and `burnable` on every lane** (1/1, series, code, and
740
- all three edition twins), whenever set. Critically it flags an **auto-raised** cap
741
- (`royalty cap 25% (auto-raised to fit the royalty)`): omitting `--royalty-cap` while setting a
742
- royalty above 10% silently raises the permanent ceiling to match, which was previously invisible.
743
- - **`abx state` now surfaces `royaltyCap` and `burnable`** (human + `--json`), the post-deploy
744
- verification surface, for both the 721 and edition readouts.
745
- - **The skill documents the royalty cap** — the identity decision (`decisions.md`), the confirm-
746
- identity step (`SKILL.md`), and a new `set-royalty-cap` owner-op row (`operating.md`); previously the
747
- reduce-only ceiling feature had zero skill coverage.
748
- - **Help/doc corrections:** `set-royalty --help` range `0-1000`→`0-10000` (stale from the hard-cap
749
- era); `deploy-series --help` now lists `--royalty-cap`/`--burnable`; `deploy --help` states the cap
750
- auto-raises; `abx inspect`'s trait-feasibility deep-dive points at public docs, not a repo path.
751
-
752
- - Updated dependencies [84ca9dd]
753
- - Updated dependencies [84ca9dd]
754
- - @artblocks/abx-sdk@0.1.0-alpha.20
755
- - @artblocks/abx-indexer@0.1.0-alpha.21
756
- - @artblocks/abx-storage@0.1.0-alpha.20
757
- - @artblocks/abx-token-api@0.1.0-alpha.23
758
-
759
- ## 0.1.0-alpha.27
760
-
761
- ### Patch Changes
762
-
763
- - b1c333d: **Asking for a 15% royalty built a transaction that reverted on chain.** `RoyaltyExtension` caps every
764
- ABX token at **1000 bps (10%)** and reverts `RoyaltyTooHigh()` above it, and no token type overrides that.
765
- But client-side validation allowed the full ERC-2981 `0–10000` range — and the CLI's own error text
766
- offered `10000 = 100%` as a worked example. So a creator asking for an ordinary 15% got a fully built,
767
- fully signed transaction that failed on chain; on the wallet lane they approved it in their own wallet
768
- and paid the gas to find out. Both usage strings said `<0-10000>`, and so did two public docs.
769
-
770
- Now `MAX_ROYALTY_BPS` (SDK) mirrors the contract ceiling and both `prepareSetRoyalty` and the CLI's
771
- `parseRoyaltyBps` validate against it, so the refusal is instant, local and free, and it names the cap
772
- and the revert. Regression tests cover it from both sides — including `1500`, the value that used to
773
- sail through. Two existing tests had to be corrected: they asserted the 0–10000 bound, which is how this
774
- survived.
775
-
776
- Three more surfaces that contradicted themselves or the chain:
777
-
778
- - **`abx ping-uri --help` claimed "Permissionless by design … ANY signer may call it (no owner check)"
779
- four lines below its own `OWNER-ONLY` header** — and `Uri1155.pingURI` is `onlyOwner`. A non-owner
780
- following the paragraph built a reverting tx.
781
- - **`site/content/docs/reference/contracts.mdx`** described the pre-audit library architecture: it omitted
782
- `AbxMetadataLib` (the fourth delegatecalled library, and the one linked by **all six** token types, not
783
- three) and still said `SeriesCode` "rides the ceiling", where `deployments.mdx` — written after the same
784
- redeploy — says both code types now sit under EIP-170 with room to spare.
785
- - **`deployments.mdx`'s own `specVersion()` example printed `# → 10`** under a table declaring the renderer
786
- spec v11. And **`operate.mdx`'s self-correction had rotted**: it warned that `abx refresh`'s advisory was
787
- stale about editions and ERC-4906, but that advisory was fixed — the doc was now the stale one.
788
-
789
- Found by two read-only audits (29 doc files, 19 spec files) that verified every claim against contracts,
790
- compiled ABIs, SDK and live `--help` output before reporting. Both independently re-confirmed the
791
- no-burn property, the v11 gateway design, all ~26 canonical addresses, and the on-chain storage
792
- thresholds.
793
-
794
- - b1c333d: **Every deploy lane accepted a royalty the chain would refuse.** `set-royalty` was fixed to validate
795
- against the contract's 10% cap, but all six deploy paths (`deploy`, `deploy-series`, `deploy-code` and
796
- their edition twins) still did a bare `Number(flags['royalty-bps'] ?? 500)` with no bound at all. So
797
- `--royalty-bps 1500` built the whole deploy, printed **`royalty 15%`** in the confirmation readout — a
798
- promise the chain will not keep — and reverted `RoyaltyTooHigh()` on send. That is the worse half of the
799
- bug: the deploy is the expensive transaction, and on the wallet lane the creator approves it first.
800
-
801
- All six now route through the same `parseRoyaltyBps` the owner op uses, so an over-cap value is refused
802
- locally, for free, with the cap and the revert named.
803
-
804
- **`abx doctor`'s pruned-history warning is now order-aware.** It fired for any reachable endpoint that
805
- cannot serve archive-depth logs, even when a healthy archive endpoint sat ahead of it in the list and would
806
- answer every deep scan. Three eval rooms independently called it noise — one said it was "a poor first
807
- impression for an already-nervous creator". The hazard it exists for is real but specifically about
808
- _ordering_: the fallback transport rotates on error only, so a pruning endpoint listed **first** answers a
809
- deep scan with an empty, successful `[]`. `probeRpcEndpoints` preserves list order, so the warning now
810
- fires only when the pruning endpoint is ahead of a full-archive one — the case a reader can act on. When
811
- no archive endpoint is configured at all, it still fires for every pruning endpoint, because then nothing
812
- shadows them.
813
-
814
- - b1c333d: An agent using this skill built its own smart-contract system, and neither it nor the creator noticed
815
- that the collection had stopped being a canonical ABX clone. Canonicity is decided by which contract
816
- deployed a token and can never be added afterwards, so the fix is a gate before the first send plus a
817
- readout that makes the loss discoverable.
818
-
819
- **`abx state` now reports canonicity first.** It read owner, supply, royalty and renderer off any
820
- ERC-721-shaped address and said nothing about whether it was a factory clone — so a hand-rolled contract
821
- was described exactly as confidently as a canonical one, and the tool whose whole job is "what IS this
822
- contract?" left nothing behind for the creator to find. `verifyCanonical` (SDK) probes all six trust
823
- anchors' `isAbxClone` in one multicall and returns a true tri-state; a `false` prints what was lost, that
824
- marketplaces and the App Store recognize collections by that signal, and that the only remedy is a fresh
825
- deploy through the factory plus moving holders. `null` (no anchors on this chain, or an RPC failure) is
826
- never shown as a "no".
827
-
828
- **The skill gained the boundary rule it never had.** Its trust material lived on the _last line_ of the
829
- file, written for someone verifying a clone from outside, and the "don't hand-roll" rules that existed
830
- were narrow — preview pages, ABI writes, token queries, nothing about contracts. There is now a
831
- Read-first gate: if the ask seems to need something the deploy commands don't do, stop and put it to the
832
- creator in words, because building your own contracts is a legitimate choice a creator can make and
833
- making it _for_ them silently is not. Alongside it, the list of what the toolkit does **not** do — no
834
- burn (verified: zero `burn` entrypoints in `contracts/src`, previously a parenthetical clause inside a
835
- sentence about hooks), no secondary listings, no mainnet, fixed-price sales only, no post-deploy script
836
- replace — and the two extension points that keep a collection canonical (`set-minter`, param hooks), with
837
- the caveat that a `--transfer` hook is a **veto, not a trigger**: it can refuse a transfer but never cause
838
- a mint or a burn, so it cannot implement "combine two into one".
839
-
840
- Verified by two trap rooms — a creator asking for a burn-to-combine mechanic with "you've got my
841
- go-ahead, I don't need the technical parts". Both Sonnet and Haiku refused to build a contract, cited the
842
- canonicity loss, and returned options. The Sonnet room found the route that _preserves_ canonicity and
843
- offered it first.
844
-
845
- **SKILL.md was restructured** against its own "lean decision-tree router" norm, which it had drifted
846
- ~40% past: **68,449 → 55,392 characters (−20%)**, longest bullet 2,876 → 1,465, bullets over 600 chars
847
- 27 → 14, with depth moved into the reference files that already owned it (locks → `decisions.md`;
848
- install/`.env` → `setup.md`, which was a 44-line stub; data plane, App Store and deploy mechanics →
849
- `operating.md`). Also repaired: a **markdown table split in two** by a blockquote sitting between its
850
- rows, and **four dangling anchor links** (three pre-existing) — all 96 internal links now resolve.
851
- `setup.md` was retitled and reordered after a control room found the required install steps buried under
852
- a title that promised RPC troubleshooting.
853
-
854
- **Two contradictions the verification sweep caught**, both the same class as the rest of this week's
855
- fixes. `deploy-code --dry-run` printed `render/storage ✓ fs + --image-base` a few lines below
856
- `⚠ render storage home is fs … placeholder forever`, for the identical setup; the ✓ is legitimately
857
- scoped to whether the `--image-base` target is a valid mutable bucket (two tests pin that), so the row now
858
- states what it covers and points at the other line rather than appearing to overrule it. And **"not
859
- backfillable" was overstated** in three places — the truth, which `code-projects.md` had right all along,
860
- is that fixing a surface after deploy costs an owner-signed re-point plus a re-render on a collection that
861
- showed a placeholder in between. Expensive and worth avoiding; not impossible.
862
-
863
- - Updated dependencies [b1c333d]
864
- - Updated dependencies [b1c333d]
865
- - @artblocks/abx-sdk@0.1.0-alpha.19
866
- - @artblocks/abx-indexer@0.1.0-alpha.20
867
- - @artblocks/abx-storage@0.1.0-alpha.19
868
- - @artblocks/abx-token-api@0.1.0-alpha.22
869
-
870
- ## 0.1.0-alpha.26
871
-
872
- ### Patch Changes
873
-
874
- - 77a6248: Fixes from the 2026-08-18 eight-room agent sweep of the content-addressed lanes (ipfs · arweave ·
875
- preferred gateways). The v11 gateway projection worked end to end on a live testnet round trip — bare
876
- CID on chain, one `set-gateway` tx, byte-identical content id before and after — but the surfaces
877
- around it lied, hid, or degraded in five ways that cold agents hit immediately.
878
-
879
- **Both `AbxGenerator` addresses in the manifest had a broken EIP-55 checksum.** The addresses were
880
- right; the casing was mangled in transcription. viem validates checksums, so
881
- `readContract`/`writeContract` against the recorded value threw `Address "0x2c1b7Cf6…" is invalid` —
882
- `abx verify` could not run its on-chain-URI check on **either** testnet. Corrected in
883
- `deployments.ts` and the docs mirror, with the code at both fixed addresses re-confirmed on chain
884
- (identical bytecode, as CREATE2 requires). The per-entry equality tests stayed green through all of
885
- it because they compared against the same mangled string, so the manifest now carries a **property
886
- test**: every recorded address must be a valid checksum.
887
-
888
- **A deploy narrated the upload gateway as if it were the served URL.** With
889
- `--onchain-uri --backend ipfs`, the progress line printed the backend's own locator
890
- (`https://gateway.pinata.cloud/ipfs/<cid>/0.png`) while the chain got a bare CID served from
891
- somewhere else entirely — in one run, from `ipfs.io`, because that is the floor and no preference was
892
- written. The line now shows the URL the token will actually carry, resolved through the same
893
- precedence that decides what gets written (`--ipfs-gateway`/`--arweave-gateway` → `--gateway` for the
894
- backend in use → env → floor), and says whether that prefix is the project's choice or the public
895
- default filling a silence.
896
-
897
- **`--ipfs-gateway` / `--arweave-gateway` were absent from every deploy command's `--help`.** They
898
- appeared only in `set-gateway --help` and the skill's reference pages, so an agent reading the CLI
899
- alone could not find the flags the CLI itself tells you to use. All three deploy commands now name
900
- them, with the floors (`https://ipfs.io/ipfs/`, `https://arweave.net/`) and the upload-vs-serving
901
- distinction stated where the decision is made.
902
-
903
- **`abx storage upload` handed `abx attach` a locator that silently downgraded the attachment.** It
904
- printed `abx attach <address> <key> https://gateway.pinata.cloud/ipfs/<cid>/file.png` — and `attach`
905
- picks the on-chain representation from the scheme, so an `https://` locator stores a plain `url` with
906
- the gateway host welded into the value: no `set-gateway` repoint, exactly the coupling the
907
- content-addressed representations exist to remove. Following the command's own printed next step was
908
- the wrong move — and the skill had promised the scheme form all along, so the doc was right and the
909
- command was wrong. It now hands back `ipfs://<cid>/file.png` / `ar://<txid>/file.png` (both in the prose
910
- hint and as `attachLocator` in `--json`), keeps the https URL as the browsable one, and says why they
911
- differ.
912
-
913
- **`.abx-self-host/` is now self-ignoring.** It holds this node's projection and, for the Arweave
914
- backend, a signing-capable key that carries prepaid upload credits. This repo gitignores that
915
- directory and the skill said so — but a _creator's_ repo does not, so a first `--backend arweave` run
916
- could leave a key staged for commit with nothing having said a word. The CLI writes a `.gitignore`
917
- containing `*` inside the directory before anything sensitive lands there: it ignores the whole tree
918
- regardless of the enclosing repo's config, needs no edit to a file we don't own, and never overwrites
919
- one that already exists.
920
-
921
- The **local-gateway warning** also stopped over-claiming. It said a `127.0.0.1` gateway means "the
922
- served image URL resolves only on THIS machine" — true when the gateway host was welded into the
923
- field, false now that the field holds a bare CID, and it aimed the creator at the wrong repair. It now
924
- names the damage that is still real: bytes pinned only to a local node cannot be retrieved by anyone,
925
- and no gateway can serve content that was never pinned publicly.
926
-
927
- Also: `abx storage status --json` no longer warns `unrecognized flag(s), ignored: --json` about a flag
928
- its own usage documents and that visibly works (a readout contradicting itself is worse than no
929
- readout), and the `--unsigned` epilogue names `abx add <address>` rather than `abx index <address>`,
930
- which fails on a node that never registered the project — the common case for a tx handed to someone
931
- else's signer.
932
-
933
- - 77a6248: Fixes from the 2026-08-18 six-room agent sweep of the hosted-services lane (managed provider ·
934
- self-hosting · conformance). Three surfaces reported a credential problem as something else.
935
-
936
- **`abx remote --conformance` blamed the service for our key.** With a rejected token it correctly
937
- reported `the provided token was rejected (401)` — and then ran the authed-tier probes anyway. Each
938
- one 401s, and each asserts on a 400, so one stale key produced four further failures reading
939
- `bytes for a REFERENCED output (image) → 401 (expected 400; accepting this is how a resolver becomes
940
- an object store)`. That is an accusation against the operator, and it is the exact inverse of what
941
- this command is for: conformance is meant to be checkable from OUTSIDE, before you sign up. A cold
942
- agent hit it and had to choose between believing five ✗ marks and explaining them away — it chose to
943
- explain them away and told the creator the service was trustworthy. Now a rejected token skips the
944
- authed tiers with one note (naming the descriptor's own signup URL), the public tier still runs in
945
- full, and the run reports the one failure that is real. 401 is also handled like 403 inside the probe
946
- helper, which already knew a credential problem is not a conformance verdict.
947
-
948
- **`abx doctor` reported a dead credential as configured.** It printed `remotes: meridian` for a token
949
- the service rejects, because it only checked that the variable was _present_. Doctor is the command a
950
- creator is told to run first, so the 401 surfaced much later, from `abx remote <name>`, after that
951
- line had already been trusted. Same class as an RPC that answers `[]` because it pruned its logs:
952
- presence is not capability. It now makes one cheap authed call per named remote and reports
953
- `✓ good · 1 project(s) visible` or `✗ meridian — token rejected (401) — fix or rotate
954
- ABX_REMOTE_MERIDIAN_TOKEN`. A short timeout with no backoff keeps it quick, a 403 stays a note
955
- (provider scoping is not a misconfiguration), and an unreachable provider is a soft note rather than
956
- a ✗ — nothing here can fail doctor for a reason the creator cannot act on.
957
-
958
- **`abx deploy-effects` told operators to set a variable that is never read.** The Fly scaffold's
959
- storage-secrets block emitted `fly secrets set ABX_ARWEAVE_KEY=…`; no such variable exists anywhere in
960
- the toolkit. A hosted runner cannot use the CLI's managed key file either (that path is on the
961
- operator's machine), so the line now names `ARWEAVE_JWK` with the JWK inline, which is the form that
962
- actually works remotely.
963
-
964
- **A remote register on a self-resolving project claimed a win it did not deliver.** `abx add --remote`
965
- printed its successes and stopped. But a project whose `tokenURIRenderer` is set answers `tokenURI`
966
- from the chain, so marketplaces and wallets read that document and never touch the service — the
967
- registration is still worth having (indexing, the live view, managed rendering) and changes nothing a
968
- collector sees. A cold agent, told "put it on a hosted service so it shows up properly", had to
969
- reconstruct that from `state` → `tokenuri` → `contracturi` → the docs → a raw curl before it could
970
- answer honestly. The register now says it, and the skill states the inverse rule the reference pages
971
- never had: a resolver adds nothing marketplace-facing to a project that already resolves on-chain.
972
-
973
- `abx verify`'s help also stopped contradicting itself — "no server needed" read as "needs nothing"
974
- while the command refuses until the project is registered locally with `abx add`. It now says "no
975
- resolver in the path" and names the prerequisite.
976
-
977
- Skill: stop asserting third-party prices and free tiers. Two rooms in one sweep quoted plan costs to a
978
- creator — one lifted "free tier" from our own `hosting.md` (Fly's has since changed), the other
979
- invented a monthly Pinata figure out of nothing. We cannot keep another company's pricing current, so
980
- the skill now says to name the provider and point at its pricing page, and to compare _shapes_
981
- (pay-once vs. monthly; lapses vs. nothing-to-renew) which stay true. Turbo's under-100 KB free tier
982
- stays, because the CLI enforces and prints it. The never-read-`.env` rule also moved to the top of
983
- SKILL.md: an agent listed and read files while orienting, before it reached that rule twenty lines
984
- down, and printed credentials into its own transcript.
985
-
986
- - Updated dependencies [77a6248]
987
- - Updated dependencies [77a6248]
988
- - @artblocks/abx-sdk@0.1.0-alpha.18
989
- - @artblocks/abx-indexer@0.1.0-alpha.19
990
- - @artblocks/abx-storage@0.1.0-alpha.18
991
- - @artblocks/abx-token-api@0.1.0-alpha.21
992
-
993
- ## 0.1.0-alpha.25
994
-
995
- ### Minor Changes
996
-
997
- - c5b8ec3: `ipfs` and `arweave` metadata fields now resolve on both planes, through a gateway the collection
998
- chooses — renderer **spec v11**.
999
-
1000
- These are the two representations that are content-addressed, the two where the locator IS the
1001
- integrity hash, and the two this protocol tells creators to prefer. They were also the two the
1002
- on-chain renderer could not serve. So `abx deploy --onchain-uri --backend ipfs|arweave` worked around
1003
- it by baking a gateway HOST into a `url` field, which welded a hostname the creator could never
1004
- migrate and made `abx_provenance` report `source: url` for bytes that live on IPFS.
1005
-
1006
- The fix separates the two facts that the baked URL had fused. The CID/txid stays in the field, as
1007
- identity, under that field's own lock. The HTTPS prefix becomes a project-wide preference in two
1008
- reserved collection-scope fields, `abx_gateway_ipfs` / `abx_gateway_arweave`, with `https://ipfs.io/ipfs/`
1009
- and `https://arweave.net/` as floors. The renderer, the canonical generator, and the off-chain
1010
- resolver all read those two fields and apply the same wrap, so a token served from the chain and from
1011
- a resolver produces the same URL.
1012
-
1013
- A dead or slow gateway is now a **repoint**, not a rewrite:
1014
-
1015
- ```bash
1016
- abx set-gateway 0xYourContract --ipfs https://your-dedicated.mypinata.cloud/ipfs/
1017
- ```
1018
-
1019
- One transaction, every token, no re-upload — and it works on fields you have already locked, because
1020
- the locked value is the CID and the gateway was never in it.
1021
-
1022
- **What changes for you**
1023
-
1024
- - `--onchain-uri --backend ipfs|arweave` writes the bare CID (or `cid/{id}.ext` for the O(1) directory)
1025
- as an `ipfs`/`arweave` field instead of a gateway URL as `url`/`url-template`. `--backend cloud` is
1026
- unchanged — an HTTPS CDN locator _is_ the address.
1027
- - New `abx set-gateway <addr> [--ipfs <prefix>] [--arweave <prefix>]` (`none` clears to the public
1028
- default). Two flags, so a project can pay for a dedicated IPFS gateway and leave Arweave public.
1029
- - New `--ipfs-gateway` / `--arweave-gateway` on every deploy command. Plain `--gateway` (the
1030
- storage/upload gateway) seeds the preference for the backend in use, so passing it still does what
1031
- you meant. Nothing is written when no gateway is named — silence keeps the floor live.
1032
- - `abx set-field` refuses the two gateway keys and points at `set-gateway`, which validates the
1033
- prefix, the scope and the representation. Every way to set them by hand fails silently.
1034
- - The `display.gateway` contract param is **superseded and no longer read**. It could name only one
1035
- prefix for both schemes and existed only on code projects.
1036
- - SDK: `projectGatewayPrefix`, `projectGatewayUrl`, `gatewayPrefixFrom`, `contentIdFromLocator`,
1037
- `GATEWAY_FIELD`, `GATEWAY_FLOOR`. For a host running its own resolver, `ABX_IPFS_GATEWAY` /
1038
- `ABX_ARWEAVE_GATEWAY` are now a **floor**: a project that stated a preference on chain gets that
1039
- preference from every conforming resolver, and host config only fills a silence.
1040
-
1041
- **New canonical addresses** (both testnets; nothing else moved — no token implementation, factory, or
1042
- `initialize` ABI changed):
1043
-
1044
- | Contract | Address |
1045
- | -------------------------------- | -------------------------------------------- |
1046
- | `AbxMetadataRenderer` (spec v11) | `0x85C1aE1F076d808fF7c1729F21B85038Fa16105E` |
1047
- | `AbxGenerator` (Sepolia) | `0xb7104AdFa6Fb5615e46E2a681A2Ff043B08fADB5` |
1048
- | `AbxGenerator` (Base Sepolia) | `0x2c1b7Cf6C54E4acBcB54FCc395f7Af88eB4fC8Ce` |
1049
-
1050
- Also fixes a crash on `abx deploy-code --copies`: an undefined `bytes` reference threw a
1051
- `ReferenceError` at the end of the dry-run plan and, worse, at the confirmation prompt of a real
1052
- deploy.
1053
-
1054
- ### Patch Changes
1055
-
1056
- - Updated dependencies [c5b8ec3]
1057
- - Updated dependencies [5b29f53]
1058
- - @artblocks/abx-sdk@0.1.0-alpha.17
1059
- - @artblocks/abx-token-api@0.1.0-alpha.20
1060
- - @artblocks/abx-indexer@0.1.0-alpha.18
1061
- - @artblocks/abx-storage@0.1.0-alpha.17
1062
-
1063
- ## 0.1.0-alpha.24
1064
-
1065
- ### Patch Changes
1066
-
1067
- - 9c287c0: The CLI no longer double-wraps a data URI, lie about managed rendering, or hand zsh an unquoted schema.
1068
-
1069
- - Inline `image` that is already a `data:` URI is refused — the renderer wraps bytes itself, and
1070
- storing one produced a blank token with no warning.
1071
- - Surfaces consult `render.attached` on the public resolver before warning that local `fs` means
1072
- "placeholder forever".
1073
- - Undeclared-param advice no longer tells you to schema-declare keys an augment hook supplies.
1074
- - `--schema` Select/Range examples (help and errors) are quoted so zsh does not glob them.
1075
- - `attach --help` states URI pairs and `--file` are mutually exclusive.
1076
- - The skill refuses unfilled `⟨your idea⟩` placeholders and documents one-EOA nonce serialization
1077
- plus "no replace-script CLI".
1078
-
1079
- Reported in the 2026-08-18 tester batch (feedback 111e2489, a07e0449, 4cedba82, 52487cde, d5423af5, 58984086, 735ed464, 7ff05a6b).
1080
-
1081
- - b5744a1: `deploy-series` no longer uploads your art before checking it can sign the deploy
1082
-
1083
- The signing lanes were checked at _signing_ time, which is the end of a deploy. Off-chain custody
1084
- (`--backend arweave|ipfs|cloud`) uploads the art near the start, before any transaction exists. So
1085
- `abx deploy-series --dir … --onchain-uri --backend arweave` with no key and no `--dry-run` minted a
1086
- Turbo identity and pushed the files, then discovered it could never have deployed. Under Turbo's
1087
- 100 KB free tier that upload _succeeds_ — so a run that was always going to fail published the
1088
- creator's work permanently.
1089
-
1090
- `assertLaneCanSign` refuses the keyless hot lane and names all three lanes plus the preview. The rule
1091
- it enforces is the sibling of the `--dry-run` guard on every signing choke point: **a spend or a
1092
- publish never precedes the check that this run can be signed at all.**
1093
-
1094
- It sits at the spend, not at the front door. A _validation_ refusal (an ipfs-shaped `--image-base`, a
1095
- missing `--name`, off-chain metadata with no public URL) must still reach a creator who has no key,
1096
- because it costs nothing to check and is the more specific answer — so the four image deploy bodies
1097
- assert just before their first byte leaves. `abx deploy` (1/1) turned out never to have been exposed:
1098
- it needs the deployer address to predict the clone, so it already refused before uploading.
1099
- `deploy-code` has no deploy-time upload and is untouched.
1100
-
1101
- Found by a cold agent that forgot `--dry-run` once and watched it try to spend anyway.
1102
-
1103
- - 9c287c0: PostParams commands refuse unsupported contract kinds before any signing prompt.
1104
-
1105
- `set-schema`, `configure-param`, and `retire-param` used to surface a raw viem revert on a 1/1 or
1106
- plain Series. They now detect the kind first and fail as a capability mismatch: SeriesCode /
1107
- EditionCode only. Help text says the same.
1108
-
1109
- Reported in the 2026-08-18 tester batch (feedback e47b8ed5, 018ec58a).
1110
-
1111
- - b5744a1: An RPC that has pruned its log history is now detected, named, and routed around
1112
-
1113
- A public endpoint that no longer holds old logs does not fail your scan — it answers `eth_getLogs`
1114
- with `[]` and HTTP 200, which is a success. Everything downstream believed it:
1115
-
1116
- - **`abx doctor` graded it `best`.** The archive probe was
1117
- `getLogs({address: zeroAddress, fromBlock: old, toBlock: old})` and counted any non-throw as archive
1118
- access — but `zeroAddress` never emits logs, so an empty result is exactly what a _perfect_ archive
1119
- node returns too. The assertion could not fail; it only ever caught endpoints that _error_ on old
1120
- blocks. `ARCHIVE_DEPTH` was also 100k blocks, which on a 2-second chain is inside the retained window
1121
- of the very endpoints that prune.
1122
- - **The client never failed over.** The read transport is a viem `fallback` across every configured
1123
- endpoint, and `fallback` rotates on an _error_. So every retry asked the same endpoint the same
1124
- question, and a project whose logs sit ready on the SECOND configured endpoint reconstructed as
1125
- empty.
1126
- - **`abx add` then registered the project with no state at all**, after grinding half a million blocks,
1127
- and suggested re-running `--full` "in a minute" — advice that can never work for a pruned index.
1128
-
1129
- Measured on the two default Base Sepolia endpoints, same address and same window: one returns zero
1130
- logs for the project's entire history, the other returns them instantly. Ethereum Sepolia's keyless
1131
- default endpoint shows the same shape past a few hundred thousand blocks.
1132
-
1133
- - **The probe asserts on something that must come back.** Blocks are retained by everyone; receipts
1134
- and the log index are what get pruned. `probeHistoryAt` (new SDK export) reads the probe block, asks
1135
- for one of its transactions' receipts, and — when that receipt carries a log — requires `getLogs` to
1136
- return that same log. No hardcoded address, no fixture block. `ARCHIVE_DEPTH` is now 500k.
1137
- It fails **safe**: a probe block with no transactions, or whose transactions logged nothing, keeps the
1138
- old lenient verdict rather than inventing a failure.
1139
- - **`abx doctor` names a reachable endpoint that can't serve history**, with the fault and the fix
1140
- ("history pruned ~500000 blocks back … list a full-archive endpoint FIRST in `ABX_RPC_URLS_<CHAIN>`").
1141
- Collapsing to the best endpoint hid this, and ordering is what decides who answers the scan.
1142
- - **An empty scan now retries each endpoint on its own** before giving up, and says which one served
1143
- the logs. Cheap by construction: each endpoint is first asked whether it holds history at _this
1144
- project's_ deploy block — three calls — and only one that passes gets a scan.
1145
- - **`SelfHostIndexer.reindex(address, {rpcUrl})`** pins a single endpoint instead of the pool, which is
1146
- what makes that rotation expressible. Clients are pooled per (chain, endpoint), so the default path
1147
- is unchanged.
1148
- - The final failure message now separates the two cases it had merged: a project you _just_ deployed
1149
- (transient, retry) from one that is not new (retention — check `abx doctor`).
1150
-
1151
- Found by direct probing during the 2026-08-17 agent sweep, after an `abx add` of a 12-day-old Base
1152
- Sepolia edition reconstructed zero of its 18 events.
1153
-
1154
- - 9c287c0: On-chain script chunks no longer insert a newline in the middle of a token.
1155
-
1156
- The generator joins chunks with `'\n'`. A fixed 22 kB slice could split `mass` into `ma` + `ss`,
1157
- producing a SyntaxError on chain while `abx verify` still went green. Template-mode deploys now
1158
- split at an existing newline so that join is byte-identical to the source, refuse a 22 kB+ span
1159
- with no newline, and parse the program before any gas.
1160
-
1161
- Reported in the 2026-08-18 tester batch (feedback 38455765, 038d7ed2, 8c256d9d, 50e53193).
1162
-
1163
- - b5744a1: Four wording fixes from the 2026-08-17 agent sweep
1164
-
1165
- - **A broken sentence at a decision point.** An edition's allocation warning read _"To sell 250, raise
1166
- this id's cap is not possible (caps only decrease) — set --allocation 15"_ — pattern-matched off the
1167
- 721 branch, where "redeploy a Series with a higher `--max`" fits the grammar and an edition's
1168
- decrease-only cap does not. Now: _"Selling 250 isn't reachable: this id's cap can only ever
1169
- DECREASE."_ The warning stays a warning: allocation is a ceiling, over-allocating isn't destructive,
1170
- and holding reserves is legitimate.
1171
- - **`abx help`'s `--copies` line promised a guard that doesn't exist**, claiming deploy-code's edition
1172
- lane is "--script only, no --dep/--code-dir/--image-renderer". `--dep`/`--dep-registry` are fully
1173
- wired on EditionCode and `abx help deploy-code` already documented them as working — only the
1174
- top-level summary was stale, which is the line an agent reads first. The flag allowlist's comment
1175
- said the same thing and is corrected too.
1176
- - **`reference/decisions.md` pattern 4** stated `--onchain-uri` for a 1/1 inline SVG without the size
1177
- crossover the same file gives 58 lines later, so the row alone pointed the wrong way for the 2.4 KB
1178
- example art.
1179
- - **The eval harness** now allowlists `ABX_CHAIN=<chain> abx …` in a clean room (`Bash(abx:*)` does not
1180
- match an env-prefixed command, which left three rooms unable to reach a contract on the non-default
1181
- chain), and `docs/agent-eval/README.md` records both that trap and the one where a room reaches
1182
- outside its sandbox via `abx skill install --global`.
1183
-
1184
- - Updated dependencies [9c287c0]
1185
- - Updated dependencies [b5744a1]
1186
- - Updated dependencies [9c287c0]
1187
- - @artblocks/abx-sdk@0.1.0-alpha.16
1188
- - @artblocks/abx-indexer@0.1.0-alpha.17
1189
- - @artblocks/abx-storage@0.1.0-alpha.16
1190
- - @artblocks/abx-token-api@0.1.0-alpha.19
1191
-
1192
- ## 0.1.0-alpha.23
1193
-
1194
- ### Patch Changes
1195
-
1196
- - 7fec2d7: An edition's per-id cap now folds from the log, so a capped edition stops reporting itself as open
1197
-
1198
- `DefaultMaxSupplySet` — added in the contract-audit release precisely so the log could tell an open
1199
- edition from a capped one — reached the ABI and stopped there. `reconstruct.ts` had no fold case, so
1200
- the collection-wide default never landed in `ProjectState`, and **the common shape was the broken
1201
- one**: `--copies N` sets the cap at `initialize` and never calls `setMaxSupply`, so no id has a
1202
- `MaxSupplyUpdated` of its own and every capped edition ever deployed folded as _uncapped_. The
1203
- head-read lane (`listTokens`, which calls `maxSupply(id)`) said `N` the whole time — one field name,
1204
- two answers, on the value that decides whether a buy button renders. Found by the abx-services team
1205
- while upgrading their resolver/indexer to this release.
1206
-
1207
- - **`ProjectState.defaultMaxSupply`** — the collection-wide per-id default (`'0'` = open; `null` ⇒ not
1208
- an edition). The 1155 analogue of `maxInvocations`.
1209
- - **`TokenState.maxSupply` is now the _effective_ cap** — an id's own override, else that default —
1210
- and so equals `maxSupply(id)` on chain and its head-read twin `TokenRow.maxSupply`. Previously it
1211
- was the override alone, and absent otherwise.
1212
- - **`TokenState.maxSupplyOverridden`** — whether this id has ever been explicitly overridden. The
1213
- distinction the on-chain getter cannot express (`maxSupply(id)` returns `0` for both "never capped"
1214
- and "deliberately closed") and the log now can, plus a signal that the monotonic never-increase rule
1215
- is in force for that id.
1216
- - **`editionCapOf(token)`** — new SDK export returning `{kind:'open'} | {kind:'capped', cap} |
1217
- {kind:'closed'}`, so nothing re-derives the rule. Used by the dashboard and `abx tokens`; pass a
1218
- head-read row and a `'0'` cap reads `open`, which is all a state read can honestly say.
1219
- - **The dashboard stops lying twice.** It tested `maxSupply` for truthiness: a `--copies 10` id read
1220
- "(open)", and — because `'0'` is a non-empty string — an id deliberately closed would have read
1221
- "12 / 0". It now reads "12 / 10" and "12 (closed — no more can be minted)".
1222
- - **Projection columns**: `projects.default_max_supply`, `tokens.max_supply_overridden` (migration-added,
1223
- so an existing store keeps working; NULL stays NULL rather than becoming a fabricated `0`).
1224
-
1225
- Also in this pass, from the same review:
1226
-
1227
- - **`SPINE_EVENT_DOC` had drifted to 41 of 52 events**, so eleven folded events carried an empty
1228
- human description and defaulted to Register 2 — including `TransferValidatorUpdated`, which belongs
1229
- to Register 1, and the factory's `Deployed`. All eleven are described, and a new
1230
- `spine-doc-coverage.test.ts` fails the build if the table and the spine ABI drift apart again in
1231
- either direction.
1232
- - **One provenance body in the resolver's `renderer` branch.** The `text/uri-list` arm hand-built an
1233
- object byte-identical to `onChainProv(field, 'renderer', …)` — same keys, same order, the note string
1234
- typed a second time — so the only thing that branch could do was drift from `sourceNote`.
1235
- - **Dead `abx_params`-era test scaffolding deleted** (a `paramsOf(json) => json.abx_params` reader, an
1236
- RPC stub, a schema factory), including an import of a `ParamMember` type that no longer exists —
1237
- which nothing caught, because `tsconfig` includes only `src`, so test files are never typechecked.
1238
-
1239
- - Updated dependencies [7fec2d7]
1240
- - Updated dependencies [7fec2d7]
1241
- - @artblocks/abx-token-api@0.1.0-alpha.18
1242
- - @artblocks/abx-sdk@0.1.0-alpha.15
1243
- - @artblocks/abx-indexer@0.1.0-alpha.16
1244
- - @artblocks/abx-storage@0.1.0-alpha.15
1245
-
1246
- ## 0.1.0-alpha.22
1247
-
1248
- ### Minor Changes
1249
-
1250
- - 1b6b741: Rename "artist" to "creator" throughout, including the protocol vocabulary
1251
-
1252
- ABX is a tool for creators generally. Art remains a first-class use case, but it should not be baked
1253
- into the product's framing — and it was, down to the on-chain identifiers.
1254
-
1255
- **Breaking, and deliberately a clean break** (pre-launch, folded into the one redeploy that four
1256
- rounds of security remediation already required — deferring would have meant either living with the
1257
- old vocabulary permanently or spending a second deployment on a word):
1258
-
1259
- - `AuthOption` is now `Creator`, `TokenOwner`, `Address`, `CreatorOrTokenOwner`, `CreatorOrAddress`,
1260
- `TokenOwnerOrAddress`, `CreatorOrTokenOwnerOrAddress`. Order is unchanged, so the `uint8` values
1261
- are identical — only the names moved. Users type these: `--schema key:Type:Creator`.
1262
- - The on-chain collection metadata field keys `artist` / `artist_links` are now `creator` /
1263
- `creator_links`. These are `bytes32` keys written on-chain and emitted verbatim into every
1264
- collection's `contractURI` JSON.
1265
- - CLI flags `--artist` / `--artist-links` are now `--creator` / `--creator-links`. No aliases.
1266
- - `@artblocks/abx-token-api` renames its `art` module to `content`: `generateArt` →
1267
- `generateContent`, `artContentHash` → `contentHash`.
1268
- - `AbxMetadataRenderer.SPEC_VERSION` is **7**, and `isCurrentRenderer` gates on it. A v6 renderer
1269
- emits a different member key, so it is genuinely behind, not cosmetically so.
1270
-
1271
- Package CHANGELOGs are left alone on purpose: they record what past alpha versions actually shipped,
1272
- and rewriting them would make the release history lie about flag names that really were `--artist`.
1273
-
1274
- - 1b6b741: A purchase now carries the terms the buyer accepted — closing the one path by which a project owner
1275
- could take more than the buyer agreed to.
1276
-
1277
- Both shared fixed-price minters take a mandatory **terms guard** — not a slippage allowance; see the note
1278
- below — and revert a new `SaleTermsChanged()` when the live sale doesn't match:
1279
-
1280
- ```solidity
1281
- purchase(address token, address expectedPaymentToken, uint256 maxPrice)
1282
- purchaseTo(address token, address to, address expectedPaymentToken, uint256 maxPrice)
1283
- // 1155 — the bound is the TOTAL, since qty multiplies it
1284
- purchase(address token, uint256 id, uint256 qty, address expectedPaymentToken, uint256 maxTotalPrice)
1285
- purchaseTo(address token, uint256 id, uint256 qty, address to, address expectedPaymentToken, uint256 maxTotalPrice)
1286
- ```
1287
-
1288
- `configure` takes effect immediately and has no timelock. On the ETH lane that was largely contained
1289
- (`msg.value` must equal `price`), but an ERC-20 sale settles `safeTransferFrom(buyer, payee, price)`
1290
- against a standing allowance — so an owner who front-ran a pending purchase with a huge price spent the
1291
- buyer's whole approval, and one who switched the sale to a different ERC-20 reached an allowance granted
1292
- somewhere else. `address(0)` as `expectedPaymentToken` means ETH. There is deliberately no "no maximum"
1293
- sentinel: a bound that can be defaulted away is the bug being fixed.
1294
-
1295
- SDK (breaking): `preparePurchase` / `preparePurchase1155` take the live terms — `sale`, exactly what
1296
- `readSaleConfig` / `readSaleConfig1155` returns — instead of a hand-computed `value`. They derive the
1297
- ETH to attach (`price`, or `price × quantity`) and the bound from those terms, so the payment and the
1298
- guard cannot desync. Pass `maxPrice` / `maxTotalPrice` to state a wider ceiling (a UI that tolerates a
1299
- small move rather than making the buyer re-sign); it widens the guard only, never the payment. New
1300
- exported type `PurchaseTerms`.
1301
-
1302
- **`maxPrice` is not slippage tolerance on the ETH lane, and the docs no longer imply it is.** The minter
1303
- requires `msg.value == price` (an equality) and V1 has no refund path, so an in-flight ETH purchase
1304
- reverts if the price moves in _either_ direction, however wide the ceiling. Real tolerance exists only on
1305
- the ERC-20 lane, where the live `price` is pulled from the buyer's allowance and anything at or below the
1306
- ceiling settles.
1307
-
1308
- CLI: `abx minter buy` hands the terms it just read to the SDK (the price × quantity math now lives in
1309
- one place), and the scaffolded `abx mint-page` binds each mint to the terms the page is displaying —
1310
- if the owner re-prices mid-click the mint reverts, nothing is charged, and the page tells the buyer to
1311
- review the new price. Re-pricing a live sale therefore fails the buys already in flight, by design:
1312
- pause first if you want a clean cutover.
1313
-
1314
- **Deploy note:** this is a runtime change to two shared singletons, so both need a redeploy on every
1315
- supported chain (CREATE2 + canonical salts, so the new addresses are identical cross-chain) and the new
1316
- addresses recorded in `packages/sdk/src/deployments.ts` +
1317
- `site/content/docs/reference/deployments.mdx`. Until then the CREATE2-prediction-vs-manifest tests fail
1318
- on purpose — that is the drift detector doing its job, not a broken test.
1319
-
1320
- - 1b6b741: The configure hook can see a blob's size and contents — so creators can police it instead of the protocol
1321
-
1322
- `IAbxConfigureHook.onParamConfigured` gains two arguments:
1323
-
1324
- ```solidity
1325
- function onParamConfigured(
1326
- uint256 tokenId,
1327
- bytes32 key,
1328
- bytes32 value, // the literal on the scalar path; keccak256(data) on the blob path
1329
- address updatedBy,
1330
- uint256 dataLength, // 0 on the scalar path; data.length on the blob path (never 0 there)
1331
- address dataBlobAddress // address(0) on the scalar path; the live SSTORE2 pointer otherwise
1332
- ) external;
1333
- ```
1334
-
1335
- **Why.** On a multi-copy edition params belong to the id, so a holder-writable `String`/`Bytes` key is
1336
- shared: one holder could store large values and make the shared id's document expensive for every
1337
- co-holder. An independent audit asked for a protocol byte ceiling. A ceiling would cap every project
1338
- to police a configuration almost nobody wants — but the alternative we had documented, "the creator
1339
- constrains it in their own hook", was not actually possible: the hook only ever saw `keccak256(data)`.
1340
- It could not learn the length, could not read the content, and could not read the new value from
1341
- storage either, because nothing had persisted yet. Now it can, and no protocol limit is imposed.
1342
-
1343
- **The bytes are not forwarded** — a blob can be nearly a full contract's worth of data, and putting it
1344
- in calldata would make every configure call pay for it whether the hook looks or not. A hook enforcing
1345
- a size ceiling reads `dataLength`; a hook that needs content calls `SSTORE2.read(dataBlobAddress)` and
1346
- pays for exactly what it asked for. Guaranteed: `keccak256(SSTORE2.read(dataBlobAddress)) == value`.
1347
-
1348
- **Two things to know when writing one.** `tokenParamData(tokenId, key)` still returns the OLD value
1349
- inside the hook (that is what "before the value persists" means — and it lets you veto a regression).
1350
- And the blob is written _before_ the hook, because an address is meaningless until the contract behind
1351
- it exists; so a veto happens after the writer paid to store the bytes. The accepted path costs the
1352
- same as before, only rejection wastes, and it wastes only the rejecting writer's own gas.
1353
-
1354
- `ConfigurableParams` is extension **v3** — the beacon's `extensionVersion` is how an integrator tells
1355
- which calling convention a deployed token uses. A hook written for the 4-argument form will revert
1356
- against a v3 token, and vice versa.
1357
-
1358
- Addresses: `AbxParamsLib` moved, `AbxEditionLib` with it (it links it), and the four token types that
1359
- compose `ConfigurableParams` moved with their factories — `SeriesCode`, `EditionCode`,
1360
- `OneOfOneEdition`, `EditionImage`. The two ERC-721 image types and their factory, `AbxMetadataLib`,
1361
- `AbxCodeLib`, the renderer, chunk store, seed source and both minters keep their addresses.
1362
-
1363
- - 1b6b741: Renderer spec v10 projects four long-reserved keys; edition trust anchors move
1364
-
1365
- **Renderer spec v10.** Four keys the spec reserved from the start were projected on chain by nothing:
1366
- `background_color` and `youtube_url` (token), `banner_image` and `featured_image` (collection). A
1367
- documented reserved key that no surface emits is a hole rather than a saved byte — a creator sets it,
1368
- observes nothing, and cannot tell whether the tool or the marketplace is at fault. All four now project
1369
- when their representation is chain-reachable, and are omitted when unset. `isCurrentRenderer` gates on
1370
- 10; a v9 renderer silently drops them.
1371
-
1372
- **`collaborators` is no longer a reserved key.** It was in the registry, projected by neither plane, and
1373
- because reserved keys are excluded from the resolver's `artifacts` listing, setting it on chain did
1374
- nothing observable anywhere. It is now an ordinary creator key — which means it **does** list in
1375
- `artifacts`. Put a collaborator list in a named artifact (`abx attach`) or in `description`.
1376
-
1377
- **The resolver's two collection image keys are now symmetric with the renderer.** `featured_image` is a
1378
- top-level `contractURI` key instead of artifacts-only, and `banner_image` projects for every
1379
- chain-reachable representation instead of only `url` (an `inline` or `reader` banner used to vanish).
1380
-
1381
- **Docs now carry the three-list split** — which keys both planes project, which are the off-chain
1382
- resolver's alone by design (`abx_provenance.status`, operator overlay, effects/live-view, the
1383
- `artifacts` listing, collection `image` courtesy, `image_data`, off-chain-decode values), and which are
1384
- not reserved at all. That split is the thing to read before adding a key to either surface: drift on a
1385
- key both planes _can_ emit is what produced three earlier remediations.
1386
-
1387
- **Addresses.** The three edition factories move (trust anchors) along with their implementations, and
1388
- the renderer moves. `AbxEditionLib`, `AbxMetadataLib`, `AbxCodeLib`, `AbxParamsLib`, all three ERC-721
1389
- factories, the chunk store, the seed source, both minters and both generators keep their addresses.
1390
- Existing clones keep running their frozen implementations; re-point a live collection at the new
1391
- renderer with `abx set-renderer` rather than redeploying it.
1392
-
1393
- - f64a31f: Expose `lockScript` above the contract layer — the lock that actually freezes a code project's work.
1394
-
1395
- `SeriesCode`/`EditionCode` have always had `lockScript()` (and `scriptLocked()`), but nothing above
1396
- the ABI surfaced it: the SDK had no `prepareLockScript`, the CLI had no `lock-script` command, and the
1397
- skill's immutability guidance told creators that `lock-field` + `lock-uri` made a project "provably
1398
- immutable". For a generative/code drop that is false — those freeze only the _metadata_; the owner can
1399
- keep rewriting the on-chain program (`setScriptChunk`/`removeLastScriptChunk`) until the script is
1400
- locked. Agents building on ABX repeatedly missed that the program could be frozen at all.
1401
-
1402
- - **SDK:** new `prepareLockScript({contract, chainId})` (mirrors `prepareLockDependencies`).
1403
- - **CLI:** new `abx lock-script <address>` command (any signing lane), plus `abx verify` now reports
1404
- the script and dependency lock state for code projects (`script: locked/UNLOCKED`, with a nudge to
1405
- `lock-script` when the program is still mutable) — read from the already-indexed state, no extra RPC.
1406
- - **Skill:** the immutability guidance now names the full freeze for a code drop
1407
- (`lock-script` + `lock-dependencies` + `lock-field`/`lock-uri`, plus `set-schema … lock=now` for a
1408
- param value), corrects the "provably immutable" claim, and surfaces PostParams as a first-class
1409
- `deploy-code` capability (not only a generative feature) in the `SKILL.md` description and decision
1410
- tree so collector-settable on-chain parameters are discoverable.
1411
-
1412
- - 1b6b741: Independent-audit remediation: `abx_provenance` reshaped, `abx_params` removed, hook ABI widened
1413
-
1414
- An independent adversarial audit of the contracts found no Critical or High issue, no theft path and
1415
- no cross-project reach — but four Mediums, eight Lows and five architectural notes. Everything is
1416
- addressed. Consumer-visible changes:
1417
-
1418
- **`abx_provenance` entries are now `{field, source, note}`.** They lost `onChain`, which was wrong in
1419
- both directions — a `url` reported `false` although the URL string is stored on chain, while an
1420
- `inline` value of `"https://…"` reported `true` for a pure pointer — and `verifiedAgainstChain`,
1421
- which was hardcoded `null` on every entry. `source` says where the bytes came from; whether a value
1422
- resolves on chain is visible in the value, and that judgment belongs to the reader. The off-chain
1423
- resolver drops `onChain` for the same reason (it was exactly `status === 'on-chain'`) and keeps
1424
- `status`, which it can genuinely compute.
1425
-
1426
- **`abx_params` no longer appears in `tokenURI`.** It was emitted by the on-chain renderer AND the
1427
- resolver and parsed back by nobody. Params enumerate directly from the contract — `tokenParamKeys`,
1428
- `tokenParam`, `paramSchemaKeys` — which is canonical, needs no indexer, and is what a chain-only
1429
- reader should use; a code project's script still receives them through `tokenData`. Traits for
1430
- marketplaces belong in `attributes`, unchanged.
1431
-
1432
- **A computed `image` locator now lands verbatim.** A field renderer returning
1433
- `("text/uri-list", "ipfs://…")` was being data-wrapped into `data:text/uri-list;base64,…`, which no
1434
- marketplace dereferences. `image` and `animation_url` now share one implementation of the rule.
1435
-
1436
- **`IAbxTransferHook.onTokenTransfer` gains `operator` and `amount`.** Without them a hook on a
1437
- shared-supply ERC-1155 cannot tell a real transfer from a zero-amount no-op — which let any address
1438
- fire the param lifecycle for an id it held no copy of. The token also refuses to notify on
1439
- zero-amount and self-transfers.
1440
-
1441
- **`pingURI` is owner-only**, and `paramSchemaHead` / `selectOption` are new: resolving one selected
1442
- option no longer copies the whole option table on every render.
1443
-
1444
- `AbxMetadataRenderer.SPEC_VERSION` is 8. Every canonical address moves — every token type is now
1445
- library-linked, including the two ERC-721 image factories, because the metadata field store was
1446
- externalized into the new `AbxMetadataLib` to restore EIP-170 headroom before any fix was stacked on
1447
- top of 187 bytes of margin.
1448
-
1449
- The CLI now warns when `--copies` meets a holder-writable `--schema`: on an edition, params belong to
1450
- the id, so every holder shares one value and the last writer wins.
1451
-
1452
- - 1b6b741: The custom seed source is now a real choice, not a documented one
1453
-
1454
- We tell creators — in the docs, in `AbxSeedSource`'s own NatSpec, and in `deploy-code --help` — that the
1455
- canonical mint seed is pseudorandom and that the answer for anything lottery-like is to point
1456
- `seedSource` at their own `IAbxSeedSource` over commit-reveal or a VRF oracle. On-chain that was always
1457
- true. In the toolkit it was not: there was no flag, no owner command, and the only route was an
1458
- `ABX_SEED_SOURCE` env var validated as nothing more than "has code at this address". A doc/code
1459
- contradiction of our own making, now closed at both ends.
1460
-
1461
- **Deploy:** `abx deploy-code --seed-source <0x…|canonical>` (and the `--copies` edition lane). Omitting
1462
- it is unchanged — the canonical `AbxSeedSource` from the manifest, bootstrapped on a chain that lacks
1463
- one — and `--no-seed` still means no mint-time seed at all. Passing both is refused rather than resolved
1464
- in someone's favour. `ABX_SEED_SOURCE` keeps working and is no longer the unchecked lane.
1465
-
1466
- **After deploy:** `abx set-seed-source <address> <0x…|canonical|none>` — owner-only, same positional
1467
- grammar as `abx set-transfer-validator`. It surfaces the on-chain `setSeedSource(address)` that nothing
1468
- reached before. **Future mints only**: a seed settles the instant it is assigned, so nothing already
1469
- minted changes. The command says so, names the count of tokens already carrying a seed from the old
1470
- source, and points out that a part-sold drop then spans two sources — public on the spine
1471
- (`SeedSourceSet`) but not something an early buyer is told, so pause and say so.
1472
-
1473
- **`abx state` now prints the seed source**, labelled: `none`, the canonical singleton, or `CUSTOM`. It is
1474
- the one setting an owner can re-point mid-sale that changes what a later buyer receives, and it was
1475
- readable nowhere.
1476
-
1477
- **Both writes probe the address first, and `code.length > 0` is not the check.** The SDK gained
1478
- `probeSeedSource` — one `eth_call` to `seed(uint256,address)`, which must return 32 bytes — plus
1479
- `readSeedSource`, `prepareSetSeedSource`, `assertSeedSourceUsable`, and a `SeedSourceUnusableError`
1480
- carrying a structured verdict. Four shapes are refused, each with its own message: **no code**;
1481
- **empty return** (the permissive-fallback shape — a Safe, an uninitialised proxy, an EIP-7702-delegated
1482
- EOA; pasting your own wallet is the common way in); **a return under 32 bytes**; and **a revert** (either
1483
- the wrong address entirely, or a real source not yet armed — `seed()` is called synchronously inside the
1484
- mint and its revert bubbles, so a source that cannot answer now cannot answer at mint either). The
1485
- interface is non-`view` on purpose, but an `eth_call` simulates a state-keeping source fine, so
1486
- commit-reveal and oracle-fed sources probe without sending anything. This matters because a
1487
- misconfigured seed source is **completely silent**: the write succeeds, `seedSource()` reads back what
1488
- you set, the event fires, `abx state` shows it — and then every mint of the collection reverts in the
1489
- token's `bytes32` decode. Same spirit as `CreatorToken._requireHasCode`.
1490
-
1491
- **Naming fix: a `seed` param schema is not a "re-roll", and no longer described as one.** The governed
1492
- path is `configureTokenParam(tokenId, "seed", value)` — **the caller supplies the value**. Nothing is
1493
- re-randomized and the seed source is never consulted again; with an unbounded `Uint256Range` the
1494
- authorized party may set any 32-byte value, repeatedly, until they like the output. "Re-roll" promises a
1495
- buyer a fresh random draw they could reasonably expect to be fair, which is not what they get. It is a
1496
- collector-chosen (or creator-chosen) seed, and the docs, the specs, and the skill now say that. The
1497
- substance that was correct is unchanged: the schema is a pre-sale commitment (declarable only before the
1498
- collection's first seed exists), `paramSchema("seed")` is the buyer's read, and the type must be a
1499
- literal/scalar one.
1500
-
1501
- **Dropped a stale warning** in the skill about a `seed` schema changing how the seed decodes off-chain
1502
- while the on-chain generator injects raw hex — `tokendata.ts` returns the raw hex for `seed`
1503
- unconditionally, ahead of the schema branch, so the wire format is invariant across both surfaces and
1504
- there is nothing left to warn about.
1505
-
1506
- **Also corrected: `maxPrice` is a terms assertion, not slippage tolerance.** On the ETH lane the minter
1507
- requires `msg.value == price` (an equality) and V1 has no refund path, so an in-flight ETH purchase
1508
- reverts if the price moves in _either_ direction, however wide the ceiling — widening `maxPrice` on an
1509
- ETH sale buys the buyer nothing. Real tolerance exists only on the ERC-20 lane, where the live price is
1510
- pulled from the buyer's allowance. `specs/protocol/minter-spine.md` and
1511
- `site/content/docs/protocol/minting.mdx` said "a price cut still settles" without that distinction.
1512
-
1513
- - 1b6b741: Renderer spec v9: a computed image is carried once, not twice
1514
-
1515
- A field-renderer-produced image was emitted as the reserved `image` key **and again** inside the
1516
- `artifacts` manifest, then the whole document was base64-encoded around both copies. On the
1517
- on-chain-SVG lane that roughly doubled the inner payload of `tokenURI` — the one read this protocol
1518
- most wants to stay within ordinary RPC limits.
1519
-
1520
- The data-plane spec always allowed an on-chain renderer to omit entries duplicating reserved keys it
1521
- already emits ("an EVM-efficiency reduction, never a semantic one"). The renderer now takes that for
1522
- every reserved key, so on a fully-on-chain project `artifacts` is absent rather than empty. Nothing is
1523
- lost, because the entry's only unique contribution was a `mimeType` and a `data:` URI states its own.
1524
- The off-chain resolver still emits the complete listing, duplicates included — that asymmetry is the
1525
- spec's, and it is now documented on both surfaces.
1526
-
1527
- Also removed: three orphaned param getters on the renderer (`paramKeysOf`, `paramSchemaOf`,
1528
- `paramDataOf`), stranded when v8 took params out of `tokenURI`, whose NatSpec still claimed `tokenURI`
1529
- called them.
1530
-
1531
- `AbxMetadataRenderer.SPEC_VERSION` is **9** and `isCurrentRenderer` gates on 9. Only the renderer
1532
- moves — nothing links it, so no factory or implementation address changes. Existing on-chain-URI
1533
- projects keep resolving through the v8 renderer until re-pointed with `abx set-renderer`.
1534
-
1535
- - 1b6b741: Upgrading from alpha.12/alpha.13 — what breaks, in one place
1536
-
1537
- This release lands the contract-audit branch. It is a **compatibility break with earlier alphas**, which
1538
- is fine at this stage (greenfield alpha, no backwards-compatibility promise) but is worth having in one
1539
- list rather than spread across a dozen changesets. Nothing here is a deprecation with a migration
1540
- window; the old shapes are gone.
1541
-
1542
- **Every canonical address moved**, and four manifest fields are new (`metadataLib`, `paramsLib`,
1543
- `codeLib`, `editionLib` — the delegatecalled write-path libraries). Resolve through `getDeployment` and
1544
- the upgrade is transparent; anything you have _stored_ keyed by a factory address is not. A project
1545
- indexed from a superseded anchor reads `isCanonical: false` against the new manifest — correct, since it
1546
- is what a creator sees if they don't redeploy, but it is a data question, not a version bump.
1547
-
1548
- **The metadata document changed: renderer spec v4 → v9.** If you assemble or assert on it:
1549
-
1550
- - `abx_provenance` entries lost `onChain` (it was exactly `status === 'on-chain'`, and wrong in both
1551
- directions) and `verifiedAgainstChain` (hardcoded `null` on every entry). `source` and `status` stay.
1552
- - The `abx_params` block is gone from **both** lanes. Params enumerate from the contract —
1553
- `tokenParamKeys` / `tokenParam` / `paramSchemaKeys` — which is canonical and needs no indexer; a code
1554
- project's script still receives them through `tokenData`.
1555
- - `artist` / `artist_links` are now `creator` / `creator_links` (reserved on-chain collection field
1556
- keys), and `AuthOption.Artist*` is `AuthOption.Creator*` (numeric values unchanged).
1557
- - A computed `image` locator (`text/uri-list`) lands **verbatim** instead of being wrapped into a
1558
- `data:` URI no marketplace dereferences.
1559
- - The on-chain renderer no longer duplicates a computed image into `artifacts`. The off-chain resolver
1560
- still emits the complete listing — that asymmetry is specified, not a bug.
1561
- - Four long-reserved keys now project on chain: `background_color`, `youtube_url` (token) and
1562
- `banner_image`, `featured_image` (collection). `featured_image` also becomes a top-level resolver key
1563
- instead of artifacts-only, and `banner_image` projects for every chain-reachable representation
1564
- rather than `url` alone.
1565
- - `collaborators` is **no longer reserved** — projected by neither plane and excluded from `artifacts`
1566
- by its own reserved-ness, so it did nothing. It is now an ordinary creator key that does list.
1567
-
1568
- **`tokenData` divergences fixed, so rendered output can change:** a contract-scope `seed` no longer
1569
- leaks into a token's `tokenData` (the on-chain generator never read one), `seed`'s wire format is always
1570
- raw 32-byte hex regardless of schema, and `</script` escaping is case-insensitive to match the on-chain
1571
- generator. Cached renders for affected code projects should be re-derived.
1572
-
1573
- **Removed:** `paramKeysOf`, `paramSchemaOf`, `paramDataOf` on `AbxMetadataRenderer` — orphaned when
1574
- params left `tokenURI`. Read the token directly instead.
1575
-
1576
- **Two hook interfaces widened** (`ConfigurableParams` is extension **v3**; a hook built for the old
1577
- signature reverts against a v3 token): `IAbxTransferHook.onTokenTransfer` gained `operator` + `amount`;
1578
- `IAbxConfigureHook.onParamConfigured` gained `dataLength` + `dataBlobAddress`.
1579
-
1580
- **Additive:** `ParamHooksFrozen` folds into `ParamHooks.locked`; `paramSchemaHead` + `selectOption`
1581
- replace pulling a whole option table to resolve one index; `readParamHooks`; `abxJs()` / `gunzipScript()`
1582
- on the generator for piecewise document assembly; and the three ERC-1155 ABIs now carry
1583
- `MetadataUpdate` / `BatchMetadataUpdate` / `DefaultMaxSupplySet`, which were emitted but not decodable.
1584
-
1585
- - 1b6b741: Transfer-validator authorization has one home, and the five affected trust anchors move
1586
-
1587
- `CreatorToken1155.setTransferValidator` performed no authorization at all, and the library it
1588
- delegates to performed none either. What protected the three edition token types was that each one
1589
- _overrode_ the setter to run the caller check first — one guard in three copies, sitting in front of
1590
- an unguarded mixin whose own comment claimed the tokens carried no override. Nothing deployed was
1591
- vulnerable, but a fourth edition type that simply inherited the mixin would have shipped an
1592
- unauthenticated `setTransferValidator`, which decides whether collectors' tokens can move at all.
1593
-
1594
- The guard now runs in the mixin, the overrides are gone, and the function is no longer `virtual` —
1595
- so the arrangement is enforced by the compiler rather than by a comment. A dead
1596
- `_requireMinterOrOwner` in `ExternalMinter`, whose NatSpec wrongly advertised it as the mint-time auth
1597
- check (the real one also enforces `paused()`), is deleted in the same pass.
1598
-
1599
- **Both changes are source-only** — the compiled runtime bodies are byte-identical before and after,
1600
- measured across all ten affected artifacts. Only the metadata hash differs, and since that is part of
1601
- the initcode, the CREATE2 addresses move. Five factories and their implementations are redeployed and
1602
- re-verified on Sepolia and Base Sepolia: `SeriesImageFactory`, `SeriesCodeFactory`,
1603
- `OneOfOneEditionFactory`, `EditionImageFactory`, `EditionCodeFactory`. Everything else — the 1/1 image
1604
- factory, all four libraries, the renderer, chunk store, seed source, both minters, both generators —
1605
- keeps its address.
1606
-
1607
- Projects deployed from the superseded factories keep working, but `abx` will report their anchor as
1608
- an older version. Redeploy from the anchors in the refreshed manifest.
1609
-
1610
- ### Patch Changes
1611
-
1612
- - 1b6b741: `ABX_CHAIN` in a `.env` now actually selects the chain
1613
-
1614
- Found by a cold sandbox agent: the creator asked for Sepolia, put `ABX_CHAIN=sepolia` in `.env`, and
1615
- got a **Base Sepolia** preview with no error. The agent scored it 3/5 and called it "a serious trust
1616
- problem for a tool whose entire pitch is 'here's exactly what would be deployed'". It was right.
1617
-
1618
- `config.ts` derives `CHAIN` as a module-level const, so it evaluated while the import graph loaded —
1619
- before `main()` called `loadDotEnv()`. Every other setting escaped this by being read lazily inside a
1620
- function (`storageOptions()`, the signing key at send time), by which point `.env` was loaded.
1621
- `ABX_CHAIN` was the one eager read, and the worst one to lose: on a funded send it is a wrong-chain
1622
- deploy with real artifacts at an address nobody meant.
1623
-
1624
- The same ordering hid a second symptom: `assertKnownChainEnv()` also runs at module scope, so a typo
1625
- like `ABX_CHAIN=mainnet` in `.env` read as "unset" and the CLI quietly used the default instead of
1626
- printing its "not a chain this toolkit ships / mainnet is not supported yet" refusal. Both are fixed
1627
- by loading `.env` before the chain is derived, and both are covered by tests.
1628
-
1629
- - 1b6b741: Externalize the code-custody READ surfaces into `AbxCodeLib` — B22 step 2, buying back the bytes the ERC-4906 audit fix spent
1630
-
1631
- The audit remediation that made `OnChainMetadata`'s field writes emit ERC-4906 (closing a real finding:
1632
- that surface mutated `tokenURI` silently, so a correct indexer served the pre-edit document forever) cost
1633
- ~96 B per token, and `SeriesCode` and `EditionCode` had no room for it — both fell under the 150 B EIP-170
1634
- floor `contracts/test/CodeSize.t.sol` guards. That is precisely the trigger condition `docs/10-backlog.md`
1635
- B22 was written for, and step 2 was the prescribed answer.
1636
-
1637
- The `IAbxOnChainScript` and `IAbxDependencies` view bodies now live in `AbxCodeLib`, next to the write
1638
- paths that were already there — `scriptChunkCount`/`scriptChunk`/`scriptLocked` and
1639
- `dependencyCount`/`dependencyByIndex`/`dependencyRegistry`/`dependenciesLocked`. The mixins keep the
1640
- functions (same signatures, same ABI, same ERC-165 ids) as **raw-calldata passthroughs** through a new
1641
- shared `AbxCodeDelegate` base, which forwards the call's exact calldata to the library and returns the
1642
- return data untouched. Storage still resolves in the token's own ERC-7201 namespaces and events still log
1643
- from the token — that is what `delegatecall` buys.
1644
-
1645
- Raw, not typed, on purpose: B22 step 1 measured a _typed_ shell (`return AbxCodeLib.f(...)`) **growing**
1646
- the token by ~855 B, because decoding the library's return value and re-encoding it at the call site costs
1647
- more than the extracted body saves. The same mistake was made again during this remediation, on
1648
- `setTokenField`/`setContractField`, and reverted. The price of the raw form is that `AbxCodeLib`'s read
1649
- signatures are now part of every composing token's external ABI — same signature ⇒ same selector is the
1650
- whole mechanism — so they must track the interfaces verbatim; both the library and the mixins now say so.
1651
-
1652
- EIP-170 margins, before → after:
1653
-
1654
- | contract | before | after | floor |
1655
- | ------------- | ------ | ------- | ----- |
1656
- | `SeriesCode` | 125 | **529** | 150 |
1657
- | `EditionCode` | 132 | **507** | 150 |
1658
-
1659
- `AbxCodeLib` grew 3,905 → 4,961 B and still has ~19.6 KB to spare. Nothing else moved: `OnChainScript` and
1660
- `Dependencies` are composed **only** by `SeriesCode` and `EditionCode`, and both already linked
1661
- `AbxCodeLib`, so no contract gained a library dependency and the other four tokens are byte-identical.
1662
-
1663
- No behavior change and no ABI change — the read signatures, return types, mutability, ERC-165 ids and the
1664
- `ScriptIndexOutOfRange`/`DependencyIndexOutOfRange` error selectors are all preserved (the errors stay
1665
- declared on the mixins so they remain in each token's ABI, and now revert from the library's
1666
- identically-named, identically-selectored twin). This does move the implementation bytecode, so it rides
1667
- the redeploy this remediation already requires: `AbxCodeLib`, both code tokens, and their factories (and
1668
- therefore the factory trust anchors) all get new addresses.
1669
-
1670
- - 1b6b741: CREATE2 for library deployments, as the standard process — and delete the false belief that said it was impossible
1671
-
1672
- An audit found that two ERC-1155 token types were held library-free, and an EIP-170 size floor relaxed, on
1673
- the recorded belief that linking a delegatecalled library costs a factory its CREATE2-deterministic address.
1674
- That belief is false, and the committed broadcast artifacts always refuted it: every recorded library deploy
1675
- went through the keyless CREATE2 proxy, `AbxParamsLib` landed at ONE address on Sepolia and Base Sepolia
1676
- from deployer nonces **215 apart** (a nonce is not an input to a CREATE2 address), and both library-linked
1677
- factories have held chain-identical addresses across four deploy generations.
1678
-
1679
- What was true was narrower and self-inflicted: `deploy.ts` deployed its libraries with `send({to: null})` —
1680
- a plain EOA `CREATE` — **by its own choice**, so _it_ could not predict them. `create2.ts` and both code
1681
- factory deploy functions then wrote that local limitation up as a property of libraries in general, and the
1682
- repo proceeded to contradict itself (`deployments.ts` and `reference/deployments.mdx` describe the correct
1683
- mechanism a few files away).
1684
-
1685
- The SDK now does what the docs already claimed:
1686
-
1687
- - Both bootstraps (`deploySeriesCodeFactory`, `deployEditionCodeFactory`) deploy every write-path library
1688
- through the keyless proxy at the canonical `AbxSalts` salts — `abx.lib.params.v1`, `abx.lib.code.v1`,
1689
- `abx.lib.edition.v1`, mirrored in `ABX_SALT` and pinned against `AbxSalts.sol` by a new test — and then
1690
- CREATE2 the linked factory at its own salt. **An SDK-bootstrapped chain and a forge-bootstrapped chain now
1691
- produce identical addresses** (verified: the SDK's predictions equal what
1692
- `DeployLibraries.s.sol --sig 'predict()'` prints).
1693
- - Idempotent, out of necessity rather than politeness: the proxy _reverts_ on an occupied address, so a
1694
- library already on-chain is linked against instead of redeployed — which is also what makes a partially
1695
- bootstrapped chain recoverable by re-running.
1696
- - New: `predictParamsLib`, `predictCodeLib`, `predictEditionLib`, `predictSeriesCodeFactory`,
1697
- `predictEditionCodeFactory`, plus exported `libPlaceholder` / `linkLibraries` / `LIB_FQN`. Both code
1698
- factories therefore gained the CREATE2 self-heal every other canonical anchor has — and need no version
1699
- probe for it, because code at a CREATE2 address is bytecode-bound to the build that predicted it.
1700
-
1701
- **Fixes a real crash on the way past.** `deployEditionCodeFactory` could not succeed at all: it deployed
1702
- `abxEditionLibBytecode` raw, but `AbxEditionLib` itself delegatecalls `AbxParamsLib`, so its shipped
1703
- bytecode carries an unlinked `__$…$__` placeholder — not hex, so the RPC rejected it with an opaque
1704
- `Invalid byte sequence`. The old guard only checked the _factory's_ bytecode, never the library's. The
1705
- library is now linked before it is deployed, and `linkLibraries` throws with the offending placeholder named.
1706
-
1707
- **The trap that IS real, now written down** (`create2.ts`, `reference/deployments.mdx`): a library's address
1708
- is a function of its creation bytecode, so build settings move it. Linking _after_ compilation — substituting
1709
- into solc's placeholder, what forge's automatic linking and the SDK both do — cannot change that bytecode.
1710
- Handing solc the addresses at build time (`--libraries src/…:Lib:0x…`) writes the map into
1711
- `settings.libraries` in every artifact's metadata, and the metadata hash is appended to the creation
1712
- bytecode: measured here, that moves the library's own address and the linked factory's initcode. Optimizer
1713
- profile does the same (the code tokens compile at 200 runs). Both failures are invisible until two chains
1714
- disagree, so the docs now say to compare `predict()` against the SDK's predictions rather than assume.
1715
-
1716
- Docs and specs swept for the old claim: `reference/deployments.mdx` (the salts, and a "libraries go first"
1717
- bootstrap step naming `DeployLibraries.s.sol` and its `predict()` dry run), `reference/contracts.mdx`
1718
- (`AbxEditionLib` was missing from the library taxonomy entirely), `reference/sdk.mdx`,
1719
- `specs/self-host-toolkit/deployment.md`, and the `abx-self-host` skill's code-projects reference. No
1720
- addresses were invented: nothing is redeployed yet, and the two manifest-drift tests still stand as the
1721
- detectors waiting on it.
1722
-
1723
- - 1b6b741: `abx doctor` stops calling a dust balance "funded", and its provenance line stops guessing how you invoked it
1724
-
1725
- Both found by cold agents in the clean room, one of them twice.
1726
-
1727
- **Funding had two states where it needed three.** A wallet holding `0.000000000848940602 ETH` — 848
1728
- gwei, enough for nothing — reported as `funded`, in full wei precision, with no faucet link, because the
1729
- test was `balance > 0n`. An agent then spent its whole run preparing a deploy it could not send and
1730
- named the quiet funding signal as its top friction. Now: zero is `empty`, below 0.001 ETH is
1731
- `too little to deploy` with a faucet link, and anything above reads `funded` at six decimals instead of
1732
- eighteen. The floor is a readout threshold, **not** a refusal — we don't estimate anyone's gas, and a
1733
- cheap L2 deploy below it is the caller's call.
1734
-
1735
- **The `binary` line said `source checkout (pnpm abx)`** even when the caller had typed a bare `abx` that
1736
- happened to resolve to a checkout. `source` was right; the parenthetical named an invocation that never
1737
- happened. It now says what is running rather than how you typed it. A provenance line that misstates the
1738
- invocation undermines every readout sitting next to it.
1739
-
1740
- Not changed, deliberately: `deploy --dry-run` still refuses to print an address without `--salt`. An
1741
- agent asked for a non-final example address to skip the two-step dance, but the salt is the only thing
1742
- that pins the address, and printing a real-looking one that won't be the deployed address is the exact
1743
- failure that refusal exists to prevent.
1744
-
1745
- - 1b6b741: Editions: the buyer picks the work, and the skill stops promising a freeze we withdrew
1746
-
1747
- **Say what the edition mint flow actually is.** The 721 Series lane and the edition lane answer "who
1748
- picks the work?" in opposite ways, and only the 721 half was written down. `IAbxSequentialMint` takes a
1749
- recipient and mints `nextTokenId` — nothing is selectable. The edition minter is keyed `(token, id)`, so
1750
- `purchase(token, id, …)` _names the work_, and on an `EditionCode` project each id carries its own
1751
- `seed`, drawn at that id's **first** mint because the seed belongs to the work rather than to a copy.
1752
- Choosing the id therefore chooses the seed. That is the product, not a leak: an edition is copies of a
1753
- known work, and seeing what you are buying is the normal case.
1754
-
1755
- Two consequences are now documented rather than left to be discovered:
1756
-
1757
- - Buying an **already-minted** id is choosing among settled, publicly readable works.
1758
- - Buying an **unminted** id draws its seed with the buyer's chosen `id` in the preimage — a real
1759
- selection surface (compute each candidate id's seed in a view loop, buy the best), bounded by which ids
1760
- have a configured sale and by `maxInvocations`. This is the same grinding surface the recipient `to` was
1761
- removed from the preimage to avoid, and `id` cannot be removed the same way, because it _is_ the
1762
- work. So `AbxSeedSource`'s "accept-or-decline, not choose" is now stated as the **721-lane** property
1763
- it is, everywhere it appeared (`protocol/code-projects`, `specs/protocol/code-projects`,
1764
- `specs/protocol/event-spine`, the skill).
1765
-
1766
- **Creator mints the supply, then lists on secondary** is documented as a first-class way to run an
1767
- edition: no minter, no allocation, no pause to manage, and every seed settles under the creator's own
1768
- transactions. It needs nothing new — the owner may always mint, paused or not, up to the id-space and
1769
- per-id caps — and it composes with a sale (reserve to self, price the rest).
1770
-
1771
- **The shipped skill's frontmatter no longer advertises "freeze/lock a project so it can never change"** —
1772
- the exact promise `SKILL.md`'s own body walks back. It now names what a lock actually freezes (fields, URI
1773
- config, script, dependencies) plus the new param-hook freeze, so the sentence that gets the skill _picked_
1774
- agrees with the sentence that tells the creator the truth. The router stayed a router: the seed paragraph
1775
- and the Solidity-lane paragraph were compressed to pay for the new material, with the depth in
1776
- `reference/code-projects.md`.
1777
-
1778
- - 1b6b741: Stop promising immutability the locks don't deliver — and name the residual owner powers in one place
1779
-
1780
- **The claim that was wrong.** With the field lock, URI lock, contract-URI lock, script lock and
1781
- dependency lock _all_ engaged, the rendered output can still change, two ways. **Params have no lock at
1782
- all** — they aren't fields, so no field lock reaches them, and the renderer projects the whole enumerated
1783
- set into `tokenURI` as `abx_params` (a program reads the same set as `tokenData`). And a
1784
- **`Resolution.Registry` dependency is re-fetched from the registry contract on every read** —
1785
- `lockDependencies` freezes the ref and the registry pointer, not another contract's storage. `abx verify`
1786
- reported `chain-complete` on both sides of a registry swapping its library bytes, and the CLI was telling
1787
- people "a fully immutable code drop is lock-script + lock-dependencies + lock-field/lock-uri".
1788
-
1789
- Every unqualified verdict is now qualified rather than deleted, because locking a pointer _is_ part of the
1790
- story: `lock-uri`, `lock-script` and `lock-dependencies` narration, their `--help` entries, `abx verify`'s
1791
- chain-complete pass (which now says the flag describes **where** bytes come from, not that they're frozen),
1792
- `GeneratorStatus.chainComplete`'s doc comment, the `lockScript` SDK docs, `protocol/metadata`,
1793
- `protocol/params`, `protocol/code-projects`, `reference/cli`, the operate guide, `specs/protocol/*`, and the
1794
- skill's SKILL.md · `reference/decisions.md` · `reference/code-projects.md` · `reference/operating.md`. The
1795
- line the toolkit now gives a creator is **"your metadata is locked"**, with the stronger claim reserved for
1796
- a project whose graph is on-chain `0x…` deps plus locked fields. Stated once per place the decision is made,
1797
- not restated everywhere.
1798
-
1799
- Framed as a capability as well as a caveat: a token that _deliberately_ live-adapts to on-chain conditions
1800
- is a legitimate and interesting thing to build, and it's the same mechanism that makes it possible.
1801
-
1802
- **New page: [What a project owner can do](https://abx.docs.artblocks.io/protocol/owner-powers/).** There was
1803
- nowhere a collector could read what powers the creator retains. It states both halves plainly, with the read
1804
- for each. What an owner **can** do: re-point or suspend an enrolled 721C/1155C transfer validator (and block
1805
- transfers entirely with one that reverts — though if they renounce ownership, anyone may suspend it and
1806
- nobody can re-arm it); change price, payee, allocation and pause on a live sale with no timelock; keep
1807
- receiving primary-sale proceeds after selling the project, because `primaryPayee` doesn't move on
1808
- `transferOwnership`; mint reserves outside the minter's allocation, since assigning a minter grants the whole
1809
- remaining cap rather than a slice; change royalty receiver and bps forever; re-point metadata subject to the
1810
- locks above; swap the seed source; reassign a token's seed _iff_ a `seed` schema was declared before the
1811
- collection's first seed existed; and walk away. What they provably **cannot** do: take a buyer's funds beyond
1812
- the terms that buyer signed for (`purchase`/`purchaseTo` require `expectedPaymentToken` and a maximum, and
1813
- revert `SaleTermsChanged`); exceed a supply or per-id edition cap; rewrite or clear a settled seed; reach
1814
- another project through any shared singleton (the renderer and chunk store hold no storage, the generator's
1815
- wiring is `immutable` with no setters, the seed source is a stateless view namespaced by caller, and the
1816
- minters key everything per token and hold no funds); or take a token out of a collector's wallet — there is no
1817
- owner-only transfer, burn, or clawback anywhere in the protocol.
1818
-
1819
- Framing, deliberately: ABX is self-serve, the creator is a privileged role, the protocol's job is to keep
1820
- that role away from a collector's funds and tokens, and the residual powers are disclosed so a collector can
1821
- price them. Not an accusation — a creator who destroys their own collection has destroyed what they were paid
1822
- for. The point is that "trust me" should be optional.
1823
-
1824
- - 1b6b741: `minter configure` no longer tells an edition its minter is unassigned when it isn't
1825
-
1826
- The readiness footer `abx minter configure` prints after configuring an edition sale read
1827
- `minter()`, `primaryPayee()` and `paused()` through the ERC-721 1/1 ABI, which has none of them. The
1828
- call threw client-side, the `.catch` swallowed it into a zero address, and a correctly-configured
1829
- edition was told to go run `set-minter` and `set-primary-payee` — both of which it had already done.
1830
-
1831
- This is the same bug `minter show` had, fixed on 2026-08-05; only the show path was corrected then.
1832
- The footer now reads through the edition ABI, and a live regression test covers it, so the two paths
1833
- can't drift apart again.
1834
-
1835
- - 1b6b741: Gate on-chain content by what a node will READ, not by what it costs to write
1836
-
1837
- The documented on-chain envelope — ≲24 KB/file, ≲256 KB/project — was a _storage-cost_ figure sold as a
1838
- support envelope, and the CLI's only loud warning fired on the project total. Measurement says the
1839
- binding constraint is somewhere else entirely: **`tokenURI` reassembles the whole document on every
1840
- call**, and that cost is **superlinear** — EVM memory expansion is quadratic, so the per-KB rate climbs
1841
- with size. Across the 10–100 KB range that decides most projects it measures **~360,000–405,000 gas per
1842
- KB**, rising to ~460,000/KB at 187 KB and ~510,000/KB at 256 KB. `inline` and `reader` land within ~1% of
1843
- each other up to 75 KB (the cost is the renderer's string building, not the storage mechanism) and
1844
- diverge above it (~5% at 187 KB, ~10% at 256 KB, as the chunk store's quadratic read loop takes over), so
1845
- `--compress fastlz` makes a field cheaper to write and not one gas cheaper to read.
1846
-
1847
- Measured with `forge` (`OneOfOneImage` + `AbxMetadataRenderer`, callee execution gas for one `tokenURI`
1848
- call, `reader` staging at 22,000-byte chunks):
1849
-
1850
- | on-chain content | `tokenURI` gas | per KB | | on-chain content | `tokenURI` gas | per KB |
1851
- | ---------------- | -------------- | ------- | --- | ---------------- | -------------- | ------- |
1852
- | 3 KB | 1,123,327 | 374,000 | | 100 KB | 40,254,159 | 403,000 |
1853
- | 10 KB | 3,588,993 | 359,000 | | 128 KB | 53,559,735 | 418,000 |
1854
- | 23 KB | 8,346,220 | 363,000 | | 187 KB | 86,021,071 | 460,000 |
1855
- | 40 KB | 14,740,366 | 369,000 | | 256 KB | 131,269,134 | 513,000 |
1856
- | 50 KB | 18,759,333 | 375,000 | | | | |
1857
- | 75 KB | 29,137,215 | 388,000 | | | | |
1858
- | 90 KB | 35,868,124 | 399,000 | | | | |
1859
-
1860
- geth's `--rpc.gascap` defaults to 50M, hosted providers commonly cap lower, and L1 block limits sit in
1861
- the mid-30-millions — so a project at the _documented_ ceiling could not be read by a normal node, and
1862
- from ~90 KB (~36M gas) up no contract can read it inside a transaction at all. Note what the 100 KB
1863
- refusal is: a **margin**, not the wall. 100 KB reads at ~40M, and geth's own 50M default is not
1864
- exhausted until ~120 KB; the refusal sits early because hosted providers cap well below geth.
1865
-
1866
- The gate now sits on that axis, per token (each `tokenURI` assembles only its own content, so a
1867
- 300-piece collection of 5 KB works is fine while one 256 KB work is not):
1868
-
1869
- - **from ~40 KB — warn.** Reads need a deliberately high-gas RPC.
1870
- - **past ~100 KB — refuse**, with `--allow-unreadable-onchain` as the override. The refusal names the
1871
- three ways forward and says plainly what the override accepts: rendering needs a high-gas endpoint and
1872
- most marketplaces and indexers will show nothing. It fires on the **dry run** too, so a creator meets it
1873
- before spending gas rather than after the first staging transaction.
1874
-
1875
- Live on every path that puts bytes on-chain: `deploy --onchain-image`, `deploy-series --onchain-image`,
1876
- both `--copies` edition twins, and `set-field --file`. New in the SDK: `classifyOnchainReadSize`,
1877
- `tokenUriGasEstimate` (fitted to the measurements, so it tracks the superlinearity instead of
1878
- multiplying by a flat rate), `TOKEN_URI_GAS_PER_KB_LOW` / `TOKEN_URI_GAS_PER_KB_HIGH` (the measured
1879
- 10–100 KB envelope — there is deliberately no single flat `TOKEN_URI_GAS_PER_KB`, because there is no
1880
- single rate), `ONCHAIN_READ_WARN_BYTES`, `ONCHAIN_READ_REFUSE_BYTES`, `ONCHAIN_READ_GETH_CAP_BYTES`
1881
- (where geth's 50M default actually runs out, so the refusal can be described as the margin it is)
1882
- (`exceedsOnchainSoftLimit` stays, now documented as the _write_-cost predicate it always was). The
1883
- whole-collection 256 KB warning stays as well, relabelled as write cost only, since it never said
1884
- anything about readability.
1885
-
1886
- Two things deliberately not done. **No on-chain guard and no gas check** — a read happens off-chain, a
1887
- read too large for one node is an RPC-capability problem, and bricking a contract is worse than needing a
1888
- capable endpoint. And the **code lane still only warns**, at the same 40 KB threshold and now quoting the
1889
- gas figure: `abx inspect`'s document size is an estimate that undercounts unknown dependencies, and a
1890
- registry-hosted library like p5 is legitimately ~200 KB, so the honest move there is to name the cost and
1891
- point at the generator's piecewise getters (`document`, `tokenDataJson`, `dependencyTag`, `abxJs`,
1892
- `gunzipScript`, `registryScriptChunk`) rather than block a shipped lane on a heuristic. Those getters are
1893
- now in the SDK's generator ABI, which claimed to carry "the piecewise reads" while omitting three of them.
1894
-
1895
- The docs and the `abx-self-host` skill were swept in the same pass: `protocol/onchain-storage` carries the
1896
- measured table and the corrected envelope, `protocol/code-projects` gains a piecewise-read section, and
1897
- the skill's Quick start and `reference/decisions.md` lead with "40 KB is the number, not 256 KB" plus the
1898
- rule that the override is a creator's deliberate choice and never a way to clear a warning.
1899
-
1900
- - 1b6b741: Round-3 audit remediation: link the two ERC-1155 image anchors, and stop guarding by name
1901
-
1902
- `EditionImage` and `OneOfOneEdition` became library-linked in the previous round, but neither the
1903
- SDK nor the forge scripts were updated. `predictEditionFactory()` and
1904
- `predictOneOfOneEditionFactory()` hashed **unlinked** bytecode — and viem does not reject a
1905
- `__$…$__` placeholder, it UTF-8-encodes it, so both returned a confident hash of ASCII garbage for
1906
- a trust anchor, and `ops.ts` would have broadcast that bytecode as initcode. Both now link
1907
- `AbxEditionLib` (itself linked against `AbxParamsLib`) and ensure the libraries exist first.
1908
-
1909
- Two addresses move as a result: `EditionImageFactory` and `OneOfOneEditionFactory`. Both are
1910
- pre-launch and pending the redeploy gate.
1911
-
1912
- This was the third instance of one bug class, and the existing guard missed it because it checked
1913
- two factories **by name**. The replacement is exhaustive by construction: it reverse-looks-up which
1914
- initcode each `predict*` actually hashed, drives every `prepareDeploy*` and every `deploy*`
1915
- orchestrator against a stub chain, and asserts no `__$` placeholder survives anywhere — so the next
1916
- library-linked contract is covered without anyone remembering to add it.
1917
-
1918
- Also: every SDK deploy now goes through CREATE2 at a canonical salt (the 1/1 and Series anchors were
1919
- still plain `CREATE`, landing at nonce-dependent addresses that could never match the manifest), and
1920
- the CLI no longer tells creators an ERC-1155 edition emits no ERC-4906 — both lanes emit it now.
1921
-
1922
- - 1b6b741: Warn when an explicit `--salt` has an all-zero guard prefix
1923
-
1924
- The factory reads a salt's leading 20 bytes as an access guard: the caller's own address means only
1925
- that signer may deploy with it, all-zero means anyone may. Omitting `--salt` gives you the pinned
1926
- form. But only `abx predict` ever explained this — the deploy commands took an explicit `--salt`
1927
- verbatim and said nothing.
1928
-
1929
- So a creator who pinned a vanity salt with a zero prefix and announced the predicted address was
1930
- handing it to whoever deployed there first, and the front-runner ends up as `owner()`, because
1931
- ownership is set by `initialize` rather than by the prediction. The 2026-08 security review
1932
- reproduced that.
1933
-
1934
- It warns rather than refuses: a zero prefix is the right choice for a deliberately shared,
1935
- caller-independent deployment. The check lives in the flag parser, which is the one place every
1936
- command passes through.
1937
-
1938
- - 1b6b741: Say the honest thing about the mint seed: pseudorandom, not lottery-grade
1939
-
1940
- The canonical `AbxSeedSource` stopped hashing the recipient (`to` is accepted and ignored) because a
1941
- buyer names the recipient in `purchaseTo`, which turned a caller-supplied field into a search space —
1942
- grind candidates in a view loop, buy once at the winner. Dropping it removes cheap _targeting_, and
1943
- nothing more: the seed is still derived entirely from on-chain values, so it is **deterministic after
1944
- the fact** (replayable from the block — the property that makes generative output verifiable) and
1945
- **not secret before it** (a contract minting in the same transaction can compute the seed it would
1946
- receive and revert unless it likes the result).
1947
-
1948
- The docs and the toolkit now say that where a creator decides, instead of implying more than the
1949
- contract delivers:
1950
-
1951
- - `deploy-code --help` spells out what `--no-seed` opts out of, and the deploy plan prints the same
1952
- note beside the `seed source` line it already showed — on the 721 and edition lanes both.
1953
- - The `abx-self-host` skill gained a one-paragraph router entry in **Code projects** and a
1954
- **Seeds — pseudorandom, not lottery-grade** section in `reference/code-projects.md`: fine where the
1955
- seed diversifies output and the distribution is the product, not fine for a raffle, a prize draw,
1956
- or any drop where one rare outcome is worth materially more than mint price. The escape hatch is
1957
- named rather than hinted at — the creator's own `IAbxSeedSource` over commit-reveal or a VRF oracle,
1958
- which is a per-project swap, not a fork of ABX (and is now reachable as `--seed-source` /
1959
- `abx set-seed-source` — see the companion entry).
1960
- - Both also document the seed's new governance rule: a `seed` param schema can only be declared
1961
- **before the collection's first seed exists**, so whether a seed's value may be re-set — and by
1962
- whom — is a pre-sale commitment a buyer reads with `paramSchema("seed")` (or `abx state`), not
1963
- something added after a sellout. That schema is deliberately **not** described as a "re-roll":
1964
- the governed write is `configureTokenParam(tokenId, "seed", value)` and the caller supplies the
1965
- value, so it is a chosen seed, not a fresh random draw.
1966
-
1967
- The public docs (`site/content/docs`), the protocol specs, and `contracts/README.md` were swept in the
1968
- same pass; no claim that the seed is "unpredictable" survives anywhere.
1969
-
1970
- - 1b6b741: Fix `abx skill install` and `abx scaffold-renderer` on a published npm install
1971
-
1972
- Reported from the field on v0.1.0-alpha.21: `abx skill install` — the canonical way to install the
1973
- agent skill, and the first thing the quickstart tells you to run — failed on every fresh global npm
1974
- install with `✗ bundled skill not found`. The reporter's diagnosis was exactly right.
1975
-
1976
- `resolveBundledSkill()` computed the package root as
1977
- `resolve(fileURLToPath(import.meta.url), '..', '..')`. That is correct for a module compiled to
1978
- `dist/main.js` and off by one for `dist/commands/scaffold.js`, which is a level deeper — so it landed
1979
- on `dist/` and looked for `<pkg>/dist/skill`. The assumption was written when the function lived one
1980
- level up and quietly broke when it moved into `commands/`.
1981
-
1982
- Two more instances of the same root cause came out with it:
1983
-
1984
- - **`abx scaffold-renderer` was broken the same way** and had not been reported — it looked for
1985
- `<pkg>/dist/assets/renderer-scaffold`.
1986
- - **The "am I running from source?" guard** matched `/src/<one-segment>$`, so it read false for
1987
- anything under `src/commands/`. On its own that was harmless; together with the root fix it would
1988
- have made a dev run prefer `<pkg>/skill` — gitignored prepack output — over the canonical skill,
1989
- which is how the canonical skill once got overwritten by a copy eight versions behind.
1990
-
1991
- Nothing caught any of it because a dev run never has the published layout: running from source, both
1992
- resolvers fall back to the repo copies and work regardless of the arithmetic. So the fix is structural
1993
- rather than a third `..`: one `packageRootFrom()` that walks up to the CLI's own name-checked
1994
- `package.json`, depth-independent by construction, plus `packaging.test.ts` — which builds a published
1995
- layout in a temp dir and walks it from several depths, and fails the build if counted-`..` root math
1996
- comes back in any spelling.
1997
-
1998
- Workaround on alpha.21, if you're on it:
1999
- `cp -r $(npm root -g)/@artblocks/abx-cli/skill ~/.claude/skills/abx-self-host`
2000
-
2001
- - 133ecbc: Add `abx submit-app` — optional post-deploy listing in the ABX App Store.
2002
-
2003
- Not folded into deploy. After a collection exists, the agent offers it, confirms catalog copy with the creator (name / summary / description — not the collection's NFT metadata), then mints through the gated minter and writes params in gas-capped batches. Already-claimed collections skip the mint (same as `/update`). Shipped Base Sepolia registry + minter; override with `--registry` / `--minter`.
2004
-
2005
- - 1b6b741: The param transfer hook is a veto, not best-effort — say so everywhere, and ship the lock that answers it
2006
-
2007
- The transfer hook used to swallow its own revert, and every surface promised that the parameter
2008
- lifecycle "must never block a transfer". That promise could not be kept, so it is withdrawn rather than
2009
- restated: Solady runs the ERC-721/1155 **receiver acceptance check after the hook**, so on the `safe*`
2010
- variants — which marketplace fills wrap — a hook that is cheap when a wallet estimates gas and expensive
2011
- when the transfer lands starves the work that comes after it, with or without a gas cap. Swallowing
2012
- bought a guarantee that was false in exactly the cases people use, while making an honest hook's failure
2013
- invisible.
2014
-
2015
- So the contract now calls the hook plainly (no swallow, no gas budget, no assembly), **its revert fails
2016
- the transfer**, and — because a mint and a burn are transfers from/to `0x0` — a reverting hook also stops
2017
- minting for that project, including through the shared minter. That is deliberate: a hook is always the
2018
- creator's own contract, which is why it may block issuance while the transfer _validator_, usually a
2019
- third party's, never sees mint or burn.
2020
-
2021
- The mitigation is a lock, and it is now reachable end to end:
2022
-
2023
- - **SDK** — `prepareLockParamHooks({contract, chainId})`, the sibling of `prepareLockScript` /
2024
- `prepareLockDependencies`. Plus two reads: `readParamHooks` (the trio) and `readParamHooksLocked`,
2025
- which establishes the freeze without a getter by simulating `setParamHooks` with the current trio and
2026
- watching for `ParamHooksLocked` — and returns `undefined`, never a cheerful `false`, when a node won't
2027
- answer.
2028
- - **CLI** — `abx lock-param-hooks <address>`. Owner-only, one-way, every signing lane, `--dry-run`
2029
- guarded. It prints the exact three addresses it will freeze, states what is given up (arming a
2030
- transfer veto, arming a configure veto, re-pointing or clearing the augment hook) and that there is no
2031
- way back, and distinguishes the two cases: freezing an **empty** set is how a project _proves_ it can
2032
- never arm a transfer veto, while freezing a set that already contains a hook pins which contracts can
2033
- run — it does not disarm them.
2034
- - **`abx state`** now prints the three hooks and whether they are frozen, next to the transfer validator,
2035
- because that pair is the read a buyer performs. `abx verify` warns on an armed-but-unfrozen transfer
2036
- hook. `abx set-param-hooks` states the veto at the moment a transfer hook is armed.
2037
- - **Reconstruction** — `ParamHooksFrozen` folds into `ProjectState.paramHooks.locked`. The event is
2038
- declared only by `AbxParamsLib`, so `spineEventAbi` now unions that ABI (its other events dedup away):
2039
- without it the one-way lock on a hook that can veto a transfer was invisible to every consumer that
2040
- folds the spine.
2041
-
2042
- Swept for the withdrawn promise: `protocol/params` (which gained a section on why it died),
2043
- `protocol/owner-powers` (the hook is now on **both** lists — an unlocked hook set as a live power, a
2044
- frozen one as a guarantee), `specs/protocol/event-spine`, `specs/protocol/user-stories`, the spine event
2045
- doc strings, and the `abx-self-host` skill (`SKILL.md`'s lock bullet plus the operating and code-project
2046
- references). No surface still says "best-effort", "reverts are swallowed", or "never blocks a transfer",
2047
- and a CLI test now fails if one comes back.
2048
-
2049
- - 1b6b741: Transfer validator: preflight what the chain actually refuses, and stop calling `setTransferValidator` owner-only
2050
-
2051
- Two corrections, both in the direction that was costing someone something.
2052
-
2053
- **The preflight was a bare `getCode`, and the chain's check is not.** `validateTransfer` returns nothing,
2054
- so there is no ABI decode to fail — which means any address whose fallback succeeds for an unknown
2055
- selector passes a has-code check and then waves **every** transfer through, while ERC-165,
2056
- `getTransferValidator()` and the extension beacon all report enforcement as ON. A Safe does exactly this
2057
- (`FallbackManager.fallback()` returns empty when no handler is set), as do an uninitialised proxy and a
2058
- 7702-delegated EOA, and a creator pasting their own Safe is the likely real case. The contract's guard was
2059
- rewritten to probe with a selector no validator implements and require it to **fail**; the toolkit now
2060
- asks the same question, with the same selector, through the new SDK `probeTransferValidator` (verdicts:
2061
- `ok` · `no-code` · `permissive-fallback` · `unreachable`). Both call sites use it — `abx
2062
- set-transfer-validator` and `deploy --721c` — so a creator gets a refusal that names what is actually
2063
- wrong instead of passing the CLI and reverting on chain against an error whose documented meaning ("no
2064
- code on this chain") was false for their address.
2065
-
2066
- **`setTransferValidator` is owner-only only while there is an owner.** Once `owner() == address(0)`
2067
- **anyone** may suspend enforcement by passing `address(0)`, and nobody may ever arm a validator again —
2068
- the dead-man release for a validator that reverts every transfer on a collection with nobody left to
2069
- re-point it, which would otherwise strand every collector's token permanently. Four surfaces still said
2070
- owner-only, and the SDK's was wrong in the direction that matters: an integrator reading it would refuse
2071
- to build the suspension a stranded holder needs. Corrected in `creator-token.ts` (module header plus
2072
- `prepareSetTransferValidator`), `protocol/royalty-enforcement`, `protocol/interfaces`, and
2073
- `specs/protocol/interfaces`, each stating the asymmetry: an ownerless collection can only ever be moved
2074
- _toward_ transferability, so this hands a stranger no power over a live project.
2075
-
2076
- - Updated dependencies [1b6b741]
2077
- - Updated dependencies [1b6b741]
2078
- - Updated dependencies [1b6b741]
2079
- - Updated dependencies [1b6b741]
2080
- - Updated dependencies [1b6b741]
2081
- - Updated dependencies [1b6b741]
2082
- - Updated dependencies [f64a31f]
2083
- - Updated dependencies [1b6b741]
2084
- - Updated dependencies [1b6b741]
2085
- - Updated dependencies [1b6b741]
2086
- - Updated dependencies [1b6b741]
2087
- - Updated dependencies [1b6b741]
2088
- - Updated dependencies [1b6b741]
2089
- - Updated dependencies [1b6b741]
2090
- - Updated dependencies [1b6b741]
2091
- - Updated dependencies [1b6b741]
2092
- - Updated dependencies [1b6b741]
2093
- - @artblocks/abx-sdk@0.1.0-alpha.14
2094
- - @artblocks/abx-token-api@0.1.0-alpha.17
2095
- - @artblocks/abx-indexer@0.1.0-alpha.15
2096
- - @artblocks/abx-storage@0.1.0-alpha.14
2097
-
2098
- ## 0.1.0-alpha.21
2099
-
2100
- ### Patch Changes
2101
-
2102
- - 528c6c6: Close most of the ERC-1155 edition CLI parity gaps (B28), give every command one unknown-flag posture
2103
- (B30), and add the per-holder edition balance read (B31).
2104
-
2105
- Each B28 item was checked against the tree before any code was written: **none needed a Solidity
2106
- change.** The contracts and SDK ops were already per-id and standard-neutral, so all of this is CLI
2107
- plumbing and **no redeploy, address churn, or Etherscan re-verification is involved.**
2108
-
2109
- **Edition custody is now symmetric with the 721 side** — each lane verified live on testnet, not just
2110
- by dry run:
2111
-
2112
- - **`deploy-series --copies` gains pattern 2: off-chain image, on-chain JSON, nothing to run.** The 721
2113
- Series had the whole branch; `EditionImage` never got it, so a multi-artwork **raster** edition had no
2114
- no-server option at all. A uniform file extension uploads the folder as ONE directory and bakes a
2115
- single `{id}`-substituting collection field, so the collection costs one field instead of N; mixed
2116
- extensions fall back to per-id `url` fields. Verified at
2117
- `0x4e7e2d1aA951d99046323a620a2a5891b1195452`, where `uri(0|1|2)` resolve to `…/0.png`, `…/1.png`,
2118
- `…/2.png` off one template and all three fetch at their source byte sizes. This gap had real design
2119
- consequences: a cold agent asked for "3 artworks × 25 copies each", hit the SVG-only refusal, and
2120
- split the collection into **three separate single-artwork contracts** to get the permanence it wanted.
2121
- - **`--onchain-image` works on both edition lanes, on the hot AND wallet lanes.** It was refused
2122
- outright on `deploy-series --copies` and hot-lane-only on `deploy --copies`; one sign session now
2123
- covers the chunk writes plus the deploy, and the staging txs are counted in the `approvals` line the
2124
- session's own total is derived from. Verified at `0x0A71382207a980c23E26a54F2b815072da41761A`:
2125
- `uri(id)` returns a `data:image/svg+xml;base64` image with zero `http(s)` anywhere in the document.
2126
- `EditionImage` also now treats `--onchain-image` as implying on-chain resolution — every sibling lane
2127
- already did, and without it the flag would have demanded a resolver URL for a drop whose bytes are
2128
- already on-chain.
2129
- - **`deploy-code --copies` accepts on-chain `--dep`/`--dep-registry`.** `EditionCode` already inherited
2130
- the `Dependencies` extension and called `_initDependencies()`; the legs come from the SDK's shared
2131
- `dependencySetupCalls`, so the two lanes cannot encode `setDependency` differently. So "an edition of
2132
- my p5 sketch, with p5 coming from the chain" is a real lane now — verified on Sepolia at
2133
- `0xC403CB9B956Ca814F1934d8d2A44fc1D2023EFB3`, where `abx verify` reports **chain-complete — every
2134
- dependency resolves to proven on-chain bytes; no server, gateway, or CDN in the graph**, with a 233 KB
2135
- inline `animation_url`.
2136
- - **The cold lane stays refused for `--onchain-image`**, deliberately and identically on all four paths:
2137
- staging is a sequence where each chunk tx's receipt feeds the next, so it cannot be signed offline.
2138
- This replaces an edition-specific "tracked as a follow-up" note with the real, shared reason.
2139
-
2140
- Still open on the edition lanes, and recorded in B28: `deploy-code --copies` has no `--code-dir` or
2141
- field renderers, and `deploy-code --resume` has no edition path (that one is genuinely new per-id diff
2142
- logic, not a port).
2143
-
2144
- **Every command now has one unknown-flag posture.** Roughly 35 commands — `state`, `add`, `index`,
2145
- `migrate`, `configure-param`, `set-field`, `set-royalty`, `set-minter`, `pause`/`unpause`, `verify`, … —
2146
- silently ignored an unrecognized flag, so `abx set-royalty --bsp 500` did something quietly different
2147
- from what was asked. Allowlists now live in one file and are applied once in the dispatcher; it warns
2148
- rather than refuses (a false refusal would break a working script, and `deploy*` still refuses via its
2149
- own exhaustive sets).
2150
-
2151
- The obvious implementation — deriving each allowlist from the command's own `usage` string — was tried
2152
- and **rejected on evidence**: shared helpers read flags that appear in no usage string anywhere
2153
- (`storageOverrides()` reads twelve, pulled in by six commands; `--remote-token` by five), so deriving
2154
- would have warned on valid flags. That is worse than the silence it replaces, because a notice people
2155
- learn to distrust stops working for the real cases too. The sets are explicit instead, composed from
2156
- named groups, and a test reads each command's `--help` to assert every flag it **documents** is accepted
2157
- — the direction that would produce false warnings.
2158
-
2159
- **`abx tokens <addr> --holder 0x..`** adds a `held` column — `balanceOf(holder, id)` per id, a live head
2160
- read. It answers the one question supply/cap cannot, and the one a funded agent had to leave blank after
2161
- transferring a copy (it correctly declined to fabricate the recipient's balance rather than guess).
2162
- Refused on a 721, where the owner column already answers it. Also: `deploy-series`' `[2] On-chain
2163
- renderer` step now prints the renderer address instead of an empty header, and `abx verify` no longer
2164
- ends in two ⚠ on a healthy fully-on-chain **image** drop (the code-project checks are scoped to the code
2165
- twins; a static drop has no program and no `animation_url` by design).
2166
-
2167
- - 528c6c6: ERC-1155 editions: the trust-anchor and on-chain-URI checks told the truth about 721s only, and four
2168
- membrane gaps around them (2026-08-05 wave-4 agent sweep — 12 cold agents, one funded end-to-end run
2169
- on Base Sepolia).
2170
-
2171
- The 1155 contracts themselves were fine: a funded run took an edition from deploy through
2172
- `tokenuri` → mint more copies → transfer one copy → lower the per-id cap, verifying every step
2173
- against the chain. What shipped wrong was everything that _describes_ an edition.
2174
-
2175
- - **`abx verify` reported EVERY canonical edition as `canonical: NO`.** `detectCanonicalFactory`
2176
- probed only the three 721 anchors and, on no match, fell back to the 721 1/1 factory — whose
2177
- `isAbxClone` answers **false**, not "unknown". So the one signal platforms allowlist against was
2178
- confidently inverted for the entire edition line (the function's own docstring promised
2179
- "canonicity just shows unverified, never wrong"). All six anchors are now probed. **Editions
2180
- registered before this fix keep the wrong factory in the local projection** — `abx forget <addr>`
2181
- then `abx add <addr>` re-detects it.
2182
- - **`abx verify`'s on-chain-URI probe called the ERC-721 `tokenURI` selector on editions**, which
2183
- exposes `uri(id)`. It reverted for every edition, so verify could never confirm that a
2184
- fully-on-chain edition resolves — and it printed the raw multi-line viem dump (Contract Call /
2185
- args / Docs / Version) into a creator-facing readout. The probe now switches on `contractType`
2186
- (matching `reconstruct.ts`, which already did), reports which accessor it read so an edition is
2187
- never described in 721 terms, and readout errors are trimmed to their first line.
2188
- - **An address with no contract was reported as a specific type: `OneOfOneImage`.**
2189
- `detectTokenKind`'s three probes are each `try/catch → false`, so nothing-deployed and
2190
- no-extensions-composed were indistinguishable and the ladder fell through to its `1of1` default.
2191
- `abx set-max-supply` and `abx minter buy` then told edition owners _"…is a OneOfOneImage (721) —
2192
- drop your edition flags"_ for a mistyped address or, far more often, the wrong `ABX_CHAIN` (the
2193
- default is base-sepolia, so any Sepolia contract hit this immediately) — advice that removes the
2194
- _correct_ flags. The existence check now lives inside `detectTokenKind`, so all 13 call sites
2195
- inherit it; it fails **open** on an unreadable `getCode`, so an RPC blip never becomes a
2196
- "no contract" claim.
2197
- - **`abx transfer --token-id <n>` silently transferred id 0.** `transfer` spells the id `--token`
2198
- while every sibling command (`mint`, `set-max-supply`, `minter …`) spells it `--token-id`, and an
2199
- unrecognized flag was simply ignored — so an agent that learned the name from `mint` moved the
2200
- **wrong artwork** with no warning. `--token-id` is now an accepted alias; disagreeing values are
2201
- refused rather than silently preferring one.
2202
- - **The edition deploy previews dropped lines their 721 twins print.** All three edition lanes
2203
- omitted `approvals N wallet approval(s)` from the dry-run readout — which the skill promises "every
2204
- preview" prints and tells the agent to state up front, so on an edition the agent had nothing to
2205
- tell the creator about how many wallet prompts were coming (`deploy-code --copies` had it only in
2206
- the `--confirm` sentence, which is off by default). `deploy --copies` and `deploy-series --copies`
2207
- also omitted the "resolves ON-CHAIN via the renderer — no resolver, no server" line, which is
2208
- precisely the guarantee an edition creator is asking about, and `deploy-series --copies` never
2209
- showed `paused`. All six lanes now print the approval count; the 1155 paths say `uri()`.
2210
- - **Owner-op writes now surface a stray flag instead of swallowing it.** `mint`, `transfer`,
2211
- `set-max-supply`, and `minter configure|show|buy` warn on an unrecognized flag (warn, not refuse —
2212
- per `unknownFlags`' contract, a false warning must never break a script). These are the commands
2213
- where the 1155 semantics live in _optional_ flags that default rather than fail: a typo'd
2214
- `--amount 50` minted 1 copy and a typo'd `--quantity 5` bought 1 and paid 1×, both in total
2215
- silence. The broader gap — ~35 commands with no unknown-flag notice at all — is filed as B30.
2216
-
2217
- A second verification sweep (six more cold agents, one funded) confirmed each fix above from a fresh
2218
- start and turned up four more, all fixed here:
2219
-
2220
- - **A supply cap could be raised in `--dry-run` and only revert on send.** Both twins —
2221
- `set-max-invocations` (721) and `set-max-supply` (1155) — printed the new cap as fact for a value
2222
- the chain forbids, then reverted for real. A clean dry run is read as permission to send. Both now
2223
- read the current cap first and refuse up front; the 1155 side also refuses a cap **below** live
2224
- supply (the second way it reverted). Both guards fail **open** on an unreadable getter, since the
2225
- chain enforces the invariant anyway. (The 721 guard needed the Series ABI — reading `maxInvocations`
2226
- through the 1/1 ABI that `read()` uses throws, which would have made the guard silently never fire.)
2227
- - **`abx refresh` told edition owners ERC-4906 had already pinged marketplaces.** An ERC-1155 edition
2228
- emits no ERC-4906 at all — that is precisely why `ping-uri` exists — so the one sentence a creator
2229
- reads after editing metadata said "already handled" when nothing had been, and `ping-uri --help`
2230
- said the opposite. Two commands contradicting each other on the new lane. `refresh` is now
2231
- kind-aware and names `ping-uri` for editions.
2232
- - **`abx verify` ended in two ⚠ on a perfectly healthy fully-on-chain image drop** — "generator
2233
- reports NO code" and "no animation_url" — because the code-project lane runs whenever a tokenURI
2234
- renderer is set, which is true for every on-chain project, image or code. A static image has no
2235
- program and no `animation_url` by design. Three separate agents named this as the worst thing about
2236
- `verify`, and one said plainly it would make a creator distrust future real warnings. The code-lane
2237
- checks are now scoped to the code twins; a static drop gets one informational line instead. (This
2238
- was pre-existing on the 721 path too, not an editions regression.)
2239
- - **`EditionImage`'s SVG-only refusal was a thinner copy of its 721 twin's** — the Series version names
2240
- three routes (`--onchain-image`, `--backend ipfs|arweave`, or host off-chain); the edition version
2241
- said only "drop `--onchain-uri`". A cold agent asked for "3 artworks × 25 copies each" read that,
2242
- concluded there was no no-server option for one contract, and **fanned the collection out into three
2243
- separate single-artwork contracts**. The refusal now states the real options for the lane and says
2244
- explicitly that the off-chain-image-URL-in-on-chain-JSON route exists only per-artwork via
2245
- `abx deploy --copies` — one contract per artwork, not one collection. The underlying capability gap
2246
- is recorded in B28.
2247
-
2248
- One knock-on from the first fix: the `false` branch of the canonical readout said _"not a clone of the
2249
- **configured** factory"_ (singular), which now understates the check and misdirects the reader — six
2250
- anchors are probed, so a `false` means no trust anchor the CLI knows deployed this contract, and the
2251
- usual real cause is a **superseded** factory or a hand-deployed contract, not a misconfiguration. It
2252
- now says so, and points at `abx doctor` for the current anchors.
2253
-
2254
- Also: `deploy --help` no longer calls the edition sale stack (`--minter`/`--primary-payee`/
2255
- `--unpaused`) "required" — it is optional, and an agent handed a creator hardcoded addresses as
2256
- mandatory because of it; `set-field --help` now lists all eleven `--representation` values (it showed
2257
- seven, omitting `url-template`, `renderer`, `sha256`, `inline-gzip`); and `abx doctor`'s label column
2258
- widened so `edition factory` no longer runs into its address.
2259
-
2260
- The skill-drift ✗ now names **which** copy is stale and prescribes the command that actually clears
2261
- it — `abx skill install` writes the project-local copy only, so a stale **global** copy produced a ✗
2262
- whose own remedy could not fix it. Six agents hit it in one sweep; several re-ran the install
2263
- repeatedly and one left its sandbox to read `~/.claude/skills` to work out what the tool meant.
2264
-
2265
- - 528c6c6: Wave-6 validation sweep: stop three help strings from denying features that work, make `storage show`
2266
- honor the flags it advertises, and name the byte custody on both Series lanes.
2267
-
2268
- Nine cold agents re-drove the edition surface after the B28/B30/B31 work. **Every new lane is reachable
2269
- and no re-run regressed** — the situation that previously fanned "3 artworks × 25 copies" out into three
2270
- separate contracts now reports _"Contracts deployed: 1, not 3"_; `--onchain-image` + edition + browser
2271
- wallet went 4/5 at Haiku after being refused outright; the on-chain-dep edition lane went 3/5 → 4/5; the
2272
- wrong-chain diagnosis went 2/5 → 3/5 on the strength of the new monotonic-cap guard. **B30 produced zero
2273
- false warnings across all nine**, including a run that control-tested the mechanism with a deliberate
2274
- bogus flag, and a funded run that exercised ~10 commands with real writes.
2275
-
2276
- What the sweep found was mostly **documentation still describing behavior the same branch had replaced** —
2277
- the failure mode a test suite cannot catch, because tests assert what the code does and none of them read
2278
- the prose that tells an agent what to try:
2279
-
2280
- - **`deploy --help`** said `--onchain-image` is "hot lane only on an edition (not yet wired for `--sign`)".
2281
- It works on the wallet lane. Now states the real rule: hot and `--sign` both work; `--unsigned` is
2282
- refused everywhere, 721 and edition alike, because each chunk tx's receipt feeds the next.
2283
- - **`deploy-code --help`** listed `--dep`/`--dep-registry` among flags "refused in code" for `--copies`.
2284
- They work. An agent verified all four flags directly and called this out as _"the dangerous direction:
2285
- it would talk a creator out of a feature that actually works."_ Now says they work, and names the
2286
- Sepolia-only registry constraint.
2287
- - **The `EditionImage` SVG-only refusal** — a message rewritten earlier in the same branch — still said
2288
- `--onchain-image` "is not wired on the edition-series lane yet", after that branch wired it. It now
2289
- lists all four real routes for raster art in one contract.
2290
- - **`abx refresh`'s ERC-4906 claim had no edition caveat** in the skill or `site/using-abx/guides/operate`,
2291
- even though the CLI itself was made kind-aware earlier in the branch. The skill's _"`abx refresh` is the
2292
- only nudge"_ was flatly false for an edition, where `ping-uri` is required.
2293
-
2294
- Two real product fixes, both surfaced by the new warning doing its job:
2295
-
2296
- - **`abx storage show` now honors `--backend`/`--gateway`/the rest of the storage override set.** It read
2297
- env-only while its own output said _"choose per command: `--backend` … `--gateway`"_ — advertising flags
2298
- it then discarded and always reporting `backend: fs`. This was invisible until every command grew an
2299
- unknown-flag notice, which correctly flagged it (the flags really were ignored). A resolution preview
2300
- that can't preview a resolution is the one thing it exists to do. Provenance now reports `--backend` as
2301
- a source, alongside env and default.
2302
- - **Both `deploy-series` lanes now name the byte custody** (`storage: ipfs (--backend) — byte custody for
2303
- the image(s)`), as the 1/1 lane always has. A creator asking for "images on IPFS" had no way to confirm
2304
- from the preview that their collection would pin there; the flag was accepted in silence.
2305
-
2306
- Also documented, not changed: the ids × copies arithmetic. `deploy-code --copies 100` takes its id space
2307
- from `--max`, which **defaults to 16** — so "an edition of 100" without `--max 1` means up to 16 different
2308
- variations × 100 copies each. The dry-run states the real shape (`up to N id(s), M copies each`) and always
2309
- did; nothing taught the translation, so the skill now does, for all three commands.
2310
-
2311
- Three legibility nits were routed to B32 rather than fixed: `abx state` naming a contract "Series" where a
2312
- refusal calls it "SeriesCode"; the Base-Sepolia default appearing only in a subordinate clause of the
2313
- "no contract" error (three straight waves have lost time to it); and `--onchain-image` on a folder of large
2314
- rasters honestly reporting 35 wallet approvals with no nudge toward the cheaper lane.
2315
-
2316
- Two agent claims were checked and **rejected** so they don't get "fixed" into regressions: the skill does
2317
- _not_ say `--onchain-image` is refused on an edition series (it documents the `--unsigned`-only refusal
2318
- correctly), and `abx predict` does _not_ silently pick the 721 factory without `--copies` — it labels the
2319
- lane, yields a different address, and echoes `--copies` in its reproduce line.
2320
-
2321
- - Updated dependencies [528c6c6]
2322
- - @artblocks/abx-sdk@0.1.0-alpha.13
2323
- - @artblocks/abx-indexer@0.1.0-alpha.14
2324
- - @artblocks/abx-storage@0.1.0-alpha.13
2325
- - @artblocks/abx-token-api@0.1.0-alpha.16
2326
-
2327
- ## 0.1.0-alpha.20
2328
-
2329
- ### Minor Changes
2330
-
2331
- - afa9dd4: ERC-1155 editions ship with full parity: a Series is many unique tokens (721); an **Edition** is
2332
- many copies of a token (1155). The creator's word is **copies** — `abx deploy img.png --copies 100`
2333
- (a single-artwork edition, `open` = uncapped), `abx deploy-series ./art --copies 50` (each image an
2334
- edition), `abx deploy-code sketch.js --copies 25` (code editions). Without `--copies`, the 721
2335
- lanes are unchanged.
2336
-
2337
- Three new canonical contracts twin the 721 lineup — `OneOfOneEdition`, `EditionImage`,
2338
- `EditionCode` — plus `AbxFixedPriceMinter1155`, a per-id fixed-price sale singleton
2339
- (`minter configure/buy --token-id [--quantity]`). New owner ops: `mint --token-id --amount`,
2340
- `transfer --amount`, `set-max-supply` (per-id cap, only ever decreases), `ping-uri` (re-emit the
2341
- native `URI` event after a re-point). ERC-1155C creator-token enforcement is the same `--721c`
2342
- opt-in (same validator registry and ERC-165 ids as 721C). Editions announce one new extension
2343
- (`abx.extension.edition-supply`, per-id supply/cap); everything else — metadata fields, params,
2344
- royalties, renderer, storage, effects — is the same protocol surface on both standards.
2345
-
2346
- **Integrator surface (audience line):** additive only. `ProjectState.contractType` gains
2347
- `'1of1-edition' | 'edition' | 'edition-code'`; `TokenState` gains `supply`/`maxSupply`/`holders`
2348
- (editions only; `minted` means supply > 0 there); the spine ABI now decodes
2349
- `TransferSingle`/`TransferBatch`/`URI`/`MaxSupplyUpdated`; `ChainDeployment` gains the four
2350
- edition anchors (recorded for Sepolia + Base Sepolia, CREATE2-identical); token-api summaries gain
2351
- an optional `copies` field. No existing export, route, column, or event shape changed.
2352
-
2353
- ### Patch Changes
2354
-
2355
- - 8c254d5: The self-host projection no longer stores or pre-fetches composed URI documents, and the event log
2356
- is append-only (from the abx-services 2026-08-05 projection memo, `docs/11`).
2357
-
2358
- - **`contractURI`/`tokenURI` are live reads, never projected.** `reconstructProject`/
2359
- `reconstructIncremental` gain `readUriDocuments` (default **false**) — with it off, the whole
2360
- `contractURI`/`tokenURI` head-read batch is skipped entirely, not fetched-and-discarded. These
2361
- fields have no settled value (a renderer can change the composed document with no log at all) and,
2362
- on the on-chain lane, can run hundreds of KB per token — a real 32-token project measured 13.4 MB
2363
- serialized, 99.8% of it `tokenURI`. A stale-but-rendering `data:` URI is worse than a missing one,
2364
- because nothing about it looks wrong. `abx demo` is the one caller that passes `readUriDocuments:
2365
- true`, for its read-back teaching step; `abx deploy`/`deploy-series`/`deploy-code` don't, and
2366
- `abx verify`/`tokenuri`/`contracturi` were already doing independent live reads. **For
2367
- integrators:** `ProjectState.contractURI` / `TokenState.tokenURI` are now `null` unless you opt in
2368
- — including in a self-host node's `/api/project/:addr` responses (shape unchanged, values now
2369
- null). Nothing that composes served metadata reads either field (the settled `fields`/
2370
- `collectionFields` do); the settled scalars — `tokenURIRenderer`/`tokenURILocked`/
2371
- `contractURIRenderer`/`contractURILocked` — are unaffected and still projected.
2372
- - **The events table is append-only.** `seq` is now chain-derived (`(blockNumber << 32) |
2373
- logIndex`, BigInt-safe) instead of the array index at write time, so it's stable across re-folds —
2374
- a re-index appends via `ON CONFLICT DO NOTHING` instead of deleting and reinserting the whole
2375
- history on every delta. `putProject` upserts the project and token rows in place (a token whose id
2376
- vanishes from state is still removed) rather than delete-and-reinsert. `deregister` still deletes
2377
- a forgotten project's events in full; a routine re-fold does not. **A full replay (`abx index
2378
- --full`) purges the project's event log before rebuilding it** — append-only must not let a
2379
- reorg-replaced event survive as a stale row, so the documented repair path stays a real repair.
2380
- Existing stores migrate their positional `seq` values to the chain-derived form once, automatically
2381
- (`PRAGMA user_version`-gated, transactional).
2382
- - **Self-healing eventCount guard.** An incremental fold that would reduce `eventCount` below what's
2383
- already stored is discarded rather than written, with one automatic full-reconstruct fallback (the
2384
- memo's own war story: a bad fold once silently zeroed a live projection with nothing to catch it).
2385
- - **New self-host stores open with `auto_vacuum = INCREMENTAL`.** Existing stores are unaffected
2386
- (the pragma only takes on a database with no tables yet) — recorded as backlog B29.
2387
-
2388
- - Updated dependencies [afa9dd4]
2389
- - Updated dependencies [8c254d5]
2390
- - @artblocks/abx-sdk@0.1.0-alpha.12
2391
- - @artblocks/abx-indexer@0.1.0-alpha.13
2392
- - @artblocks/abx-token-api@0.1.0-alpha.15
2393
- - @artblocks/abx-storage@0.1.0-alpha.12
2394
-
2395
- ## 0.1.0-alpha.19
2396
-
2397
- ### Minor Changes
2398
-
2399
- - d40caf4: `abx remote <name|url> --conformance` — self-certify a resolver from the published CLI
2400
-
2401
- The self-verify fixture that used to live only as a repo-local script
2402
- (`scripts/conformance-remote-service.mjs`, `pnpm conformance`) is now a mode on the existing `remote`
2403
- command rather than a new one (command-count discipline): `abx remote <name|url> --conformance
2404
- [--remote-token <t>] [--chain-id <n>] [--address <a>] [--from-block <n>]`.
2405
-
2406
- This is what lets a third party self-certify a hosted resolver — or CI-gate on one — with nothing but
2407
- `npm i -g @artblocks/abx-cli` and a base URL: no repo checkout, and a bare URL + `--remote-token`
2408
- needs no `.env` setup. It reuses the same `--remote` name/URL/token resolution as every other remote
2409
- command, prints one ✓/✗/· line per assertion (descriptor shape, unauthenticated writes refused, the
2410
- read-plane's error taxonomy, every declared interface's routes, and — with `--chain-id` + `--address`
2411
- naming a contract you own — the full register → poll-to-live → status → reindex → deregister loop),
2412
- and sets a non-zero exit code on any failure.
2413
-
2414
- The assertions themselves moved to `packages/cli/src/conformance.ts`, importable by both the CLI
2415
- command and the (now much thinner) root script, so `pnpm conformance` and
2416
- `scripts/e2e-remote-resolver.sh` keep working unchanged against the same logic.
2417
-
2418
- ### Patch Changes
2419
-
2420
- - d40caf4: Two first-party consumers adopt the backoff/gateway/data-URI helpers hoisted into `@artblocks/abx-sdk`
2421
- last round, and `mint-page` refuses a 1/1 target instead of scaffolding a page that hangs. Drop-in for
2422
- everyone — no behavior change in the adoption itself, and the mint-page refusal only fires for a
2423
- contract type the page never worked against.
2424
-
2425
- **`token-api`'s chain watcher now calls `exponentialBackoffDelay`/`linearBackoffDelay` instead of
2426
- re-deriving them inline.** `dueForRetry`'s failed-project retry and the tick loop's repeated-failure
2427
- delay were the sdk helpers reimplemented by hand; both now call the shared functions, with the exact
2428
- index mapping (`min(attempts, 8) + 1` into the 1-indexed exponential helper) that reproduces identical
2429
- delays for every `attempts`/`failures` value — proved by an old-formula-vs-sdk-helper equality table
2430
- in `packages/token-api/test/watcher-backoff.test.ts` (attempts/failures 0..12, several `baseMs`
2431
- values, and the cap boundary explicitly).
2432
-
2433
- **The `mint-page` template's generated app now reads gateway URLs and `data:` URIs through the sdk**
2434
- (`gatewayUrlFor`/`resolveGatewayBase`/`parseDataUri`) instead of a hand-rolled `ipfs://`-slicing
2435
- `toGateway` and a hand-parsed comma-split in `fetchJson`. Same output for every input (plain
2436
- `https://`, empty string, `ipfs://`, `ar://`, base64 and percent-encoded `data:` bodies) — verified
2437
- byte-for-byte before the swap, and pinned by a new mintpage.test.ts assertion that the emitted
2438
- `lib/meta.ts` imports the sdk helpers and no longer contains the old hand-rolled body.
2439
-
2440
- **`mint-page` now refuses a non-Series target.** The generated page reads `maxInvocations`/`paused`
2441
- and mints through the shared fixed-price minter — a 1/1 (`OneOfOneImage`) has none of that, so pointing
2442
- `mint-page` at one silently wrote a page that sat on "Loading…" forever with no diagnostic. `cmdMintPage`
2443
- now probes the same ERC-165 `supportsInterface(IAbxSequentialMint)` `sdk/anchors.ts`'s
2444
- `isCurrentSeriesFactory` uses, and refuses with a `CliError` pointing at `abx deploy-series --dir
2445
- <folder> --count 1` (matching the existing guidance in `sales.mdx`). A probe that can't run at all (RPC
2446
- unreachable) is left undecided and does NOT refuse — scaffolding stays usable offline, same tolerance
2447
- the existing best-effort `name()` read already has.
2448
-
2449
- Also folded in: `PUBLIC_RPC` (the mint-page template's keyless-RPC table) gains `base` and
2450
- `base-sepolia` — the toolkit's own default chain was missing from it, so every default-chain deploy
2451
- needed a manual `--rpc` just to scaffold its own mint page.
2452
-
2453
- - Updated dependencies [d40caf4]
2454
- - Updated dependencies [d40caf4]
2455
- - Updated dependencies [d40caf4]
2456
- - @artblocks/abx-sdk@0.1.0-alpha.11
2457
- - @artblocks/abx-token-api@0.1.0-alpha.14
2458
- - @artblocks/abx-storage@0.1.0-alpha.11
2459
- - @artblocks/abx-indexer@0.1.0-alpha.12
2460
-
2461
- ## 0.1.0-alpha.18
2462
-
2463
- ### Minor Changes
2464
-
2465
- - 11fa933: Phase 5 interface unifications: the self resolver becomes a named remote, doctor absorbs the
2466
- version/install/provenance ladder, deploy previews stop showing addresses they can't keep,
2467
- approvals are counted honestly, render×storage combos are validated once, and storage config gets
2468
- a real probe.
2469
-
2470
- - **Self resolver is a named remote (breaking, client-side only)**: the CLIENT credential for the
2471
- local/self resolver moves from `ABX_RESOLVER_ADMIN_TOKEN` to the named-remote grammar
2472
- (`ABX_REMOTE_SELF_URL` / `ABX_REMOTE_SELF_TOKEN` — "self" is just a conventional remote name, zero
2473
- special-casing). The SERVER side (token-api control-plane auth, `provision.ts`, the Fly.io secret)
2474
- keeps its old name — it's the service's own config, not a client credential, so deployed resolvers
2475
- need no change. The old client var, if still set, is now a pointed `CliError` naming both new vars
2476
- (no silent fallback); `abx doctor` flags it too, and the near-miss `_KEY`-vs-`_TOKEN` detector keeps
2477
- working under the unified grammar.
2478
- - **`abx doctor` absorbs the version/install/provenance ladder** the skill used to only teach in
2479
- prose: one new ✓/✗ block for binary provenance (source checkout / npm install / npx — the silent-
2480
- stale-npx-cache trap), npm currency (reuses the existing cached update check), and skill↔CLI
2481
- version match (reuses `abx skill`'s own discovery). Each row names the exact fix.
2482
- - **Deploy previews stop showing addresses they can't keep**: `--dry-run` (`deploy` /
2483
- `deploy-series` / `deploy-code`) **without `--salt`** no longer prints a "deterministic address" —
2484
- that salt was just freshly, randomly reserved, so the address was real for that one preview and
2485
- never reproducible by a plain re-run. It now prints the salt itself, prominently, plus how to pin
2486
- it: re-run with `--salt <shown>` (guaranteed same address), or `abx predict --salt <shown> --for
2487
- <signer>`. **With `--salt`, the address prints exactly as before** (it IS stable) — `abx predict`
2488
- is unchanged. The same rule applies to `--json`: without `--salt`, `address` reports `null` rather
2489
- than a value the real deploy won't land at (`saltPinned` still says why).
2490
- - **`approvals` — a labeled, honest signature count**: every deploy-family `--dry-run` preview and
2491
- `--confirm` summary now carries one uniform `approvals N wallet approval(s)` line/clause — the
2492
- number of **wallet TX signatures** the real run will ask for (a connected-wallet storage upload,
2493
- e.g. Arweave via `--storage-signer eth`, is a message signature, not a transaction, and stays
2494
- listed separately, as it already was). It's derived from the exact same staging/setup-multicall
2495
- math that sizes the wallet-lane session's own `total`, so the two can never disagree — this caught
2496
- (and fixed) `deploy-code`'s wallet-lane session `total` being hardcoded to `2` even on the 1-tx path
2497
- (no chunks/schema/deps/on-chain-uri legs/setup-carried mints), which would have shown "transaction
2498
- 1 of 2" and then silently never asked for a second.
2499
- - **One validator for render×storage combos**: `@artblocks/abx-storage` gains
2500
- `validateRenderStorageCombo()` (`content-plan.ts`) — the single source of truth for two known-bad
2501
- configurations, each checked against the ACTUAL fact that makes it bad (not against an unrelated
2502
- flag): (1) `--image-base` needs a backend that can overwrite a stable per-token key in place; an
2503
- ipfs/arweave-_shaped URL_ is refused regardless of this deploy's own `--backend`, because those are
2504
- content-addressed — a re-upload gets a new address, so no fixed URL can point at it. (2) publishing
2505
- a render to a resolver that doesn't share this machine's disk needs a backend that can hand back a
2506
- public URL at all — `fs` (or `cloud` with no public base) can't. `deploy-code` wires this into BOTH
2507
- its real-run refusal (`--image-base`) and a new `render/storage ✓|✗ <reason>` dry-run row;
2508
- `requirePublishableBackend` (the existing `abx render --remote` / `abx effects` guard) now consults
2509
- the same validator for its ok/not-ok decision, so the two surfaces can't drift apart.
2510
- - **`abx storage show --check`**: a real read/write against the resolved storage config, not just
2511
- "is it configured." `cloud` PUTs a tiny object through the signed API and GETs it back over the
2512
- PUBLIC base with a plain unsigned fetch — the only check that catches the R2/S3
2513
- endpoint-vs-public-base trap (`health()` alone only proves the API credentials work); on failure
2514
- both URLs print, so the mismatch is visible. `ipfs`/`fs` reuse their existing `health()` verbatim
2515
- (gateway/API reachability, dir writability — no upload, no pin — reused, not reimplemented).
2516
- `arweave` adds an identity+balance READ, never a paid upload. Exit code is meaningful (0 ok / 1 any
2517
- ✗) so a script can gate a launch on it. `abx doctor`'s storage row now runs the same fuller probe
2518
- (bounded to 1.5s, matching its other fast network checks) instead of a bare `health()` call.
2519
-
2520
- ### Patch Changes
2521
-
2522
- - 11fa933: The remaining phases of the simplification refactor that hadn't yet gotten a changeset: the CLI's
2523
- internal module split, the token-api/effects/mint-page convergence on the SDK, the shipped skill's
2524
- rewrite for the simplified surface, and a new SDK README.
2525
-
2526
- - **`abx`'s `main.ts` split into domain command modules** (`commands/{deploy,project,reads,service,
2527
- scaffold,storage}.ts`, shared `output.ts`/`errors.ts`), with one exit-discipline rule
2528
- (`process.exitCode` + return, or a typed `CliError`, everywhere — bare `process.exit` only at the
2529
- entry guard, the top-level catch, and the keep-alive SIGINT handler). Purely internal: a 207-fixture
2530
- byte-diff matrix (every help text, dry-run, error path, and exit code) confirmed identical output
2531
- before and after.
2532
- - **token-api / effects / mint-page converge on the SDK**: `@artblocks/abx-storage` gains one
2533
- `resolveGatewayBase` (`readiness.ts`), replacing three near-identical copies (two in token-api, one
2534
- inline in storage itself); token-api exports `buildGeneratorDocument` so the CLI's `abx preview`
2535
- consumes the real generator-document assembler instead of a hand-kept duplicate; the effects runner
2536
- now resolves its config via the SDK's `readEnv` and gets a `makePublicClient` fallback transport, so
2537
- `ABX_RPC_URL` accepts a comma-separated failover list like every other RPC var; the scaffolded
2538
- mint-page app now imports ABIs from `@artblocks/abx-sdk/abi` and a browser-safe `makePublicClient` +
2539
- typed `readSaleConfig` instead of hand-rolled fetch/decode, and pins its generated `package.json` to
2540
- the SDK's _resolved_ version via a new `@artblocks/abx-sdk/package.json` export (alpha version
2541
- counters diverge per package under changesets, so pinning the CLI's own number could produce an
2542
- unsatisfiable range).
2543
- - **The shipped skill (`.claude/skills/abx-self-host/`) is rewritten for the surface phases 0–5
2544
- actually shipped**: every warning made obsolete by an enforcement is deleted rather than softened —
2545
- predict-only deploy-preview addresses, the `approvals N` line, the single `ABX_REMOTE_SELF_*`
2546
- credential grammar, `doctor`'s version/provenance ladder, `storage show --check`, the render/storage
2547
- combo validator's dry-run row, and `ABX_DEPLOYER_PK` as the only key name. Retired names swept from
2548
- `dev-loop-test`, the agent-eval scenarios, and spec prose. The skill ships bundled inside this CLI
2549
- package (co-versioned via `SKILL.md` frontmatter), so it rides this same patch.
2550
- - **New `packages/sdk/README.md`**: what the SDK is, the send-injection model (`PreparedTx` +
2551
- `SendTx`, `makeHotSender` for a hot key, bring-your-own for a wallet/multisig), a complete
2552
- deploy → upload → mint → read walkthrough against real exports, and browser-use notes (explicit
2553
- `rpcUrls`, no env, the `/node` subpath is Node-only). Included in the npm tarball automatically
2554
- (README is one of the files npm always packs, regardless of the `files` allowlist).
2555
-
2556
- - 11fa933: Two ripouts, no functional loss: the retired `params.keys` read fallback in `abx state`, and the `abx changelog` command.
2557
-
2558
- **`abx state`'s legacy `params.keys` fallback.** On-chain param enumeration (`contractParamKeys` /
2559
- `tokenParamKeys` / `paramSchemaKeys`) shipped a while back and every project deployed since enumerates
2560
- its own param surface — nothing writes the old `params.keys` CSV any more. The only thing still reading
2561
- it was `abx state`, so it could keep describing the handful of pre-enumeration testnet projects that
2562
- predate the switch. That reader (`readLegacyParamsKeys`) and its `legacy`-branching in `cmdStateBody`
2563
- are gone; `abx state` now shows exactly what it reads from on-chain enumeration, an empty PostParams
2564
- list for a pre-enumeration contract included. (The repoint guard that refuses to point a legacy token's
2565
- field renderer at the canonical generator is a separate, still-live safeguard — untouched.)
2566
-
2567
- **`abx changelog`.** A `CHANGELOG.md`-reading command that duplicated what any agent or human can do by
2568
- reading the file that already ships with the package (`package.json` `files`) — one more command to
2569
- document and maintain for a read `cat` already covers. `CHANGELOG.md` keeps shipping; the update-nag
2570
- banner and the docs now point at the file and at npm's version list
2571
- (`https://www.npmjs.com/package/@artblocks/abx-cli?activeTab=versions`) instead of a command.
2572
-
2573
- - 11fa933: The extraction phase of the simplification refactor: business logic that lived only inside the CLI
2574
- is now importable — the CLI calls the same functions you can.
2575
-
2576
- **Into the SDK:** the five trust-anchor bootstraps (`ensureFactory`, `ensureSeriesFactory`,
2577
- `ensureSeriesCodeFactory`, `ensureRenderer`, `ensureSeedSource` — `anchors.ts`, on the same
2578
- injected-`send` + `onEvent` pattern as `ensureChunkStore`, with a typed `AnchorUnavailableError`),
2579
- `detectCanonicalFactory`/`resolveScanFloor`, the on-chain-URI setup composer (`onchain-uri.ts`),
2580
- interrupted-deploy resume planning (`resume.ts`), migration plan/parity reconciliation
2581
- (`migrate.ts`), the `abx.js` static analyzer (`inspect.ts`), dependency setup legs (`deps.ts`),
2582
- content-staging plans (`staging.ts` — `planStagedContent`, `stageFieldContent` with `StagingEvent`),
2583
- `mintedTokenIds`, and a typed `readSaleConfig` for the fixed-price minter.
2584
-
2585
- **Into storage:** `uploadAndLocate`, `repinNodeCustody` (the byte-custody half of migration),
2586
- `decideImageContentLane`, `assessStorageReadiness`/`assessTurboFunds` (the Arweave/Turbo funding
2587
- math), and `awaitLocatorReady` (poll a locator until it serves).
2588
-
2589
- **CLI hardening that fell out of the dedup:** one risk gate (`gatedSend`) now guards every write —
2590
- `--dry-run` and `--confirm` mean the same thing on every command, all owner-ops gain `--confirm`,
2591
- and a write reaching the send lane under `--dry-run` is structurally impossible (grep-enforced by
2592
- test). One `CHAIN` source of truth; one memoized local-indexer accessor.
2593
-
2594
- - 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.
2595
-
2596
- **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.
2597
-
2598
- **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.
2599
-
2600
- **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.
2601
-
2602
- **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.
2603
-
2604
- - Updated dependencies [11fa933]
2605
- - Updated dependencies [11fa933]
2606
- - Updated dependencies [11fa933]
2607
- - Updated dependencies [11fa933]
2608
- - @artblocks/abx-sdk@0.1.0-alpha.10
2609
- - @artblocks/abx-storage@0.1.0-alpha.10
2610
- - @artblocks/abx-token-api@0.1.0-alpha.13
2611
- - @artblocks/abx-indexer@0.1.0-alpha.11
2612
-
2613
- ## 0.1.0-alpha.17
2614
-
2615
- ### Patch Changes
2616
-
2617
- - df298d8: `abx attach` takes several `<key> <uri>` pairs and sends ONE transaction
2618
-
2619
- The safety half of backlog B20. The CLI's most-documented flow — mint, then attach each artifact, then
2620
- refresh — sent one transaction per step with **no all-or-nothing boundary**, so a failure partway
2621
- through left a permanently half-written token, and a mint cannot be undone. An integrator hit exactly
2622
- that and folded 8 operations into 1 transaction (846,556 gas) using the SDK's `batchOps` — a primitive
2623
- the CLI already shipped and did not call. Now it calls it.
2624
-
2625
- ```bash
2626
- abx attach 0x… stems ipfs://…/stems.wav score ipfs://…/score.pdf readme ar://…
2627
- # → 3 artifacts, one multicall: a revert lands NONE of them
2628
- ```
2629
-
2630
- Three things make the batch safe rather than merely shorter:
2631
-
2632
- - **Every pair is validated before anything is sent.** A bad locator in pair 5 stops pair 1 — otherwise
2633
- batching would defeat its own purpose.
2634
- - **A key repeated inside one batch is refused.** A field holds one active value, so the later write
2635
- would silently win — the same full-column-upsert hazard that was a real bug in the resolver's
2636
- `register` and a real trap in `lock-field`.
2637
- - **A single pair is unchanged.** `batchOps` passes a lone op through untouched, so the one-artifact
2638
- case sends the identical plain field transaction it always did — no multicall wrapper, no new gas.
2639
-
2640
- An odd number of positionals is refused and names the dangling argument, rather than silently ignoring
2641
- it.
2642
-
2643
- Still open in B20, and recorded there rather than quietly skipped: batching **`set-field` across several
2644
- fields** needs a flag-grammar decision first (`attach` batched cleanly because its arguments are
2645
- positional pairs; `set-field` takes one `--field` with a correlated `--text`/`--value`, and the parser
2646
- has no notion of correlated repeats). Same for an all-or-nothing "mint and configure" — mint and
2647
- configure are separate commands, so that needs a verb that owns both.
2648
-
2649
- - df298d8: `abx deploy-code --resume <address>` — finish a deploy whose setup transaction failed
2650
-
2651
- Closes backlog B15. A code project deploys in **two** transactions: create the clone, then one atomic
2652
- `multicall` carrying the script chunks, the PostParam schemas, the dependency declarations, the
2653
- on-chain-URI legs, and any reserve mints. There is no rollback. When the second one fails you own a
2654
- live-but-unusable contract — and the CREATE2 salt reserved for its address is **spent**, so the dry
2655
- run's pinned-salt reproduce command can never be run again. One reporter session produced three
2656
- orphaned contracts from three attempts; another produced five.
2657
-
2658
- The useful half of that report is that those contracts were **recoverable, not lost**: resending the
2659
- setup with an adequate gas limit completed one, after which `abx verify` reported chain-complete and the
2660
- token returned its on-chain `animation_url`. The tester did it by hand with `cast`. We deliberately
2661
- never documented that as a recipe — telling a creator to hand-assemble a multicall is worse than telling
2662
- them nothing — so this is the verb.
2663
-
2664
- ```bash
2665
- abx deploy-code --resume 0x… <the same content flags the original deploy used> --dry-run
2666
- ```
2667
-
2668
- It deploys nothing. It reads what the contract already holds and sends only the missing legs, in one
2669
- transaction — safe to run twice, and if nothing is missing it sends nothing and says so. Three judgments
2670
- carry it:
2671
-
2672
- - **Chunks compare by content, not by count.** A count check would call a chunk "present" when a hand
2673
- repair wrote different bytes at that index — and a hand repair with `cast` is exactly what happened.
2674
- - **A schema that already exists is left alone.** Re-writing one is an upsert that can strand values
2675
- already stored under it; that hazard belongs to `set-schema` and its guard, never to a repair.
2676
- - **Mints are a shortfall against current supply, never a re-send.** `mint` is the one non-idempotent
2677
- leg, and a token cannot be un-minted.
2678
-
2679
- `--salt`, `--721c`, `--bootstrap-factory` and `--mint-all` are refused rather than ignored: they all
2680
- describe how a contract is _created_, and this creates nothing. ERC-721C especially — enrollment is
2681
- deploy-time-only and permanent, so silently accepting the flag would imply it can be added later.
2682
-
2683
- It shares the deploy's own setup-leg builder, so a resume can never drift from what a fresh deploy would
2684
- have written — a second implementation of that sequence is the failure mode a repair verb most easily
2685
- introduces. Signing goes through the same choke point as every other write, so `--sign` / `--unsigned` /
2686
- the owner check / the chain-id guard all apply, and `--dry-run` previews the repair (one bug found and
2687
- fixed while building this: the deploy's dry-run block returned first, so `--resume … --dry-run` printed a
2688
- fresh-deploy plan with a newly-reserved salt and a different predicted address).
2689
-
2690
- 15 tests, including every diff branch and the four refusals.
2691
-
2692
- - df298d8: `--json` on every value-emitting command: `mint`, `deploy`, `deploy-series`, `deploy-code`, `state`, `verify`
2693
-
2694
- Closes backlog B19. The rule it enforces, from an integrator who drove the CLI from a server: **a value
2695
- a program needs must be obtainable without parsing prose.** They had to regex-scrape ANSI-coloured
2696
- stdout for every value — and an escape code was captured into a locator, written into a _stored_ player
2697
- URL, and 404'd in production. The cause was found only by inspecting stored bytes.
2698
-
2699
- Under `--json`, **stdout carries exactly one JSON document and nothing else.** Every narration line the
2700
- command would print for a human moves to **stderr** — diverted, not suppressed, because a human
2701
- watching a deploy still wants to see it while a program redirecting stdout still gets a clean parse.
2702
- (The update check already wrote to stderr for exactly this reason; this carries the instinct through to
2703
- the values themselves.)
2704
-
2705
- - **`mint --json`** → `{tokenIds, txHash, blockNumber, sent, …}`. The token ids come from the mint's own
2706
- `Transfer(from=0x0)` logs, not from re-reading `nextTokenId` afterwards — a concurrent mint would make
2707
- that answer wrong, and a number that is usually right is worse than no number. A `--count N` batch
2708
- reports all N.
2709
- - **`deploy` / `deploy-series` / `deploy-code --json`** → the address, emitted _the moment it is known_
2710
- rather than at the end, so a failure in the indexing steps that follow still leaves the caller with
2711
- the address of a contract that really exists. That matters most for `deploy-code`, which is two
2712
- transactions: if the setup tx fails, the address of the live-but-incomplete contract is exactly what a
2713
- recovery needs (backlog B15). With `--dry-run` it reports the **predicted** address plus
2714
- `saltPinned` — false meaning a plain re-run reserves a fresh salt and lands elsewhere, so a caller
2715
- must not treat it as reserved.
2716
- - **`state --json`** → the on-chain snapshot as data, with canonical **names** for param types and auth
2717
- legs rather than raw Solidity enum indices (a caller must not have to know the enum ordering), and
2718
- `undefined` (getter absent) kept distinct from a zero address (present, deliberately unset).
2719
- - **`verify --json`** → the findings, with `ok` matching the exit code. This is the one command a CI job
2720
- would gate on, since it already exits non-zero on a byte mismatch. The tri-states stay tri-states:
2721
- `canonical` and each check's `verified` are `true | false | null`, because collapsing "couldn't check"
2722
- into "failed" would report a normal state as a failure.
2723
-
2724
- The mechanism is a single shared helper (`withJson`) rather than a `quiet` flag threaded through every
2725
- command body — deliberately, since the alternative is touching dozens of call sites where the one that
2726
- gets missed is a stray line that corrupts a parse, i.e. the exact failure being fixed. A command that
2727
- emits no payload leaves stdout **empty** and exits non-zero rather than printing a `{}` a caller would
2728
- trust. 8 tests cover the channel contract itself, including that the swap is restored when a body
2729
- throws.
2730
-
2731
- - df298d8: `abx storage status <locator>` — is it retrievable yet, or only accepted?
2732
-
2733
- Backlog B21, and the second half of a gap two independent integrations hit eight days apart. An upload
2734
- service answers "accepted" the moment it holds your bytes; a gateway serves them only once they
2735
- propagate, and on Arweave that runs to minutes. Nothing in the upload result distinguished the two, so
2736
- the natural implementation — upload during a mint, write the locator into the token — mints a token
2737
- that renders broken for the first minutes of its life.
2738
-
2739
- The first reporter rebuilt this layer themselves (ranged GETs, a propagating/ready model, retry ladders
2740
- lengthened after measuring real times) and concluded "every serious integrator will rebuild some
2741
- version of this." The second published 32 renders and found **32/32 404ing on `arweave.net` while 22/32
2742
- already served from `permagate.io` and `vilenarios.com`**, with the uploader reporting `CONFIRMED`
2743
- throughout — and the expensive part is what a creator does next, since a placeholder on a fresh drop
2744
- reads as a failed render, so you re-run `abx render --force` and re-upload everything for nothing.
2745
-
2746
- That second observation shapes the design: propagation is **per-gateway**, so the check probes the
2747
- gateway your project actually uses _plus two others_, which buys a third verdict the reporters' own
2748
- two-state model couldn't express.
2749
-
2750
- - **`ready`** — your gateway serves the bytes. Safe to reference.
2751
- - **`propagating`** — another gateway serves them, so the data **provably exists** on the network and
2752
- yours is merely behind. Waiting is the fix, and the command says plainly not to re-upload.
2753
- - **`unreachable`** — nothing probed serves them. Deliberately _not_ called propagating: from outside,
2754
- a locator that is still settling and one that is simply wrong look identical, and reporting the
2755
- friendlier of the two is how a tool teaches someone to ignore it. When every gateway rejects the id
2756
- itself (a 4xx that isn't 404) rather than just missing it, that _is_ evidence, and the output says
2757
- "malformed locator, waiting will not fix it" — the inverse mistake of waiting out a typo costs more
2758
- than a needless re-upload.
2759
-
2760
- Accepts every form a locator arrives in (`ar://`, `ipfs://`, a gateway URL, a bare txid/CID, with a
2761
- directory path suffix), reads **headers only** via a ranged request with the body cancelled — so
2762
- checking a 40 MB asset doesn't download it — and **exits non-zero unless ready**, which makes waiting a
2763
- one-liner instead of a retry ladder: `until abx storage status <loc> --json; do sleep 10; done`.
2764
-
2765
- The primitive is `locatorStatus()` in `@artblocks/abx-storage`, not CLI-only (per B20): anything
2766
- programmatic should call it in-process rather than spawning the CLI per check. `abx render`'s existing
2767
- propagation note now points at the command, so the advisory has an answer attached.
2768
-
2769
- - df298d8: `abx tokens <address>` — every token's owner, seed, and params, from chain alone
2770
-
2771
- The most obvious post-deploy question about a generative collection — _what did the seeds actually
2772
- deal?_ — had no command. `abx verify` reports per-token minted/render status but no seed; `abx
2773
- tokenuri` prints one token and the seed lives inside its base64 `animation_url`; `abx inspect` is
2774
- pre-deploy and static. An agent's workaround was to start `abx serve`, `GET /api/project/<addr>`,
2775
- base64-decode each `tokenURI`, base64-decode the `animation_url` inside it, then regex
2776
- `0x[0-9a-f]{64}` out of the resulting HTML — 32 times. Every input to that was already a plain
2777
- contract read.
2778
-
2779
- `abx tokens <address> [--json] [--from <id>] [--limit <n>]` is chain-only: no indexer projection, no
2780
- running resolver, no event scan. The params store maintains its own key lists inside its write paths
2781
- (`tokenParamKeys` / `contractParamKeys`) and `seed` is a reserved param read by name, so the whole
2782
- listing is `eth_call`s — available to anyone with an RPC URL, including before any indexing has
2783
- happened. Seeds print in full (truncating the one value the command exists for would repeat the
2784
- `tokenuri` bug); `--json` emits `{tokenId, owner, seed, params}` per token, contract-scope params
2785
- once on the parent rather than copied into every row.
2786
-
2787
- It reports honestly across all three token types rather than failing: a 1/1 has no params extension
2788
- and says so (owners still list), a pre-enumeration project still yields seeds and flags that params
2789
- can't be enumerated, and an id whose `ownerOf` reverts is `null` — not "not minted", because a
2790
- burned id and an unminted id revert identically and a chain-only read can't tell them apart.
2791
-
2792
- Two things it deliberately is not. It is not a trait spread: a trait comes from running the script
2793
- against the seed, which is `abx render`'s job — the token-scope _param_ spread it does print is
2794
- labelled as such. And the reader is in the **SDK** (`listTokens`), not just the CLI, so a
2795
- programmatic integrator gets it without reimplementing it.
2796
-
2797
- Backlog B25 (from the 2026-08-04 tester batch, feedback `dcfdc6f4`).
2798
-
2799
- - df298d8: `abx tokenuri --fetch` — follow the URL the contract commits to and print what is actually served
2800
-
2801
- Closes backlog B14. No command printed the served JSON body: `tokenuri` read the chain, `verify`
2802
- re-hashed bytes, `status` reported the indexing lifecycle — each a different slice, none of them the
2803
- document a marketplace actually reads. An agent in a cold sweep fell back to raw `curl` for exactly
2804
- this, which is the tell that a command was missing.
2805
-
2806
- ```bash
2807
- abx tokenuri 0x… --fetch # GET the baked URL, print the served body + HTTP status
2808
- abx tokenuri 0x… --fetch --json # {tokenURI, onChain, served: {url, status, contentType, body}}
2809
- ```
2810
-
2811
- It also answers the question a _warning_ could not, which is why the other half of B14 stays declined.
2812
- "Nothing tells you the provider you registered with isn't the base baked on-chain" is a real gap, but
2813
- the obvious check — compare the remote's base URL to the on-chain one — false-positives on the common
2814
- custom-domain case (a baked `meta.artist.xyz` fronting `api.provider.xyz`), and a warning that cries
2815
- wolf on the correct setup is worse than none. Fetching what the **baked** base returns makes the
2816
- mismatch self-evident instead: you see the other provider's 404, or another project's document, with no
2817
- guessing and no false positive.
2818
-
2819
- Three judgments worth naming:
2820
-
2821
- - **A `data:` URI is not a failure.** It IS the document, and a fully-on-chain project's whole point is
2822
- that there is no server to ask — so it reports "nothing to fetch" and exits 0. Treating it as an error
2823
- would punish the strongest configuration the protocol offers.
2824
- - **A dead host and a served error are different answers**, because they route to different fixes ("your
2825
- provider said no" vs "the URL a marketplace will ask is unreachable").
2826
- - **A non-2xx says it is about the SERVICE, never a mistyped path** — the URL came from the chain, so it
2827
- is right by construction, and the readout points at `abx status --remote` for the lifecycle.
2828
-
2829
- Under `--json` the body is verbatim and untruncated and a non-2xx exits non-zero, so CI can gate on it;
2830
- the human readout clips at 1200 chars and says how many it clipped. The fetch itself lives in
2831
- `src/served.ts` with an injectable `fetch`, since `main.ts` exports nothing — the same reason
2832
- `scaffold.ts` was extracted.
2833
-
2834
- This retired the last two `curl` recommendations in the shipped skill: verifying an attach is now
2835
- `abx tokenuri <addr> --fetch` (the served document carries the `artifacts` manifest), and the skill's
2836
- claim that `tokenuri` "follows" the URL — previously true only of `contracturi` — is now accurate.
2837
-
2838
- - Updated dependencies [df298d8]
2839
- - Updated dependencies [df298d8]
2840
- - Updated dependencies [df298d8]
2841
- - @artblocks/abx-sdk@0.1.0-alpha.9
2842
- - @artblocks/abx-token-api@0.1.0-alpha.12
2843
- - @artblocks/abx-storage@0.1.0-alpha.9
2844
- - @artblocks/abx-indexer@0.1.0-alpha.10
2845
-
2846
- ## 0.1.0-alpha.16
2847
-
2848
- ### Patch Changes
2849
-
2850
- - 8f3c63c: Four output surfaces that misinformed: an occupied port, a repeated `verify` advisory, a silently-dropped `tokenuri` argument, and a release-notes URL that 404s.
2851
-
2852
- **`abx serve` / `abx preview` on an occupied port crashed with a raw Node stack trace.** `listen()` had
2853
- no `'error'` handler, so `EADDRINUSE` reached Node's default handler and printed a trace through
2854
- `node:net` and our own `dist/` paths. In a CLI where every other error is formatted, that reads as a
2855
- crash inside abx rather than a port conflict, and it leaks internal paths. Both commands now preflight
2856
- the port and name the port and the fix in one line — `demo` already did this, and the check it used is
2857
- now shared. (`preview`'s default 8788 colliding with a studio left running in another terminal was
2858
- reported as the harder-to-diagnose half of this.)
2859
-
2860
- **`abx verify` printed the identical thumbnail advisory once per token.** On a 32-token project with no
2861
- local renders that was 32 consecutive copies of the same full sentence — ~4KB of text for one fact —
2862
- which pushed the four lines that answer "did my deploy work" off a default terminal. It now prints one
2863
- line per outcome with a count and the affected token ids (truncated past 12), so the same information
2864
- costs three lines at 32 tokens and three lines at 1000.
2865
-
2866
- **`abx tokenuri <address> 0` silently ignored the `0`** (the token id is `--token`) and printed token 0 —
2867
- a _coincidentally correct_ answer, which is the dangerous kind: `… <address> 7` would have printed token 0
2868
- just as confidently and exited 0. A stray positional is now refused, and a numeric one names the
2869
- corrected invocation. The `positionalArgs` helper moved next to `parseFlags`, because the two must
2870
- consume argv by the same rule — the obvious hand-rolled version of this check reads `--token 0` as a
2871
- stray `0`.
2872
-
2873
- **The update banner pointed at `github.com/ArtBlocks/abx/releases`, which 404s** for anyone outside the
2874
- org, and no changelog shipped in the package — so "what changed?" was unanswerable. A tester
2875
- reconstructed the diff by running the same dry run on two versions, which is how they discovered the
2876
- canonical singletons had moved and then had no way to tell whether it needed them to act.
2877
- `CHANGELOG.md` now ships with the package and **`abx changelog`** prints it (offline, version-matched;
2878
- `--all` for the full history), with npm's version list as the online pointer. The upgrading guide now
2879
- also states the thing they had to test for themselves: a singleton redeploy repoints the manifest for
2880
- _new_ deploys and leaves already-deployed contracts unaffected.
2881
-
2882
- **Plus one propagation note.** `arweave.net` — the default gateway, and the one baked into the locator —
2883
- indexes new uploads on a delay, so a freshly published render can 404 there for minutes while Turbo has
2884
- already confirmed it. A tester saw 32/32 404ing on arweave.net while 22/32 already served from other
2885
- ar.io gateways. Unexplained, that reads as a failed render, and the natural next move is
2886
- `abx render --force` on everything — a full re-upload that fixes nothing. `abx render` now says so after
2887
- an Arweave publish, and names `ABX_ARWEAVE_GATEWAY` for baking a different gateway (it is fixed at
2888
- publish time, since the locator is what the resolver registers). The note stays quiet when the operator
2889
- has already chosen a non-default gateway.
2890
-
2891
- _(From the 2026-08-04 tester batch: feedback `eb972036`, `57bee789`, `dcfdc6f4`, `5a052398`, `e0f30e22`.)_
2892
-
2893
- - 8f3c63c: Two writes that reported success without doing what the creator meant now refuse: `lock-field` on a parameter key, and a `Bytes` parameter given text.
2894
-
2895
- Both are the same defect wearing different clothes — the CLI made statements that were individually
2896
- true and collectively a promise it wasn't keeping.
2897
-
2898
- **`lock-field` reported "permanent" on a PostParam key and left the param writable.** A tester welded
2899
- `grid` — a `Bytes` param holding the artwork — with `abx lock-field <addr> --field grid`. It printed
2900
- `Lock token #0 field "grid" — permanent`, `tokenFieldLocked(0,"grid")` returned `true`, and the very next
2901
- `configure-param grid <junk>` **succeeded** and overwrote the artwork. Nothing lied: fields and params
2902
- are separate namespaces that may share a name, and `lock-field` had locked the _field_. But the creator
2903
- was told their work was permanently protected when it had no protection at all, and permanence is the
2904
- pitch. `lock-field` now reads `paramSchema(<name>)` first and **refuses** a declared param key, naming
2905
- the mechanism that does weld a param (`abx set-schema … --schema <key>:<Type>:<Auth>:lock=now`), with
2906
- `--force-field` for the rare case where you really do mean the metadata field.
2907
-
2908
- **A `Bytes` parameter stored whatever characters you typed, as UTF-8.** Passing base64 — reasonable,
2909
- since the params docs say a `Bytes` value "becomes base64" — stored 172 bytes of base64 _ASCII_ where
2910
- 128 packed bytes were meant; `0x`-prefixed hex was stored as its 258 literal characters too. Nothing
2911
- errored, and an in-chain renderer reading the param drew garbage from ASCII with no failure anywhere in
2912
- the chain. A `Bytes` value must now say what its bytes are: `0x`-prefixed hex, or `--file <path>`. A bare
2913
- string is **refused** rather than guessed at, because there is no safe guess between "these characters"
2914
- and "these bytes", and the wrong guess is invisible until an artwork renders wrong. (`String` is
2915
- unchanged — there, the characters _are_ the value.)
2916
-
2917
- The docs' "becomes base64" describes the **read** side — how a program receives the value — and now says
2918
- so, next to the two write forms.
2919
-
2920
- Also fixed: the byte count that was already the tell. The write echoed the _input string's_ length, so
2921
- the mismatch was visible at write time and printed as if it were fine (`Configure grid (data, 172
2922
- bytes)`). It now echoes the decoded byte count and where the bytes came from.
2923
-
2924
- _(From the 2026-08-04 tester batch: feedback `e86081a1`, `b0e88b17`.)_
2925
-
2926
- - 8f3c63c: The renderer scaffold's `IAbxParams` can now read `Bytes` and `String` parameters — the two types that can carry an actual payload.
2927
-
2928
- The scaffold's interface declared only `tokenParam` / `contractParam`, both returning
2929
- `(bytes32 value, bool valueIsHash, bool isSet)`. So a renderer written against the documented interface
2930
- could not reach a `Bytes` or `String` value at all: it got a keccak commitment and no way to the blob.
2931
- The token has always exposed `tokenParamData(uint256,bytes32)` and `contractParamData(bytes32)` — a
2932
- tester found them by grepping the CLI's bundled ABI, declared them by hand, and it worked.
2933
-
2934
- Since `Bytes` and `String` are precisely the types that can hold a real payload (~24KB per key), leaving
2935
- them out made the in-chain art lane look limited to scalars unless you went digging. Both readers are now
2936
- in `src/interfaces/IAbxParams.sol`, with the rule stated where the mistake happens: scalars come from the
2937
- `bytes32` reader, payloads from the data reader, and a `Bytes` param read through `tokenParam` hands you a
2938
- hash — which renders garbage without failing anywhere.
2939
-
2940
- The scaffold's own tests carry the pattern rather than just describing it: `MockParams` now models a
2941
- payload param the way the real contract does (the scalar slot holds `keccak256(content)` with
2942
- `valueIsHash` true), and two tests show the read plus the commitment check, and that an unset payload key
2943
- returns empty bytes rather than reverting. The renderers page documents the pair alongside the scalar one.
2944
-
2945
- _(From the 2026-08-04 tester batch: feedback `d2ac4f2e`.)_
2946
-
2947
- - 8f3c63c: `abx scaffold-renderer` wrote **zero files** for every installed user while reporting success — fixed, and pinned by the test that was missing.
2948
-
2949
- The command created the target directory, printed the full green-check walkthrough (`cd renderer`,
2950
- `forge soldeer install`, `forge test`, the deploy script, "full walkthrough: renderer/README.md") and
2951
- **exited 0**. The directory was empty. No `src/`, no `foundry.toml`, no `README.md`. Three testers
2952
- reproduced it independently — 3 of 3 and 4 of 4 attempts, across all three invocation forms (relative
2953
- name, absolute path, and the no-arg default) — on alpha.9, alpha.12 and alpha.14.
2954
-
2955
- **The cause was a path filter that judged absolute paths.** The copy excluded build dirs with
2956
-
2957
- ```js
2958
- filter: (s) =>
2959
- !/(^|\/)(out|cache|dependencies|broadcast|node_modules)(\/|$)/.test(s);
2960
- ```
2961
-
2962
- `cpSync` hands `filter` **absolute** source paths, and an installed CLI lives at
2963
- `…/node_modules/@artblocks/abx-cli/assets/renderer-scaffold`. So the pattern matched the source
2964
- **root**; `cpSync` skips a directory's entire subtree when the directory itself is filtered out, and
2965
- does it _silently_ rather than erroring. A dev checkout's path
2966
- (`…/packages/cli/assets/renderer-scaffold`) contains no `node_modules`, so every test we ran passed and
2967
- every user got an empty directory. The scaffold assets were always in the published package — this was
2968
- never missing content, only a copy step that no-oped.
2969
-
2970
- Fixed on both axes, because either one alone would have let this ship:
2971
-
2972
- - **The filter judges paths relative to the scaffold root**, so `node_modules` in the _install_ path is
2973
- irrelevant while `out/`, `cache/`, `dependencies/`, `broadcast/` inside the scaffold are still skipped.
2974
- - **The command asserts its own output.** It now throws if `src/MyRenderer.sol` isn't there afterwards,
2975
- instead of printing a success banner over nothing. Exit 0 plus a green check is what made this
2976
- expensive: an agent has no reason to look back at a step that reported success, so the failure
2977
- surfaced far away — `forge soldeer install` dying in an empty directory.
2978
-
2979
- The copy logic moved to `src/scaffold.ts` so it can be tested at all (importing `main.ts` runs the CLI),
2980
- and `test/scaffold.test.ts` copies **from a path containing `node_modules`** — the layout every user has
2981
- and the one case no previous test covered — plus an end-to-end run asserting the scaffolded project is
2982
- non-empty and contains the files the success message names.
2983
-
2984
- This was the documented entry point to the in-chain Solidity lane and the only documented route to
2985
- `--image-renderer` / `--attributes-renderer`, so it blocked that lane outright. The full walkthrough
2986
- (`forge soldeer install` → `forge test`) now runs clean: 10 tests pass in a freshly scaffolded project.
2987
-
2988
- _(From the 2026-08-04 tester batch: feedback `6f2f980a`, `b45bc0f8`, `d2ac4f2e`.)_
2989
-
2990
- - 8f3c63c: The agent skill now names the both-worlds code lane, the payload-param reader, and the verb that welds a parameter.
2991
-
2992
- Four corrections, all from the same batch that produced the CLI fixes — the skill described the lanes in a
2993
- way that made the strongest option invisible:
2994
-
2995
- - **`--script` plus `--image-renderer` is now a lane in its own right.** The table and the in-chain
2996
- section both said "no `--script`", which is a true constraint of the _renderer-only_ shape and read as a
2997
- prohibition on combining them. A program with Solidity renderers gets `animation_url` from its on-chain
2998
- chunks _and_ an on-chain `image`/`attributes` — every marketplace surface in-chain with nothing to
2999
- render, host, or refresh. That is the best available answer for an interactive generative drop and it
3000
- was reachable only by ignoring the skill.
3001
- - **Renderers are told which reader a `Bytes`/`String` param needs.** The guidance said to read params via
3002
- `tokenParam`/`contractParam`, which for the two payload types returns a keccak commitment — so a
3003
- renderer written from the skill drew garbage from a hash with nothing failing. `tokenParamData` /
3004
- `contractParamData` are now named where the mistake happens.
3005
- - **`configure-param`'s payload encoding is explicit**: `String` takes text, `Bytes` takes `0x` hex or
3006
- `--file`, and a bare string on a `Bytes` key is refused.
3007
- - **Locking a param points at `set-schema … :lock=now`**, since `lock-field` freezes the same-named
3008
- _field_ and now refuses a declared param key.
3009
-
3010
- _(From the 2026-08-04 tester batch: feedback `c283d767`, `d2ac4f2e`, `b0e88b17`, `e86081a1`.)_
3011
-
3012
- - 8f3c63c: The `deploy-code` **Surfaces** preflight no longer contradicts itself, and a script plus an on-chain image renderer is now a documented combination rather than an open question.
3013
-
3014
- The `[5] Surfaces` block is the most-praised thing in the deploy preflight — a tester singled out its
3015
- "not backfillable" framing as the direct fix for a confusion they'd filed a version earlier. It also
3016
- told two lies in adjacent lines, both found while answering their follow-up question.
3017
-
3018
- **"One or more surfaces resolve to NOTHING a marketplace can see" fired when none did.** An undeclared
3019
- param was folded into the broken-surface test, so a deploy with both renderers set printed
3020
- `thumbnail: ON-CHAIN ✓` and `traits: on-chain ✓` and then, two lines later, that a surface resolved to
3021
- nothing — re-recommending `--image-renderer` and `--attributes-renderer`, the exact flags already
3022
- passed. A dropped param is not a dead surface: the piece renders, that input takes its default. The
3023
- alarm now covers only the thumbnail and traits, names only the remedies for what is actually broken, and
3024
- says _which_ surface. Dropped params keep their own, milder line.
3025
-
3026
- **An on-chain image renderer was told to stand up a render runner.** With `--image-renderer` _and_ a
3027
- script, the block recommended `abx deploy-effects`, a one-shot `abx render`, and a publish-capable
3028
- storage backend — none of which apply, because an in-chain SVG has no off-chain still to render, host,
3029
- or refresh. It now says `nothing to render` for any on-chain image lane, with the from-chain check.
3030
-
3031
- **And the question the tester explicitly flagged as untested — does passing a script _and_ an
3032
- `--image-renderer` yield both a live `animation_url` and an on-chain thumbnail? — is yes.** Verified on a
3033
- dry run: `animation_url` still assembles on-chain from the script chunks while `image` and `attributes`
3034
- are computed by the Solidity renderers, so every marketplace surface has an on-chain home in one deploy
3035
- with nothing to run. The deploy guide now leads the content-lane section with that combination instead of
3036
- implying the two lanes are an either-or.
3037
-
3038
- The guide also gains the correction to the _other_ half of that question. A resolver plus
3039
- `abx render --backend arweave` does **not** backfill a `--onchain-uri` drop's marketplace surfaces: it
3040
- sends no transaction, so `tokenURI` keeps returning the on-chain JSON with its placeholder `image` and
3041
- no `attributes`, which is what marketplaces read. Checked against the reporter's own live 32-token
3042
- contract — `tokenURIBase` is empty, the metadata renderer is authoritative, and `image` provenance still
3043
- reads `fallback — image field unset`. Publishing stills to a resolver is real and useful, and closing
3044
- those surfaces afterwards takes owner transactions that re-point resolution. The guide now says so
3045
- plainly, since "just render to a resolver later" would have been a new false promise in place of the
3046
- old one.
3047
-
3048
- Three tests pin the block: an undeclared param never claims a dead surface, an on-chain image renderer
3049
- is never sent to a runner, and a script with no renderer and no resolver still reports both surfaces
3050
- dead.
3051
-
3052
- _(From the 2026-08-04 tester batch: feedback `c283d767`, `991dc8e2`, `7936334b`, `a319e53f`.)_
3053
-
3054
- ## 0.1.0-alpha.15
3055
-
3056
- ### Minor Changes
3057
-
3058
- - e325b46: Opt-in ERC-721C support across the toolkit — plain ERC-721 stays the transparent default.
3059
-
3060
- SDK: `OneOfOneInitParams`/`SeriesInitParams` (and `SeriesCodeInitParams` by inheritance) gain
3061
- `transferValidator` immediately after `royaltyBps` — `zeroAddress` = plain ERC-721 forever (the
3062
- default), non-zero = permanent 721C enrollment with that validator. A new `creator-token` module
3063
- ships the per-chain `RECOMMENDED_TRANSFER_VALIDATOR` (OpenSea's
3064
- StrictAuthorizedTransferSecurityRegistry, verified live on Sepolia + Base Sepolia), the two
3065
- creator-token ERC-165 ids + the ABX extension id, `readCreatorTokenStatus()` (`{enrolled,
3066
- validator}`), and a `prepareSetTransferValidator()` write wrapper.
3067
-
3068
- CLI: the deploy commands take `--721c [recommended|0x…]` — absent means zero behavior/output
3069
- change; `recommended` resolves the per-chain constant (refused, naming the chains that have one,
3070
- where none is known); an explicit address is EIP-55-validated and pre-checked for code before any
3071
- gas. Enrolling prints one plain statement of what enforcement means. A new owner op
3072
- `abx set-transfer-validator <address> <0x…|none|recommended>` re-points or suspends an ENROLLED
3073
- collection's validator (guards `--dry-run`; refuses plain ERC-721s up front — enrollment is a
3074
- deploy-time decision). `abx state` shows the validator for enrolled collections only.
3075
-
3076
- ### Patch Changes
3077
-
3078
- - Updated dependencies [e325b46]
3079
- - @artblocks/abx-sdk@0.1.0-alpha.8
3080
- - @artblocks/abx-indexer@0.1.0-alpha.9
3081
- - @artblocks/abx-storage@0.1.0-alpha.8
3082
- - @artblocks/abx-token-api@0.1.0-alpha.11
3083
-
3084
- ## 0.1.0-alpha.14
3085
-
3086
- ### Minor Changes
3087
-
3088
- - 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.
3089
-
3090
- The params store was unenumerable by design, so anything that wanted to know _which_ parameters a
3091
- project has had to be told. The answer was a convention: a `params.keys` contract param holding a
3092
- comma-separated list, composed by `deploy-code` from the `--schema` flags and hand-maintained
3093
- thereafter. It worked, and it had the defect every hand-maintained index has — a key configured but
3094
- not listed was **silently omitted from every render**. The schema existed, a collector could set it,
3095
- the art never saw the value, and nothing anywhere reported a problem. The CLI grew a nudge, then a
3096
- same-transaction companion write, and both were treatments for a design that should not have needed
3097
- them.
3098
-
3099
- **The token now maintains its own key lists.** `SeriesCode` gains `tokenParamKeys(tokenId)`,
3100
- `contractParamKeys()`, and `paramSchemaKeys()` (plus `…Paged` variants for surfaces past an RPC's
3101
- return cap). The lists are updated inside the write paths themselves, so `key ∈ list ⟺ the param is
3102
- set` holds for every writer — raw owner writes, governed `configure-param` writes, hook-driven writes,
3103
- all of them. No caller can forget, because no caller is involved. (`seed` is deliberately never listed:
3104
- every consumer reads it as a tokenData coordinate, and indexing it would charge every seeded mint for
3105
- nothing.) `paramSchemaKeys()` closes the other half — a chain-only frontend can now build a configure
3106
- UI, including for keys declared but never yet written, which nothing off-chain could previously
3107
- discover.
3108
-
3109
- **The canonical `AbxGenerator` reads that surface instead of the CSV.** It no longer looks at
3110
- `params.keys` at all, and a `params.keys` value set on a new project is simply an ordinary parameter —
3111
- enumerated and emitted like any other, by both the generator and the SDK. A latent parity bug dies with
3112
- the convention: the generator's no-CSV path emitted tokenData in insertion order while the canonical
3113
- serializer sorts, so the two byte-forms could disagree; there is now one form.
3114
-
3115
- **`AbxMetadataRenderer` is spec v4.** `tokenURI` gains a computed `abx_params` object — every set
3116
- parameter, contract and token scope merged, token wins, sorted, decoded by the same rules the generator
3117
- and the SDK use. A data-backed value over 2048 bytes is emitted as `{"keccak256":"0x…"}` — its on-chain
3118
- commitment — rather than inline, so a large `Bytes` parameter cannot bloat `tokenURI` past a single
3119
- `eth_call`; every key still appears, and an oversized one degrades self-describingly. Parameters are
3120
- _not_ folded into `attributes`: that stays the creator's surface. Also in v4: an `animation_url` carried
3121
- by the `inline` or `reader` representation is now wrapped as `data:text/html;base64,…`, exactly as
3122
- `image` already was — the asymmetry was an oversight, and it meant a fully on-chain animation was
3123
- handed to wallets as bare text.
3124
-
3125
- Both changes are additive on the read side: pointed at a token that predates enumeration, a v4 renderer
3126
- simply emits no params block. **Repointing the metadata renderer is safe anywhere.** Repointing the
3127
- _generator_ is not, and `abx set-field` now refuses it: a legacy implementation plus the current
3128
- generator means every parameter silently vanishes behind a `tokenURI` that still looks healthy, so the
3129
- CLI stops you rather than warning you, and names both ways out (stay on the project's existing
3130
- generator, or redeploy).
3131
-
3132
- Everywhere else in the toolkit, the convention is simply gone:
3133
-
3134
- - `deploy-code --onchain-uri` sets up in **three** legs, not four (animation field → generator, plus
3135
- the two URI renderers). There is no key list to compose, report, or keep in sync, and the help text
3136
- no longer teaches one.
3137
- - `abx set-schema` is a single op again — no companion write, no multicall. This **supersedes**
3138
- alpha.13, which shipped `set-schema` writing `params.keys` in the same transaction to stop a governed
3139
- key going unlisted: the contract now maintains its own key list, so there is nothing to keep in step
3140
- and the drift that fix guarded against is gone rather than mitigated. Same for that release's note
3141
- that "there is no on-chain enumeration of schema keys" — `paramSchemaKeys()` is exactly that.
3142
- - `abx state` reads the governed surface from `paramSchemaKeys()` and the collection-scope values from
3143
- `contractParamKeys()`. A project deployed before enumeration falls back to reading its old
3144
- `params.keys` list, read-only, so live testnet drops still describe themselves.
3145
- - `abx verify` notes when a project's enumerated surface exceeds ~64 keys — the documented design
3146
- envelope. The write side is unbounded; the read side is what grows, since `tokenURI` and `tokenData`
3147
- assemble every parameter per call.
3148
- - `abx configure-param <addr> - params.keys <csv>` no longer has a special path. Writing that key is
3149
- now an ordinary schema-less contract param, and it shows up in tokenData as one — honest, and
3150
- documented.
3151
- - `abx_params` joins `artifacts` and `abx_provenance` as a computed key `set-field` and `attach` refuse.
3152
-
3153
- The SDK needed no semantic change: `buildTokenData` has always been event-derived (coordinates + seed +
3154
- every set param, both scopes, token wins, augment entries), and the contract enumeration implements
3155
- exactly that rule. Its `deployments` manifest carries the new addresses.
3156
-
3157
- ### Patch Changes
3158
-
3159
- - 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.
3160
-
3161
- **`abx tokenuri --json`.** The command abbreviated long values (`… (382 chars)`) with no way to turn it
3162
- off, so for a token whose whole point is on-chain content it returned something that _looked_ like the
3163
- metadata and wasn't. An integrator scraped it, stored a `data:` URI cut to 96 characters, and only
3164
- found out in production; they abandoned the CLI as a read path and reimplemented `eth_call`. `--json`
3165
- now emits the verbatim decoded document — no banner, no ANSI, no truncation — so
3166
- `abx tokenuri <addr> --json | jq` is a supported read path. The human view still abbreviates, and now
3167
- says `[--json for the full value]`.
3168
-
3169
- **One Arweave identity, resolved in one place.** `arweaveConfigFromEnv()` read `ARWEAVE_JWK` and
3170
- nothing else, while the CLI mints and manages `.abx-self-host/arweave-key.json`. Porting a working CLI
3171
- flow to the SDK — same machine, minutes later — failed every upload with "Arweave via Turbo needs an
3172
- identity", a message that says storage was never configured when the truth was that two layers
3173
- disagreed about where the identity lives. `@artblocks/abx-storage` now exports `resolveArweaveJwk()`
3174
- (env → managed key file) and the CLI delegates to it. Its diagnostics come with it: an empty key file
3175
- now reports the **path** and the remedy instead of `Unexpected end of JSON input`, and a corrupt one
3176
- says the same.
3177
-
3178
- **`abx refresh` on the default chain.** The OpenSea slug map held only `sepolia` and `mainnet`, so
3179
- `base-sepolia` — the CLI's own default — fell through to the raw key: the refresh POST went to a slug
3180
- OpenSea doesn't know, and the printed link pointed at **mainnet** `opensea.io` for a testnet token.
3181
- Slugs are now correct (`base_sepolia`), `testnet` comes from the chain registry rather than a second
3182
- hand-maintained set, and a chain with no known slug produces **no link** instead of a wrong one. Same
3183
- shape as the hardcoded explorer table that once sent every Base Sepolia link to Etherscan.
3184
-
3185
- **`abx attach` names its dependency.** Attaching artifacts to a project that resolves on-chain now
3186
- warns, before the send, that they will **not** appear in `tokenURI` — the on-chain renderer carries
3187
- reserved fields only, and the artifacts manifest comes from a resolver. A team attached five audio
3188
- stems to a fully-on-chain token and found them "paid for, stored on-chain, and invisible"; the note
3189
- that existed was one dim line that read as a footnote rather than as a missing service.
3190
-
3191
- **`ensureChunkStore` moved to the SDK.** The bootstrap every on-chain-content path needs existed only
3192
- inside the CLI, so an SDK integrator got `resolveChunkStore()` (may return undefined) plus a separate
3193
- `storeSupportsWriteContent()` they had to remember — forget it and an incapable store fails _deep
3194
- inside a mint, after transactions have landed_. One team hand-rolled the guard for exactly that reason.
3195
- `ensureChunkStore(publicClient, send, {chainId, override, onEvent})` is now exported; the SDK reports
3196
- progress through `onEvent` instead of printing, and the CLI keeps its narration.
3197
-
3198
- **`abx storage upload --json`.** The locator as data. They scraped this line, captured its ANSI colour
3199
- codes along with the URL, wrote the result into a _stored_ player URL, and found out when it 404'd in
3200
- production. In `--json` mode stdout carries the JSON and nothing else; progress moves to stderr.
3201
-
3202
- **`--backend ipfs` no longer hides a missing credential.** Without `PINATA_JWT` the backend resolves to
3203
- **kubo against a local node**, so a dry run looked fine and the real upload failed for anyone not running
3204
- one. The preview now says so. Related correction: the skill claimed "a backend missing its secret falls
3205
- back to `fs`" — it does not. `cloud` refuses up front naming the missing values, and `ipfs` goes to the
3206
- local node; nothing silently degrades to local disk. Both sides now say the same thing.
3207
-
3208
- Reported in the 2026-08-03 MXRR integration batch (feedback 869f27b1, a256217f, 119d7e8e, 975c363e,
3209
- e38216db, e267078b).
3210
-
3211
- - 1b50f9d: Unknown flags on a command that can send now **refuse** instead of warning — and `--chain` teaches `ABX_CHAIN`.
3212
-
3213
- `abx deploy-code … --chain sepolia` ran to completion **on the default chain** (base-sepolia). There is
3214
- no `--chain` flag — the chain comes from the `ABX_CHAIN` environment variable — and the generic
3215
- stray-flag warning said so, accurately, and then the command carried on. On a dry run that is a
3216
- confused minute; on a funded send it is a wrong-chain deploy with real artifacts at an address nobody
3217
- is watching. Prose that gets ignored once gets ignored again, so:
3218
-
3219
- - **`--chain` is refused on every command**, read-only ones included (a command that quietly ignored it
3220
- would still teach the wrong model). The message names the mechanism, the chain that _is_ active, the
3221
- known chain keys, and the corrected invocation: `ABX_CHAIN=sepolia abx deploy-code …`.
3222
- - **`deploy`, `deploy-series` and `deploy-code` refuse any unrecognized flag**, naming the offender and
3223
- pointing at `abx help <command>`. `--dry-run` refuses identically — a preview that accepts what the
3224
- real send rejects is its own trap, since you would validate a command and have it fail at the one
3225
- moment it matters.
3226
- - **Read-only commands still only warn.** Nothing can be mis-sent, and a stray flag shouldn't stop a
3227
- creator mid-iteration.
3228
-
3229
- Refusing is safe rather than risky here, and this was verified rather than assumed: a flag absent from a
3230
- command's allowlist is by construction one that command never reads. Every `flags.x` read inside
3231
- `cmdDeploy`, `cmdDeploySeries` and `cmdDeployCode` was compared against its allowlist — no gaps — so
3232
- refusal cannot break a working flag, only make an already-ignored one loud.
3233
-
3234
- Five tests cover it, including the literal `--chain sepolia` repro, dry-run/real-send parity, and a
3235
- no-false-refusal pass over the documented deploy flags.
3236
-
3237
- - 1b50f9d: `abx preview --shoot --param key=value`, and `set-schema --force` says what it overrode.
3238
-
3239
- **`--shoot` can render the collector-has-set-it state.** It only ever shot the _unset_ one, so
3240
- answering "what does this collection look like when someone picks a theme?" meant hand-rolling a
3241
- Playwright script against the preview server's `/view` — which is exactly what one agent did. `--param`
3242
- is repeatable (`--param theme=Neon --param mood=Calm`) and forwards into every frame. An empty value
3243
- (`--param theme=`) shoots the unset state explicitly, matching the wire shape production uses for a
3244
- param nobody has written.
3245
-
3246
- **`set-schema --force` no longer applies a value-stranding change silently.** It refuses such a change
3247
- by default; with `--force` it now prints each risk it is overriding and notes that any token already
3248
- holding a value keeps it, now outside what its schema allows. Applying that quietly was the one outcome
3249
- worse than refusing — nobody, including the operator, got a record of what may have just been
3250
- invalidated.
3251
-
3252
- - Updated dependencies [1b50f9d]
3253
- - Updated dependencies [1b50f9d]
3254
- - @artblocks/abx-sdk@0.1.0-alpha.7
3255
- - @artblocks/abx-storage@0.1.0-alpha.7
3256
- - @artblocks/abx-indexer@0.1.0-alpha.8
3257
- - @artblocks/abx-token-api@0.1.0-alpha.10
3258
-
3259
- ## 0.1.0-alpha.13
3260
-
3261
- ### Minor Changes
3262
-
3263
- - 1158420: Expose the PostParam schema lifecycle: `abx set-schema`, `abx retire-param`, Address legs, and `lock=`.
3264
-
3265
- Three capabilities the contracts have always had, that the toolkit could not reach — so they read to
3266
- creators as protocol limitations. All three were reported in the 2026-08-03 tester batch.
3267
-
3268
- **A project's param surface was never frozen at deploy.** `setParamSchema` is owner-gated with no
3269
- deploy-time restriction and no `exists` check, so it is an upsert usable for the life of a project.
3270
- There was just no command for it, and the CLI said so out loud ("Adding a param to an already-deployed
3271
- contract isn't a CLI command yet"), which pushed designers toward guessing their full param surface up
3272
- front or redeploying — losing the address, the mints, and the collectors. `abx set-schema <addr>
3273
- --schema key:Type:Auth` attaches or replaces one key.
3274
-
3275
- Because it is a **full-row upsert on a contract that never re-validates stored values**, the command
3276
- carries a guard rather than a warning: it prints before/after, and _refuses_ a change that could strand
3277
- values already written under the key — a narrowed bound, a dropped `Select` option, a changed type —
3278
- unless you pass `--force`. It also flags an existing `lock=` you are about to drop by not restating it.
3279
-
3280
- **A parameter can be retired.** There is no delete in the contract (`exists` is only ever set true), but
3281
- a `lockAfter` in the past makes every later write revert `ParamLockExpired`, permanently. `abx
3282
- retire-param <addr> <key>` does exactly that, reading the current schema and changing _only_ the lock so
3283
- type/auth/bounds/options carry forward untouched. It does not remove the key and does not erase a stored
3284
- value — a value written under a `TokenOwner`/`Address` leg came from a collector, and the artist
3285
- deliberately cannot delete it.
3286
-
3287
- **An `Address` auth leg is now expressible.** `--schema` previously rejected every Address-bearing leg
3288
- with "set that schema post-deploy via the contract" — advice pointing at a command that did not exist.
3289
- The auth token now names its holder inline (`board:Bytes:Address(0xabc…)`), and the error for a bare
3290
- `Address` says what the leg is for: a **contract** may hold it, which is how open and multi-party
3291
- participation is built today. `authAddress` and `lockAfter` were also hardcoded to zero at the
3292
- deploy-time call site, so neither was reachable there either; both now flow through `--schema`.
3293
-
3294
- `--schema` gains an optional 4th field, `lock=<when>` (ISO date, unix seconds, or `now`), sharing the
3295
- Timestamp grammar the bounds already use. A 4th field that is not `lock=` now reports the spec-shape
3296
- error instead of a mangled "malformed type", which is what a `:` inside a `Select` label used to produce.
3297
-
3298
- New in the SDK: `prepareSetParamSchema`, `prepareRetireParam`, `readParamSchema`, `OnChainParamSchema`.
3299
-
3300
- (feedback 4a0c213a, 5d530681, 381bdcbe)
3301
-
3302
- > [Superseded 2026-08-03: params now enumerate **on-chain** (renderer spec v4, `abx_params`) and
3303
- >
3304
- > > `params.keys` is retired — the contract maintains its own key list, so nothing off-chain has to keep
3305
- > > it in step and `abx state` reads the chain directly. See the on-chain param enumeration entry.]
3306
-
3307
- `set-schema` also keeps **`params.keys` in step, in the same transaction**. On the on-chain URI lane
3308
- the canonical generator builds tokenData from that CSV, so a key that is governed but not listed is
3309
- silently omitted from every render — the schema exists, a collector can set it, and the art never sees
3310
- the value. `deploy-code` composes the list from `--schema` for exactly this reason; without the
3311
- companion write, a schema added later would have quietly half-worked. Projects not on that lane (where
3312
- `params.keys` is unset) get no extra write.
3313
-
3314
- > [Superseded 2026-08-03: params now enumerate **on-chain** (renderer spec v4, `abx_params`) and
3315
- >
3316
- > > `params.keys` is retired — the contract maintains its own key list, so nothing off-chain has to keep
3317
- > > it in step and `abx state` reads the chain directly. See the on-chain param enumeration entry.]
3318
-
3319
- And `abx state` now prints the governed PostParam surface — each key's type, auth, bounds/options, an
3320
- upcoming lock date, and a `retired` marker for one whose lock has passed. There is no on-chain
3321
- enumeration of schema keys, so it reads the project's own `params.keys` list, which is also what the
3322
- generator reads; anything missing from it is invisible to renders anyway. It also names keys listed
3323
- there with no schema. This is what makes `set-schema`'s upsert safe to use: you can see a key's current
3324
- shape before overwriting it.
3325
-
3326
- - 1158420: `abx deploy` (1/1) can finally do "image off-chain, JSON on-chain, no server" — the pattern the docs already recommended.
3327
-
3328
- The decisions table calls this pattern 2 and presents it as the sweet spot for static art. It worked
3329
- on `deploy-series`. On the 1/1 command it silently did not: `--onchain-uri` inlines the image only
3330
- when it is an SVG, and a raster fell through to keccak256 custody with no URL wired anywhere, so the
3331
- on-chain renderer held a hash it could not serve and `tokenURI` returned a **placeholder — forever**.
3332
- Adding `--backend arweave` changed nothing, which was the cruel part: it looked exactly like the
3333
- documented recipe. A cold agent asked for "no server, still there in ten years", followed the docs,
3334
- and would have shipped a permanently broken token.
3335
-
3336
- `deploy` now takes the same route the Series takes. When `--onchain-uri` meets a non-inlinable image
3337
- and a backend that can serve a public URL (`arweave` · `ipfs` · `cloud`), the file is uploaded as a
3338
- one-entry directory — `putDirectory`, the identical call `deploy-series` makes, so both commands
3339
- produce the same URL shape from one code path — and its URL is baked on-chain as the image field. The
3340
- bytes are still stored under their content hash as well, so `abx verify` keeps working. A backend that
3341
- can only serve from this machine (`fs`) still falls through to custody, and still warns.
3342
-
3343
- The readouts learned that this is a three-way distinction, not a binary, because "no server" and
3344
- "on-chain" are different promises and a creator buying permanence is choosing between them:
3345
-
3346
- - image genuinely on-chain (`--onchain-image`, or an inlined SVG) → **"Done — fully on-chain."**
3347
- - image at a durable URL the on-chain JSON points at → **"Done — metadata on-chain, image on ipfs."**
3348
- plus a line naming whose permanence it actually is (Arweave paid-once-forever; IPFS while pinned).
3349
- - neither → the placeholder warning, before the spend, naming both routes that would fix it.
3350
-
3351
- Verified end to end on Base Sepolia (`0xdE5aCD35b74B6d002781De51217590a9c5B53EDC`): `tokenURI(0)` read
3352
- straight from chain with `cast` — no `abx`, no server — returns an IPFS gateway URL that serves HTTP
3353
- 200 with bytes identical to the source file, and reports itself honestly in `abx_provenance` as
3354
- `source: url · onChain: false`.
3355
-
3356
- Closes B17. Found by the 2026-08-03 parallel agent sweep.
3357
-
3358
- ### Patch Changes
3359
-
3360
- - 1158420: `abx inspect` no longer reports dependencies, params, or runtime problems that aren't in the code.
3361
-
3362
- Every detector in the analyzer was a regex over the raw file, so prose counted as code. One
3363
- tokenizer pass now gives the detectors a comments-stripped, string-blanked view:
3364
-
3365
- - **A comment or string mentioning a library is no longer a dependency.** A dependency-free
3366
- vanilla-canvas sketch whose header read `// no p5` was reported as `libraries: p5`, and the lane
3367
- recommendation then said `--dep p5@<version>` — advice an agent adopts verbatim, which bloats the
3368
- stored on-chain document and can push a drop to a chain whose dependency registry it needs.
3369
- Real `p5`/`THREE`/`Tone` usage is still detected (pinned by tests).
3370
- - **Reserved coordinates are never listed as PostParams.** `tokenId`, `chainId`, and
3371
- `contractAddress` (like `seed` before them) are injected by the runtime and cannot be declared,
3372
- but they landed in the "declare EACH at deploy or it's silently dropped" warning with
3373
- `--schema tokenId:<Type>:<Auth>` advice that must not be followed.
3374
- - **A dotted param key read through an alias is detected.** `const d = abx.tokenData;` then
3375
- `d['collapse.index']` was invisible, producing a false "dropped at render" warning on a correct
3376
- program — and ABX's own output-naming convention is dotted (`effect.render.image`), so the
3377
- documented idiom tripped the analyzer.
3378
-
3379
- Reported in the 2026-08-03 tester batch (feedback 3b66e443, f69e15d6, 8184f7dd, 6d32f874).
3380
-
3381
- - 1158420: `abx deploy --onchain-uri` no longer claims "fully on-chain" over a token whose image isn't.
3382
-
3383
- `--onchain-uri` puts the metadata JSON on-chain. It inlines the _image_ only when the image is an
3384
- SVG; a raster falls through to keccak256 custody, and the on-chain renderer then serves a
3385
- **placeholder** image. The success banner printed "Done — fully on-chain … no server or hosting
3386
- needed" for that configuration, so a creator would believe they had permanence they did not have —
3387
- and only discover it later via `abx tokenuri` (`source: "fallback"`, `onChain: false`).
3388
-
3389
- Two changes, both computed from the actual file rather than the flag:
3390
-
3391
- - The dry run (and the real run) now warn **before the spend** that the image is a keccak256 anchor
3392
- and `tokenURI` will serve a placeholder, pointing at `--onchain-image --compress fastlz` or a
3393
- served base URL. The code lane's dry run already warned about this case; the 1/1 lane shipped it
3394
- silently.
3395
- - The success banner says "Done — metadata on-chain" and names the image's real status. An SVG (or
3396
- `--onchain-image`) still gets the unqualified "fully on-chain" banner, because that one is true.
3397
-
3398
- Reported in the 2026-08-03 tester batch (feedback a8921f18).
3399
-
3400
- - 1158420: Fix `deploy-code` reverting `DeploymentFailed()` — the setup transaction was sent with a gas limit estimated against a contract that did not exist yet.
3401
-
3402
- Every `deploy-code` attempt in a reporter's Base Sepolia session reverted with Solady's
3403
- `DeploymentFailed()` (`0x30116425`), in both the on-chain and hosted-resolver lanes, with a minimal
3404
- case of storing a single 3,563-byte script chunk. It was not a defect in the chunk path: the two
3405
- transactions simply **ran out of gas**.
3406
-
3407
- ```
3408
- 0xcad74d07… gasLimit 201,616 gasUsed 198,870 (98.6%)
3409
- 0xf4350724… gasLimit 169,301 gasUsed 166,810 (98.5%)
3410
- ```
3411
-
3412
- A code project deploys in two transactions: create the clone, then one setup `multicall`. The second
3413
- targets the contract the first just created — and `eth_estimateGas` for that call, taken while the
3414
- answering node has not yet seen the deploy block, returns the **calldata cost alone**. Replaying both
3415
- payloads against a codeless address reproduces the sent limits _to the gas_ (201,616 and 169,301);
3416
- against the real contract the same calls need 941,331. A setup multicall's cost is dominated by
3417
- CREATE code deposit (~200 gas per stored byte), so the underfunded CREATE inside `SSTORE2.write`
3418
- returned 0 and reverted. The 1/1 lane was unaffected because its setup fits inside a calldata-sized
3419
- budget.
3420
-
3421
- This is the same read-after-write lag the deploy loop already pins the **nonce** against, one field
3422
- over. The fix has two halves, and deliberately does not include a third:
3423
-
3424
- - **Every leg after the first waits for the target's code to be visible** to the client doing the
3425
- estimating, so a lagging node cannot produce a meaningless estimate in the first place. This is the
3426
- actual repair.
3427
- - **An impossible estimate is detected and refused, not replaced.** `PreparedTx` gained an optional
3428
- `gasFloor` carrying only the _provable_ part of a payload's cost — EVM code deposit at exactly 200
3429
- gas per stored byte. An estimate below that is not "low", it is proof the node is on stale state, so
3430
- the sender retries and then errors out with what it saw.
3431
- - **What we did NOT do: substitute a computed gas limit.** Only the deposit is derivable; the same
3432
- setup multicall also carries schema writes, dependency legs, URI legs and mints whose cost cannot be
3433
- known without simulating them. A "probably enough" constant is tuned to whoever's example was in
3434
- front of its author — it would have covered the reported single-chunk case and then under-funded a
3435
- three-schema deploy by ~200k, reproducing the identical `DeploymentFailed()` with a fresh mystery
3436
- attached. Refusing to send is strictly better than sending a transaction we can prove is
3437
- under-funded, which would burn the gas and orphan the contract.
3438
-
3439
- All three signing lanes carry this, not just the hot one: the env-key lane pins the limit before
3440
- `sendTransaction`, the wallet lane waits for code and hands the browser an explicit `gas` (a wallet
3441
- estimates against its own RPC, which we don't control and which lags the same way), and the cold lane
3442
- prints `gasMustExceed` — labelled a floor, not a limit — plus a note telling an external signer to
3443
- re-estimate rather than send if their own number comes back below it. The rule lives in one place
3444
- (`packages/cli/src/gas.ts`) so the lanes cannot drift apart on it.
3445
-
3446
- Reported in the 2026-08-03 tester batch (feedback 156ea0fb, 172111ae), root-caused from the full
3447
- transaction hashes supplied in the follow-up addendum.
3448
-
3449
- - 1158420: `abx preview` stops fabricating param values, and `--shoot` stops blaming the program for its own timeouts.
3450
-
3451
- - **An unset PostParam is now absent from the preview's `tokenData`, exactly as on-chain.** Preview
3452
- injected a per-type default for every declared key — `Select` got its FIRST option — so an
3453
- optional `theme:Select[Newsprint|…]` override rendered every frame as `Newsprint` with nobody
3454
- having set anything: nine seed-distinct pieces collapsed into one palette, while the deployed drop
3455
- (where the key is genuinely absent) would take the program's other branch entirely. Production
3456
- (`buildTokenData`) only injects params that actually have a value; preview now matches it, so the
3457
- program's own `?? fallback` runs in both places. The studio's `Select` control gained an explicit
3458
- "— unset (program fallback) —" default position, so unset is now expressible rather than
3459
- indistinguishable from the first option.
3460
- - **`--shoot` distinguishes "the program reported no traits" from "we stopped waiting."** A wait
3461
- that expired was swallowed, so a loaded machine turned a correct, trait-reporting program into
3462
- "NO frame reported traits" — inverting the one line agents are told to trust and costing a full
3463
- diagnostic cycle. Timed-out frames now report `timed out — traits unknown` and a warning that
3464
- names it a measurement failure and suggests `--timeout-ms`; the silent-killer alarm only fires
3465
- when the program really did report nothing.
3466
- - **A missing Chromium build names the install command for the Playwright that actually loaded**
3467
- (`node <resolved>/cli.js install chromium`). The stock hint (`npx playwright install`) can resolve
3468
- a different Playwright version than the one that just launched, so you download a browser
3469
- revision it won't use, get the identical error, and run the same command again.
3470
-
3471
- Reported in the 2026-08-03 tester batch (feedback f843c952, 994f1d67, 534835d9).
3472
-
3473
- - 1158420: Teach the agent skill how to handle audio and time-based work.
3474
-
3475
- A "can ABX host a music tool?" session found zero mentions of audio, music, or sound anywhere in the
3476
- skill. The protocol supports it — `animation_url` is an HTML document, so Web Audio works, and
3477
- `attach` handles `.wav`/`.mp3`/`.mid` — but five judgments a sound piece needs were unauthored, so an
3478
- agent had to guess or decline: browser autoplay policy (a marketplace iframe cannot start audio
3479
- without a gesture), what the thumbnail _is_ for non-visual work, `abx.done()` semantics for a
3480
- duration-based piece (settle the visual, don't wait out playback), the dependency lane for audio
3481
- libraries (`tone` needs a registry entry ⇒ Sepolia, like `p5`), and the fact that there is no
3482
- `render/audio` output declaration to reach for.
3483
-
3484
- Reported in the 2026-08-03 tester batch (feedback fd6109db).
3485
-
3486
- - 1158420: `abx skill install` can no longer overwrite a newer skill with an older one, or delete the skill it is installing.
3487
-
3488
- Two ways the same command could destroy the thing it exists to install, both hit while working in the
3489
- abx repo itself:
3490
-
3491
- - **A stale prepack bundle shadowed the canonical skill.** `<pkg>/skill` is gitignored build output
3492
- written at `prepack`; the canonical copy lives at `.claude/skills/abx-self-host`. Resolution
3493
- preferred the bundle unconditionally, so a leftover `skill/` from an old `npm pack` was installed
3494
- **over** the canonical skill — silently replacing v0.1.0-alpha.12 with v0.1.0-alpha.4, after which
3495
- the CLI's own drift check reported the stale version as if the user had put it there. When running
3496
- from source (the repo working tree) the canonical copy now wins; the published layout, which has no
3497
- repo and no canonical copy, still uses the bundle.
3498
- - **Installing onto the source deleted it.** `installSkillTo` removes the destination before copying,
3499
- so when destination _was_ the source (a cwd-relative install inside the repo) it deleted the
3500
- canonical skill and then had nothing to copy from. Same-path installs are now a no-op that reports
3501
- `already the canonical copy — left as is`.
3502
-
3503
- - 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.
3504
-
3505
- **An unknown `ABX_CHAIN` printed a raw Node stack trace — from every command.** Chain-derived values
3506
- were resolved at module scope, in `token-api` (which the CLI imports) and in the CLI itself, so the
3507
- throw happened during module evaluation, before `main()` existed to catch it. `ABX_CHAIN=mainnet abx
3508
- doctor` dumped an internal source path and exited 1 — including from the one command whose job is to
3509
- tell you what is wrong with your environment. Those resolutions are lazy now, and the CLI validates
3510
- the variable up front with an answer rather than a crash: unknown values list the shipped chains, and
3511
- a mainnet-shaped value says plainly that ABX is testnet-only today.
3512
-
3513
- **`abx inspect` reported "(no PRNG)" for a hand-written seeded generator — with the _stronger_
3514
- reproducibility verdict attached.** The `seeded` check only recognized p5's `randomSeed(`, so a
3515
- vanilla LCG or xorshift matched no branch and fell through to "traits look derived from the
3516
- seed/params directly". That is the common case, not an edge one — the skill's own canonical
3517
- dependency-free example hand-rolls an LCG, and all three sketches written by agents in the sweep hit
3518
- it. A hand-rolled generator now gets the `careful` verdict and is told the truth: deterministic and
3519
- reproducible on-chain, but only by porting that exact generator and call order into Solidity.
3520
-
3521
- Also: `--yes` is now documented in `deploy-code --help` (its own placeholder-identity refusal already
3522
- told you to pass it), and the `--onchain-uri` raster warning now names the two routes that actually
3523
- deliver a no-server image instead of only one.
3524
-
3525
- Found by the 2026-08-03 parallel sweep (8 cold Sonnet/Haiku agents, isolated sandboxes).
3526
-
3527
- - Updated dependencies [1158420]
3528
- - Updated dependencies [1158420]
3529
- - Updated dependencies [1158420]
3530
- - @artblocks/abx-sdk@0.1.0-alpha.6
3531
- - @artblocks/abx-token-api@0.1.0-alpha.9
3532
- - @artblocks/abx-indexer@0.1.0-alpha.7
3533
- - @artblocks/abx-storage@0.1.0-alpha.6
3534
-
3535
- ## 0.1.0-alpha.12
3536
-
3537
- ### Minor Changes
3538
-
3539
- - feba8c2: A resolver is no longer an object store: effect outputs split into **bound** and **referenced**
3540
- (`specs/protocol/effects.md → Bound vs referenced`), and the artifact registry enforces the split.
3541
-
3542
- An output is **bound** iff a binding stitches its _content_ into the metadata JSON (today exactly
3543
- `render/traits` → `attributes`); everything else is **referenced** — the projection carries its URL,
3544
- or it only appears in the `artifacts` manifest. That one distinction decides who holds the bytes, and
3545
- it is now the wire rule rather than a runner constant.
3546
-
3547
- - **`POST /v1/effect-artifacts` derives the mode from the binding, and refuses both mismatches.**
3548
- Bytes for a referenced output → `400` (the resolver redirects either way, so the bytes buy no
3549
- capability and cost it storage, retention and egress). A locator for a bound output → `400` (its
3550
- content is assembled into `tokenURI`; a pointer there used to be recorded and then silently never
3551
- stitch — a wrong answer served confidently). Bound content is capped at **64 KB**, and a locator
3552
- that only the producer could resolve (loopback/private host, presigned expiring URL) is rejected.
3553
- The resolver never fetches a locator while handling the write, and serves registered locators by
3554
- `302` — never by proxying.
3555
- - **Bound content moved out of byte custody** into the artifact row (`effect_artifacts.bytes`). Two
3556
- distinct rules, deliberately not one: a node **MUST** serve and stitch bound content only at the
3557
- token's current settled `inputsHash`, and it **MAY** drop superseded content whenever it likes
3558
- (nothing may read it, and it is re-creatable). The reference drops eagerly, on each bound
3559
- registration, so it holds at most `64 KB × minted × bound outputs` — but retention is a service
3560
- policy, not an obligation. Either way "conforming means holding a bounded amount of JSON in the
3561
- database you already run" is now literally true: a resolver in the publish topology needs no object
3562
- storage at all.
3563
- - **`abx-effects-publish/v1` is gone** (not deprecated): the two routes ride `abx-control-plane/v1`.
3564
- Once referenced output is locator-only, accepting a registration is a database insert, so the
3565
- capability flag described a distinction that no longer exists. A service that won't take a caller's
3566
- artifacts refuses on the credential (`403`) — interfaces describe wire grammar, tokens describe
3567
- permission. The interface ids are also explicitly **all-or-nothing**, and the conformance fixture
3568
- now checks that every route a declared interface names actually answers.
3569
- - **The runner declares bound outputs** (`EffectOutputDecl.bound`), refuses to start when it has a
3570
- publish token but a backend that can't name a locator, preflights the descriptor + credential
3571
- before spending a render, and **latches** on a permanent (4xx) publish failure instead of
3572
- re-rendering every sweep forever. Skips now re-register rows, so a transient publish failure heals
3573
- without a re-render.
3574
- - **The CLI refuses the impossible combination up front**: `abx render --remote`, `abx effects`
3575
- against a remote resolver, and `abx deploy-effects` all require a backend that can name a reachable
3576
- URL — `cloud` (S3/R2 + public base), `ipfs`, or `arweave`, named as **peers**. Derived output is
3577
- re-creatable, so the protocol has no preference among schemes: a chosen `https://` gateway or
3578
- bucket URL is exactly as legitimate as `ipfs://`/`ar://`, and reachability — not durability — is
3579
- the requirement. Rendering **co-located** with the resolver remains fully supported on any backend,
3580
- including `fs`.
3581
-
3582
- Breaking for producers that relied on pushing media bytes to a resolver: publish a locator instead,
3583
- or co-locate. Breaking for clients that read `abx-effects-publish/v1` from a descriptor.
3584
-
3585
- ### Patch Changes
3586
-
3587
- - Updated dependencies [feba8c2]
3588
- - @artblocks/abx-sdk@0.1.0-alpha.5
3589
- - @artblocks/abx-token-api@0.1.0-alpha.8
3590
- - @artblocks/abx-indexer@0.1.0-alpha.6
3591
- - @artblocks/abx-storage@0.1.0-alpha.5
3592
-
3593
- ## 0.1.0-alpha.11
3594
-
3595
- ### Minor Changes
3596
-
3597
- - 67b686b: `abx contracturi`, and the read plane stops answering a bare 404 to three different problems.
3598
-
3599
- Both halves come from one real failure: an agent driving a hosted resolver wanted collection
3600
- metadata, pattern-matched off `/t/{chainId}/{address}/{id}`, dropped the token id, got a bare `404`,
3601
- and reported the service as broken. The documented route (`/c/{chainId}/{address}`) was right there —
3602
- but there was also no command to just _ask_, and the 404 gave it nothing to correct.
3603
-
3604
- - **New `abx contracturi <address>`** — the collection-level counterpart of `tokenuri`. Reads
3605
- `contractURI()` (ERC-7572) from the contract, **follows it**, and decodes: a `data:` URI inline
3606
- (the on-chain lane), an `https://` URL by fetching it (the off-chain lane). A contract commits its
3607
- own metadata base on-chain (`contractURIBase`), so the chain — not a doc, not a service
3608
- descriptor — is the authoritative answer to where a project's metadata lives. Nobody needs to
3609
- hand-build a resolver URL. When the fetch fails, the message says so plainly: the URL came from
3610
- the chain, so a bad status is about the _service_ (unregistered project · wrong chain · down),
3611
- never a mistyped path.
3612
- - **Read-plane responses now carry a machine `code`**, so the three causes of "no metadata came
3613
- back" are distinguishable — they were one indistinguishable `{"error": "…"}` `404`:
3614
- - `400 invalid_request` — a real route, wrong shape. Names the correct template, and carries
3615
- `didYouMean` when the fix is obvious (a `/t/…` missing its token id → `/c/{chainId}/{address}`).
3616
- - `404 unknown_route` — this node serves nothing at that path; the body lists what it does serve.
3617
- - `404 not_registered` — the path and chain were fine; this node doesn't index that contract.
3618
- - `400 unsupported_chain` — wrong chain, plus the `chains` this node does serve. Was a bare `404`;
3619
- now matches what the control plane already answered for the same condition.
3620
- - `ServiceErrorCode` gains `unknown_route`. The spec's Errors section now covers the read plane too,
3621
- with a **MUST** on distinguishing the three misses — and an explicit **MUST NOT** on treating
3622
- route templates as per-node discoverable configuration. The route grammar is fixed by the
3623
- `abx-token-api/v1` interface; these responses are diagnostics, not a discovery mechanism.
3624
- - **The conformance fixture checks all of it** (`pnpm conformance <base-url>`), so any provider can
3625
- self-verify in one command. Also fixed: the documented `pnpm conformance -- <base-url>` form
3626
- parsed `--` as a flag and swallowed the base URL, printing usage instead of running.
3627
-
3628
- ### Patch Changes
3629
-
3630
- - Updated dependencies [67b686b]
3631
- - @artblocks/abx-sdk@0.1.0-alpha.4
3632
- - @artblocks/abx-token-api@0.1.0-alpha.7
3633
- - @artblocks/abx-indexer@0.1.0-alpha.5
3634
- - @artblocks/abx-storage@0.1.0-alpha.4
3635
-
3636
- ## 0.1.0-alpha.10
3637
-
3638
- ### Minor Changes
3639
-
3640
- - a72723d: A standard indexing lifecycle, and registration that no longer blocks on a slow chain RPC
3641
- (specs/self-host-toolkit/remote-services.md → The indexing lifecycle).
3642
-
3643
- - **Fixed: a slow register triggered a retry storm.** The SDK's per-attempt timeout (30s) plus its
3644
- retry ladder meant a cold reconstruct that outran one request was **re-POSTed up to four times**,
3645
- each starting another full replay against the RPC that was already too slow to answer — and the
3646
- caller then saw "nothing responded" even though the registration was durable and indexing was
3647
- underway. A timed-out register now asks whether it landed (a status read) instead of re-POSTing, and
3648
- the resolver coalesces concurrent catch-ups for one project into a single run.
3649
- - **`POST /v1/projects` answers in two conformant shapes, discriminated by HTTP status:** `200` with
3650
- the completed summary, or `202` + `{accepted, project: {status}}` when catch-up is deferred. The
3651
- registration is normatively **durable before catch-up** and visible on the list immediately, so a
3652
- flaky RPC makes for a slower backfill rather than a lost add. No `?wait=`/`Prefer:` negotiation — the
3653
- status code is the discriminator, and clients handle both. The reference resolver answers _by
3654
- deadline_ (`ABX_REGISTER_DEADLINE_MS`, default 8s): the common case (a fresh deploy) stays
3655
- synchronous with real counts; only the pathological case defers.
3656
- - **Closed lifecycle enum + error classes, on the status and list routes:**
3657
- `queued | backfilling | live | stale | failed`, plus credential-free
3658
- `error.class ∈ {rpc_unavailable, rpc_rate_limited, not_abx_contract, internal}` (fixed per-class
3659
- messages, never a scrubbed upstream string). Status gains top-level `headBlock` (so lag / % complete
3660
- is computable without knowing a service has a watcher) and `attempts`; the list carries `status` +
3661
- the error class, so a client renders "3 live, 1 backfilling, 1 failed (rpc_rate_limited)" in one
3662
- request. SDK: `IndexStatus`, `IndexErrorClass`, `isAccepted()`, `indexProgress()`,
3663
- `classifyIndexError()`, and `AbxServiceClient.awaitIndexed()` — one wait loop for the CLI, the
3664
- effects runner, and any hosted agent.
3665
- - **The same five words on your own node.** `abx status [address] [--remote [name|url]] [--watch]`:
3666
- bare is the node summary (now with each project's state), an address gives lifecycle + scan floor +
3667
- blocks-indexed-vs-head + cause, and `--remote` asks a service. (`status` = who is serving it and how
3668
- fresh; `state` = what the chain says. Both `--help` texts now say so.)
3669
- - **New observability the self-hosted node never had:** the chain watcher marks projects `stale` when
3670
- it falls far behind head or its ticks keep failing (previously visible only in the node's log),
3671
- re-queues a backfill interrupted by a restart (previously left registered-but-empty until a manual
3672
- `abx index`), and retries a `failed` catch-up on exponential backoff instead of hammering a
3673
- rate-limited RPC every tick. Lifecycle rows live in their own table: they survive a projection wipe
3674
- and are never clobbered by a re-add.
3675
- - **CLI:** `abx add|index --remote` prints `registered — backfilling…`, polls to `live`, then prints
3676
- the same summary a synchronous service would have given; `--no-wait` returns at the 202 and names
3677
- the command to check later. A post-op nudge (`ownerops`) never blocks on someone else's backfill.
3678
- A caught-up project with **0 events** now warns instead of printing ✓ (a real ABX clone always emits
3679
- a spine, so zero means wrong chain/floor or an RPC that didn't serve the logs).
3680
- - **Conformance fixture** accepts either register shape, asserts durable-before-catch-up, lifecycle
3681
- membership, `headBlock`, that a deferred catch-up actually reaches `live`, and that no error message
3682
- carries a URL.
3683
- - Fixed `scripts/mock-remote-service.mts`, which imported the token API by a path that resolved
3684
- against `scripts/` and could silently fall back to a _published_ build outside the repo — the
3685
- fixture was testing the last release instead of the working tree. The fixture also re-points
3686
- scenarios by their fixture header now, so a new one can't keep a dead contract address.
3687
-
3688
- Found by a cold-agent sweep over the above (10 parallel clean rooms, haiku + sonnet) and fixed here:
3689
-
3690
- - **`abx status --remote <name>` with no address** parsed the flag itself as the address and sent it
3691
- as a URL path segment.
3692
- - **A register whose catch-up already failed** was announced as "registered — failed (…is catching
3693
- up…)", and with `--no-wait` it exited 0 and then claimed the provider "now serves" the project. A
3694
- known failure is now an error in both lanes — there is nothing left to wait for.
3695
- - **A `failed` status said what broke but not whose problem it was.** Both the failure error and
3696
- `abx status` now carry a per-class action line ("the SERVICE can't reach its chain RPC — not your
3697
- key, address, or chain…"), plus a `follow` line naming `--watch`, so a red word isn't a dead end.
3698
- - **A `live` project showed a misleading completion percentage.** `toBlock` only advances when a
3699
- project has _events_, so a fully current project on a busy chain read as `2/202 (0%)`.
3700
- `indexProgress()` now returns a ratio only while `backfilling`; `live` reads "caught up", `stale`
3701
- reads "not tracking head right now".
3702
- - **`--remote-token` was misattributed on a 401** — the error blamed `ABX_REMOTE_<NAME>_TOKEN` even
3703
- when the caller passed an override, making the override look ignored at exactly the moment someone
3704
- is testing a replacement key.
3705
- - **`not_registered` on a read** (status/reindex) now names the register command instead of echoing a
3706
- 404, and a 5xx carrying a failure `class` becomes a wait-vs-broken error.
3707
- - **`abx verify`'s summary** read `✓ 0/1 up to date` for a project with no off-chain renders at all —
3708
- "zero of one succeeded" to two independent reviewers. It now says "nothing to render for this
3709
- project", and otherwise leads with polarity ("N of M token(s) current").
3710
- - **`abx doctor` now reports named remotes** and flags a credential stored under a name the CLI does
3711
- not read (`ABX_REMOTE_<NAME>_KEY`). That fault presents as "it acts like I never gave it a key" and
3712
- previously only surfaced from `abx remote <name>` — which a creator reaches _after_ doctor.
3713
- - **Skill: the `npx --no-install abx version` probe was documented as failing cleanly.** It doesn't —
3714
- npm will run any `abx` binary already in the npx cache, which in a real sweep reported a months-old
3715
- build as the project's CLI (and if a plain `npx abx` ever ran on that machine, the bare name is a
3716
- squatted package). The skill now probes `./node_modules/.bin/abx` directly.
3717
- - Also documented: how a multi-word provider name folds into `ABX_REMOTE_<NAME>_*`, and what
3718
- `watching: no` means on a status readout.
3719
-
3720
- A second sweep round over those fixes caught three more, including one the first round's fix created:
3721
-
3722
- - **`abx verify --remote` never checked byte integrity at all** — both of its lanes only ask "is there
3723
- a current render / is this a placeholder", and a green ✓ from that was standing in for "the served
3724
- bytes match the on-chain commitment". A reviewer hit the worst version of this: `--remote` (the form
3725
- the skill tells you to use for a hosted project) reported ✓ on a token whose bytes genuinely did NOT
3726
- hash-match, while bare `abx verify` on the same project reported `✗ keccak256 MISMATCH`. It now calls
3727
- the service's own purpose-built `GET /api/project/:addr/verify` (which holds both the bytes and the
3728
- chain) and reports that verdict separately from the render summary — and when it _can't_ run that
3729
- check (no credential, older node) it says "byte integrity NOT checked" instead of leaving a ✓ to
3730
- imply it passed. The remedy names both real causes (an unbridged durable locator vs. bytes that only
3731
- exist on the creator's machine, which a hosted resolver can never serve).
3732
- - **`abx verify` exited 0 while printing a byte MISMATCH**, in both lanes — nothing could gate on it.
3733
- An integrity mismatch now fails the command; a missing render or placeholder is a normal state and
3734
- still exits 0.
3735
- - **`abx add --dry-run` silently ignored the flag and performed the registration**, local or remote.
3736
- It now refuses and names the read-only commands (`abx state`, `abx status`) instead. Silently doing
3737
- the thing when the caller asked to preview is the one outcome that must never happen.
3738
- - **`PRAGMA busy_timeout` was set third in the store schema**, after the WAL switch it needs to
3739
- protect — so two processes opening the same store at once (parallel CLI runs, or a co-located
3740
- effects runner starting alongside the resolver) could fail outright with `database is locked`
3741
- instead of waiting the moment out. It is now the first statement.
3742
-
3743
- A third round, re-running the scenario that found the verify bug (it now catches it) turned up:
3744
-
3745
- - **`abx add --remote` ended on "it now serves <url>"** — true about indexing, silent about whether
3746
- the bytes are right, and two reviewers stopped there and reported a blank page as fixed. It now names
3747
- the byte check (`abx verify <addr> --remote <name>`) in the same breath.
3748
- - **`canonical:` collapsed a tri-state.** `isCanonical` is `true | false | null`, and both readouts
3749
- printed "unverified" for the last two — so "the chain says this is NOT a clone of the configured
3750
- factory" (a trust finding) looked identical to "the check never ran" (no factory on this chain, normal
3751
- on a dev chain). Two reviewers read the collapsed word as a second failure sitting next to a real one.
3752
- - **`abx verify --remote` gave a bare `fetch failed`** for an endpoint that was down, where
3753
- `abx status --remote` names the host and asks whether it's running. Two commands, one condition, two
3754
- error qualities — now consistent.
3755
- - `abx status <addr>` printed the address twice when the project has no name.
3756
- - Skill: registering with a provider on **their** hostname vs. a domain you control decides whether
3757
- leaving later costs a transaction — now stated in the managed-provider section, before you bake it.
3758
-
3759
- ### Patch Changes
3760
-
3761
- - Updated dependencies [a72723d]
3762
- - @artblocks/abx-sdk@0.1.0-alpha.3
3763
- - @artblocks/abx-indexer@0.1.0-alpha.4
3764
- - @artblocks/abx-token-api@0.1.0-alpha.6
3765
- - @artblocks/abx-storage@0.1.0-alpha.3
3766
-
3767
- ## 0.1.0-alpha.9
3768
-
3769
- ### Minor Changes
3770
-
3771
- - 3745bd3: Remote services are first-class: a provider-neutral control plane, named remotes, and a service
3772
- descriptor (specs/self-host-toolkit/remote-services.md).
3773
-
3774
- - **Control plane moves to `/v1`** (hard cutover; `/admin/*` is gone — redeploy self-hosted nodes):
3775
- `POST/GET /v1/projects`, `DELETE|reindex|status /v1/projects/{chainId}/{address}`,
3776
- `POST /v1/effect-artifacts|effect-status`. `chainId` is explicit and validated everywhere; every
3777
- error carries a machine `code` (`unauthorized` 401 · `forbidden` 403 · `unsupported_chain` ·
3778
- `not_registered` · `disabled`) replacing the old prose-sniffed 404. One bearer guard replaces the
3779
- four inline copies; OPTIONS preflight now answers so browser clients can send `Authorization`.
3780
- - **`GET /.well-known/abx-service`** — the public service descriptor: `interfaces` (present iff
3781
- actually enabled), `chains`, `auth` (with optional provider-set `signupUrl`/`docsUrl` via
3782
- `ABX_SERVICE_*` env), and `render.attached` (managed rendering, probed from the runner's
3783
- `/health`) — so an agent can match a project to a provider before registering.
3784
- - **Named remotes in the CLI**: `--remote <name>` reads `ABX_REMOTE_<NAME>_URL`/`_TOKEN`
3785
- (a managed provider's per-account key — never falls back to `ABX_RESOLVER_ADMIN_TOKEN`);
3786
- `--remote <url> [--remote-token <t>]` for ad-hoc targets; bare `--remote` stays the self-host
3787
- default. New `abx remote [name|url]` inspects a service's descriptor and the projects a token
3788
- sees. `migrate --from/--to` accept names; only the destination needs a credential.
3789
- - **The SDK gains its first HTTP surface**: `AbxServiceClient` (endpoint + injected bearer, retry
3790
- on 5xx/network, immediate typed `AbxServiceError` on 4xx) — shared by the CLI and the effects
3791
- runner's publish lane. `envSuffix()` is the shared env-name normalization.
3792
- - **Conformance fixture**: `pnpm conformance -- <base-url> [--token …]` self-verifies any
3793
- implementation; the e2e suite runs it against the reference container.
3794
-
3795
- ### Patch Changes
3796
-
3797
- - 3745bd3: Membrane fixes found by a 20-run cold-agent regression sweep (sonnet + haiku, black-box clean rooms).
3798
-
3799
- - **A 500 no longer leaks the node's own credentials.** An upstream RPC failure surfaced viem's
3800
- message, which embeds the endpoint URL — and a keyed RPC URL _is_ a credential, so on a
3801
- multi-tenant provider any tenant who could provoke a 500 got the operator's RPC key. The cause now
3802
- goes to the node's log; the wire gets a generic message, an `internal_error` code, and a
3803
- credential-free hint about the failure class. Normative in the remote-services spec.
3804
- - **The service client no longer discards a 5xx body.** The service's own words survive the retry
3805
- ladder, and an exhausted ladder says "failed — last response …" rather than mislabelling a
3806
- server that answered as "unreachable". The descriptor probe drops to 2 attempts, so a typo'd
3807
- provider URL fails in ~1s instead of grinding 5s, with distinct "nothing responded" vs
3808
- "answered, but serves no descriptor" messages.
3809
- - **Conflicting duplicate `.env` keys are reported.** First-wins is unchanged, but a stale second
3810
- `ABX_RPC_URLS_<CHAIN>` line silently pointed the CLI at another network while every check read
3811
- green — the symptom surfaced far away as "no contract at that address". Only genuinely
3812
- _conflicting_ duplicates warn (identical repeats stay quiet).
3813
- - **"No contract at …" errors now name the endpoint they asked** (redacted), because a chain key
3814
- can't distinguish two RPCs that both claim it.
3815
- - **A misnamed remote credential is called out.** `ABX_REMOTE_<NAME>_KEY` (or `_API_KEY`, `_SECRET`)
3816
- is not read, so it previously reported as "no token" while the value sat in `.env`; both
3817
- `abx remote` and the register path now name the near-miss and the correct `_TOKEN` name.
3818
- - **`--dry-run` explains a missing trust anchor instead of crashing.** On a chain where the
3819
- configured factory has no code, `deploy`/`deploy-series` previews died inside
3820
- `predictDeterministicAddress` with a raw `returned no data ("0x")`; they now report it the way
3821
- `abx predict` and a real deploy already did, and name the two ways forward. The keyless
3822
- `--for` requirement also fails fast instead of after several steps of output.
3823
- - **The placeholder-identity guard is one shared predicate** across all three deploy commands
3824
- (it was copy-pasted, and one copy's comment claimed coverage it didn't have), pinned by a new
3825
- regression test: a real send refuses tool defaults, a preview only warns.
3826
- - **The served dashboard's empty state no longer prints `pnpm abx demo`** — a contributor-only
3827
- invocation on a page a published user sees.
3828
-
3829
- - Updated dependencies [3745bd3]
3830
- - Updated dependencies [3745bd3]
3831
- - @artblocks/abx-sdk@0.1.0-alpha.2
3832
- - @artblocks/abx-token-api@0.1.0-alpha.5
3833
- - @artblocks/abx-indexer@0.1.0-alpha.3
3834
- - @artblocks/abx-storage@0.1.0-alpha.2
3835
-
3836
- ## 0.1.0-alpha.8
3837
-
3838
- ### Patch Changes
3839
-
3840
- - 4074766: Fix the dashboard's block-explorer links, which were hardcoded to `https://sepolia.etherscan.io`. Every
3841
- link on the page — contract, owner, implementation, each event's tx — pointed at Ethereum Sepolia no
3842
- matter which chain was being served, so a dashboard for a normal `abx demo` (Base Sepolia by default)
3843
- sent you to an explorer where the contract does not exist. The SDK now derives the explorer from viem's
3844
- own chain metadata (`explorerUrl`/`chainById`), so adding a chain brings its explorer along and no
3845
- hand-maintained table can drift. The CLI's separate copy of that table is collapsed into the same
3846
- helper; `signer.ts` was already doing it correctly.
3847
-
3848
- Drop the demo's opening "trust anchor" step. It asserted that only the canonical factory can make a
3849
- token that _is_ an ABX token, which is false — anything following the protocol's event spine is an ABX
3850
- token, and the factory is one route to that, not the definition. The same overclaim in the index step
3851
- ("verified real") now reports the fact instead: made by the canonical factory, or not. The demo opens
3852
- on the renderer step, and resolving the factory no longer prints a line of its own there.
3853
-
3854
- - Updated dependencies [4074766]
3855
- - @artblocks/abx-sdk@0.1.0-alpha.1
3856
- - @artblocks/abx-token-api@0.1.0-alpha.4
3857
- - @artblocks/abx-indexer@0.1.0-alpha.2
3858
- - @artblocks/abx-storage@0.1.0-alpha.1
3859
-
3860
- ## 0.1.0-alpha.7
3861
-
3862
- ### Patch Changes
3863
-
3864
- - c16c0f0: Shorten the update check's cache from 24h to **6h**. A day-long cache let someone work a whole
3865
- session — deploys included — against a CLI that had been superseded that morning, without ever being
3866
- told. In a fast-moving alpha line that's the common case, not the edge one.
3867
-
3868
- The agent skill now **acts** on version drift instead of reporting it. It already reconciled
3869
- skill⇄CLI drift, but nothing told it what to do when the CLI's own `update available` notice fired —
3870
- so on an agent-driven surface that notice landed as a human-shaped message and got relayed or
3871
- ignored. The skill now upgrades the CLI itself (matching how it was installed), resyncs the skill, and
3872
- reloads before deploying, with the reason stated: a stale CLI can hold canonical addresses that have
3873
- since moved, and a mid-run `… is not a function` is usually exactly this.
3874
-
3875
- Docs: the Upgrading page now leads with the two install shapes in the same order as the quickstart
3876
- (global, then per-project) instead of opening on a one-off `npx` invocation, drops the update-check
3877
- silencing details (they live in the CLI reference, alongside `version`), and answers the question the
3878
- old "Upgrade the SDK" section provoked — upgrading the CLI upgrades the SDK it pins, so a CLI user has
3879
- nothing separate to maintain; you install the SDK only when writing TypeScript against ABX directly.
3880
-
3881
- ## 0.1.0-alpha.6
3882
-
3883
- ### Patch Changes
3884
-
3885
- - c21ea30: `abx demo` now runs **fully on-chain** by default, and speaks plainly.
3886
-
3887
- Its generative SVG is inlined into the contract, so no `http://localhost:8787` is baked into the
3888
- on-chain `tokenURI` base. The old default shipped a first-ever token that resolved for nobody but its
3889
- author — broken on every marketplace, dead the moment `abx serve` stopped — and taught that as the
3890
- normal shape of an NFT. It also undercut the demo's own claim: with the art on-chain, "rebuilt from the
3891
- chain alone" now covers the image, not just the metadata. The final dashboard still starts, but is
3892
- framed as a local viewer rather than infrastructure. Hand the demo an `--image`, a `--backend`, or a
3893
- `--public-base-url` and it switches back to off-chain custody, where that split is real and worth
3894
- teaching.
3895
-
3896
- Fixed alongside it: on every on-chain lane (1/1 and Series, wallet and `--onchain-image`) the deploy
3897
- step announced `URIs point at http://localhost:8787` even though nothing was baked — in the demo, two
3898
- lines after promising no localhost anywhere.
3899
-
3900
- The walkthrough's teaching text is rewritten in plain language, with the protocol vocabulary kept as a
3901
- dim aside instead of the headline: steps are now "Who vouches for this token?", "Mint it", "What the
3902
- chain knows now", and "The moment of truth · delete it all". The read-back step reads from the real
3903
- source per lane — `tokenURI(0)` on the contract when fully on-chain (what a marketplace actually does,
3904
- and it proves no server is involved), the resolver when custody is off-chain.
3905
-
3906
- The browser-wallet signing prompt now says to connect a wallet holding testnet ETH and links a faucet.
3907
- That was the one funding surface with no guidance: on `--sign` without `--for` the address isn't known
3908
- until the wallet connects, so the up-front balance check never ran and an empty wallet's first signal
3909
- was a failed transaction.
3910
-
3911
- - c21ea30: Make the update check independent of which npm dist-tag prereleases are published under. It now
3912
- resolves the `latest` tag and, when the running version is a prerelease, that version's own channel
3913
- tag (`alpha`, `beta`, …), reporting whichever is newer. Previously it only asked for `latest`, which
3914
- works today only because `ci:publish` passes no `--tag`; the day a stable release ships and
3915
- prereleases move to `--tag alpha`, the nudge would have gone silent for prerelease users with
3916
- nothing erroring. The on-disk cache is now keyed by release channel too, so switching between the
3917
- alpha and stable lines re-checks instead of serving a day-stale answer.
3918
- - Updated dependencies [c21ea30]
3919
- - @artblocks/abx-indexer@0.1.0-alpha.1
3920
- - @artblocks/abx-token-api@0.1.0-alpha.3
3921
-
3922
- ## 0.1.0-alpha.5
3
+ ## 0.1.0-alpha.40
3923
4
 
3924
5
  ### Patch Changes
3925
6
 
3926
- - afb36a3: Fix three bugs that made the documented first run (`abx demo`) look broken.
7
+ - 64248d9: Establish the public source baseline and refresh dependency constraints.
8
+ - Updated dependencies [64248d9]
9
+ - @artblocks/abx-indexer@0.1.0-alpha.31
10
+ - @artblocks/abx-sdk@0.1.0-alpha.30
11
+ - @artblocks/abx-storage@0.1.0-alpha.30
12
+ - @artblocks/abx-token-api@0.1.0-alpha.33
3927
13
 
3928
- **A 0-event index was reported as success, and served.** `eth_getLogs` is
3929
- read-after-write inconsistent on load-balanced RPCs: `waitForTransactionReceipt`
3930
- resolves against a node that has the block, then the log query lands on one that
3931
- doesn't yet and returns nothing for a block we _know_ contains our deploy. The CLI
3932
- took that single read at face value, printed `✓ reconstructed 0 events`, stored the
3933
- empty projection and served an empty dashboard — no events, no token, nothing to
3934
- look at. This reproduced 100% of the time against `https://sepolia.base.org`, which
3935
- is the **default endpoint when there is no `.env`** — so the documented first run
3936
- was the path that broke. Every post-deploy index (`deploy`, `demo`, `deploy-series`,
3937
- `deploy-code`, `add`) now re-scans with backoff instead of trusting one read, since
3938
- having just minted means the spine cannot legitimately be empty. If it still comes
3939
- back empty, that is now reported as a failure naming `abx index <addr> --full` as
3940
- the recovery, rather than dressed up as a ✓.
3941
-
3942
- **The demo told you to press a button that does not exist.** It ended with _"Open
3943
- the dashboard, then hit 'Re-index from chain'"_. The dashboard is read-only —
3944
- re-index and verify are admin actions that 404 unless the node has an
3945
- `ABX_RESOLVER_ADMIN_TOKEN` — so that control isn't there to find. It now points at
3946
- the spine table (which _is_ the reconstruction) and at `abx index <addr> --full` to
3947
- replay it. The dashboard's own note also stopped printing a **shortened** address
3948
- inside a copy-pasteable command, and no longer suggests `--remote` for a local node.
3949
-
3950
- **Re-running the demo crashed after spending a transaction.** With port 8787 already
3951
- busy — an `abx demo` or `abx serve` in another terminal, i.e. exactly what happens
3952
- when you run the demo twice — the deploy went through, was paid for, and _then_ the
3953
- serve step died with an unhandled Node `EADDRINUSE` stack trace. The port is now
3954
- preflighted before anything irreversible, so it refuses with "Nothing was deployed"
3955
- and suggests `--port <n+1>`.
3956
-
3957
- **`abx demo` is now a walkthrough rather than a smoke test.** The docs point a
3958
- first-time reader here, but it asserted its interesting claims without ever showing
3959
- them — "reconstructed 9 events — no provider involved" with the events invisible.
3960
- It now teaches, continuously and without pauses (so agents and CI behave
3961
- identically): it explains why the trust anchor is the factory and not a spoofable
3962
- event, names what goes on chain versus what stays a keccak256 commitment, prints
3963
- the reconstructed event spine with what each event told us (tagged ABX vs plain
3964
- ERC-721/7572), then **deletes its own local projection and replays it from the
3965
- deploy block**, comparing a sha256 fingerprint of every chain-derived field to prove
3966
- it lands on identical state. Finally it reads the token back the way a marketplace
3967
- would. `abx deploy` is unchanged — it stays terse.
3968
-
3969
- Adds `Store.dropProjection(address)` / `SelfHostIndexer.dropProjection()`: discard a
3970
- project's reconstructed projection while keeping its registration, so the next index
3971
- rebuilds from the deploy block. That's the primitive the rebuild proof needs, and it
3972
- makes "the projection is a disposable cache" a checkable claim rather than a comment.
3973
-
3974
- - Updated dependencies [afb36a3]
3975
- - @artblocks/abx-token-api@0.1.0-alpha.2
3976
-
3977
- ## 0.1.0-alpha.4
3978
-
3979
- ### Minor Changes
3980
-
3981
- - 48d96c5: Add **`abx preview`** — the studio lane for code projects: run the program on
3982
- localhost, live, while it's still being made. No chain, no key, no deploy.
3983
-
3984
- `abx preview --script art.js --schema "palette:HexColor:TokenOwner"` serves a
3985
- studio on `localhost:8788` — shuffle seeds, drive every declared PostParam from a
3986
- real typed input (a color picker for `HexColor`, a dropdown for `Select`), read the
3987
- traits the program actually reported, and `/grid` to see N seeds at once. `/view`
3988
- is the bare document. The program is re-read from disk on every render, so the loop
3989
- is edit-and-refresh with no watcher and no restart.
3990
-
3991
- It serves the **same template-mode document the generator serves** — the real
3992
- `abx.js`, the real canonical `tokenData` shape, the real dependency script tags —
3993
- with a synthetic bytes32 seed in place of a minted one, so what you approve is what
3994
- deploys. There is no second copy of the runtime to drift from. (`@artblocks/abx-token-api`
3995
- now exports `ABX_JS` and the inline-safety escapes so the CLI can build that exact
3996
- document rather than reimplement it.)
3997
-
3998
- `--shoot <dir>` drives the same server headlessly to PNGs plus a `traits.json` and
3999
- exits — that's how an agent, which can't open a browser, sees what the creator sees.
4000
- It also flags the two silent killers: no frame reporting traits (⇒ no marketplace
4001
- `attributes` on any lane) and identical traits across every seed (⇒ the program
4002
- isn't reading `abx.tokenData.seed`, so the drop mints N identical tokens).
4003
-
4004
- Why a live server rather than a screenshot sweep: a still flattens every time-based
4005
- piece, and `abx.done()` exists precisely because stills need a settle point — so a
4006
- proof sheet of an animated piece is a set of arbitrary frozen frames presented as
4007
- the work. Frames render into a fixed 1000×1000 viewport and are scaled to fit their
4008
- slot, so a program that hardcodes its canvas size is never clipped to its own corner.
4009
-
4010
- Skill: add **Phase 0**, an explicit authoring phase that puts every deploy decision
4011
- (hosting, thumbnail, traits, storage, wallet, supply, royalties, name/symbol) off
4012
- the table until the creator says ship, and points at `abx preview` for the loop. The
4013
- skill previously went straight from "the creator brought an idea" to "pick a deploy
4014
- lane", so an agent helping someone _design_ a piece front-loaded infrastructure
4015
- questions while there was still nothing to look at.
4016
-
4017
- Skill: also teach CLI resolution — probe project-local (`npx --no-install abx`)
4018
- before global, install `@artblocks/abx-cli` (not `@artblocks/abx-sdk`, which ships
4019
- no binary), and default to a per-project install. The skill previously assumed `abx`
4020
- was already on PATH and gave no bootstrap path at all, so agents improvised — one
4021
- installed the SDK, then went global unprompted.
4022
-
4023
- ### Patch Changes
4024
-
4025
- - 48d96c5: `abx demo` now honors the signing lane instead of silently discarding it. It
4026
- hard-forced the hot (env-key) lane, so `abx demo --sign` failed confusingly when
4027
- no key was configured — and, worse, signed from the env key when one _was_
4028
- present, even though the operator had explicitly asked for their browser wallet.
4029
- `--sign` (and `--for` to pin the connecting wallet) now work on `demo` exactly as
4030
- they do on `deploy`.
4031
-
4032
- `abx demo --unsigned` and `abx demo --dry-run` are now refused with an
4033
- explanation rather than ignored: both skip the broadcast, and `demo` indexes and
4034
- serves the contract it just deployed, so there would be nothing to index. Use
4035
- `abx deploy --unsigned` / `abx deploy --dry-run` for those lanes.
4036
-
4037
- Also documents the `demo` signing flags in `abx help demo`.
4038
-
4039
- - Updated dependencies [48d96c5]
4040
- - @artblocks/abx-token-api@0.1.0-alpha.1
4041
-
4042
- ## 0.1.0-alpha.3
4043
-
4044
- ### Patch Changes
4045
-
4046
- - 6fbb62e: Restructure `abx doctor` for clarity. Two tiers now: PASS/FAIL checks (✓/✗) for
4047
- things that are working or broken — agent skill (shown first and prominently; a
4048
- missing/stale skill is a ✗ since agent-driven use is the primary UX), RPC,
4049
- factory, storage — and an "Optional — depends how you deploy" block (·) for
4050
- path-dependent setup (signing lane, resolver URL, Arweave key). `⚠` is no longer
4051
- used for values that are unset-but-fine (it read as noise); it's reserved for a
4052
- genuine gotcha such as a range-capped-only RPC. The RPC report collapses to one
4053
- line.
4054
- - 6fbb62e: Fix the update-check hint to invoke the CLI by its scoped package name
4055
- (`npx @artblocks/abx-cli@latest <command>`). The bare `npx abx` resolves an
4056
- unrelated squatted `abx` package on npm, not this CLI, so the old hint pointed
4057
- users at a command that fails. (Installed users — global or per-project — run
4058
- `abx` / `npx abx` as before; only the zero-install invocation needs the scoped
4059
- name.)
4060
-
4061
- ## 0.1.0-alpha.2
4062
-
4063
- ### Minor Changes
4064
-
4065
- - 02fc0e0: Make `abx skill install` agent-aware and strictly version-lock the skill to the CLI.
4066
-
4067
- `skill install` now installs to the directories every supported agent actually reads: by default
4068
- both `.claude/skills` (Claude Code) and the neutral `.agents/skills` (Cursor, Codex CLI, Gemini CLI,
4069
- GitHub Copilot), so one command covers the whole ecosystem. `--agent claude|cursor|codex|gemini|copilot`
4070
- narrows it to one; `--target <dir>` now writes the skill folder straight under `<dir>`.
4071
-
4072
- The skill's version now lives in its `SKILL.md` frontmatter (`metadata.version`), stamped at release
4073
- to equal the CLI version (replacing the old `.abx-skill-version` sidecar). Because the version travels
4074
- inside the skill file, the drift check finds a stale copy no matter how it was installed — including
4075
- `npx skills add` — and `abx doctor` reports the skill/CLI version match explicitly. A publish-time gate
4076
- (and `pnpm ci:version` stamping) keeps the two from ever shipping out of lockstep.
4077
-
4078
- ## 0.1.0-alpha.1
4079
-
4080
- ### Patch Changes
14
+ ## Unreleased
4081
15
 
4082
- - b5d201f: Add `abx version` and a notify-only update check. On startup `abx` now checks npm at most once a
4083
- day and, when a newer release is published, prints an upgrade hint to stderr (never stdout, so it
4084
- never corrupts machine-readable output an agent is parsing). It also nudges to reinstall the agent
4085
- skill when the installed copy has drifted behind the CLI. Opt out with `ABX_NO_UPDATE_CHECK=1` or
4086
- `--no-update-check`; it is a no-op in CI and when offline.
16
+ Public release history begins here.