@artblocks/abx-cli 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (137) hide show
  1. package/CHANGELOG.md +1991 -0
  2. package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +17 -0
  3. package/assets/renderer-scaffold/test/MyRenderer.t.sol +51 -2
  4. package/dist/commands/deploy.d.ts +206 -0
  5. package/dist/commands/deploy.d.ts.map +1 -0
  6. package/dist/commands/deploy.js +4273 -0
  7. package/dist/commands/deploy.js.map +1 -0
  8. package/dist/commands/project.d.ts +241 -0
  9. package/dist/commands/project.d.ts.map +1 -0
  10. package/dist/commands/project.js +1060 -0
  11. package/dist/commands/project.js.map +1 -0
  12. package/dist/commands/reads.d.ts +64 -0
  13. package/dist/commands/reads.d.ts.map +1 -0
  14. package/dist/commands/reads.js +697 -0
  15. package/dist/commands/reads.js.map +1 -0
  16. package/dist/commands/scaffold.d.ts +71 -0
  17. package/dist/commands/scaffold.d.ts.map +1 -0
  18. package/dist/commands/scaffold.js +552 -0
  19. package/dist/commands/scaffold.js.map +1 -0
  20. package/dist/commands/service.d.ts +41 -0
  21. package/dist/commands/service.d.ts.map +1 -0
  22. package/dist/commands/service.js +618 -0
  23. package/dist/commands/service.js.map +1 -0
  24. package/dist/commands/storage.d.ts +37 -0
  25. package/dist/commands/storage.d.ts.map +1 -0
  26. package/dist/commands/storage.js +343 -0
  27. package/dist/commands/storage.js.map +1 -0
  28. package/dist/config.d.ts +91 -2
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +244 -11
  31. package/dist/config.js.map +1 -1
  32. package/dist/conformance.d.ts +31 -0
  33. package/dist/conformance.d.ts.map +1 -0
  34. package/dist/conformance.js +371 -0
  35. package/dist/conformance.js.map +1 -0
  36. package/dist/deps.d.ts +6 -39
  37. package/dist/deps.d.ts.map +1 -1
  38. package/dist/deps.js +4 -68
  39. package/dist/deps.js.map +1 -1
  40. package/dist/errors.d.ts +20 -0
  41. package/dist/errors.d.ts.map +1 -0
  42. package/dist/errors.js +25 -0
  43. package/dist/errors.js.map +1 -0
  44. package/dist/flag-allowlists.d.ts +53 -0
  45. package/dist/flag-allowlists.d.ts.map +1 -0
  46. package/dist/flag-allowlists.js +148 -0
  47. package/dist/flag-allowlists.js.map +1 -0
  48. package/dist/flags.d.ts +41 -0
  49. package/dist/flags.d.ts.map +1 -1
  50. package/dist/flags.js +94 -1
  51. package/dist/flags.js.map +1 -1
  52. package/dist/jsonout.d.ts +37 -0
  53. package/dist/jsonout.d.ts.map +1 -0
  54. package/dist/jsonout.js +68 -0
  55. package/dist/jsonout.js.map +1 -0
  56. package/dist/kind.d.ts +53 -0
  57. package/dist/kind.d.ts.map +1 -0
  58. package/dist/kind.js +94 -0
  59. package/dist/kind.js.map +1 -0
  60. package/dist/main.js +534 -4708
  61. package/dist/main.js.map +1 -1
  62. package/dist/mintpage.d.ts +17 -2
  63. package/dist/mintpage.d.ts.map +1 -1
  64. package/dist/mintpage.js +214 -52
  65. package/dist/mintpage.js.map +1 -1
  66. package/dist/output.d.ts +148 -0
  67. package/dist/output.d.ts.map +1 -0
  68. package/dist/output.js +634 -0
  69. package/dist/output.js.map +1 -0
  70. package/dist/ownerops.d.ts +132 -44
  71. package/dist/ownerops.d.ts.map +1 -1
  72. package/dist/ownerops.js +1151 -329
  73. package/dist/ownerops.js.map +1 -1
  74. package/dist/preview.d.ts +81 -0
  75. package/dist/preview.d.ts.map +1 -0
  76. package/dist/preview.js +535 -0
  77. package/dist/preview.js.map +1 -0
  78. package/dist/prompt.d.ts +17 -0
  79. package/dist/prompt.d.ts.map +1 -0
  80. package/dist/prompt.js +19 -0
  81. package/dist/prompt.js.map +1 -0
  82. package/dist/provision.d.ts.map +1 -1
  83. package/dist/provision.js +10 -7
  84. package/dist/provision.js.map +1 -1
  85. package/dist/remote.d.ts +134 -53
  86. package/dist/remote.d.ts.map +1 -1
  87. package/dist/remote.js +358 -46
  88. package/dist/remote.js.map +1 -1
  89. package/dist/riskgate.d.ts +29 -0
  90. package/dist/riskgate.d.ts.map +1 -0
  91. package/dist/riskgate.js +91 -0
  92. package/dist/riskgate.js.map +1 -0
  93. package/dist/scaffold.d.ts +10 -0
  94. package/dist/scaffold.d.ts.map +1 -0
  95. package/dist/scaffold.js +52 -0
  96. package/dist/scaffold.js.map +1 -0
  97. package/dist/schema.d.ts +18 -1
  98. package/dist/schema.d.ts.map +1 -1
  99. package/dist/schema.js +74 -23
  100. package/dist/schema.js.map +1 -1
  101. package/dist/served.d.ts +30 -0
  102. package/dist/served.d.ts.map +1 -0
  103. package/dist/served.js +112 -0
  104. package/dist/served.js.map +1 -0
  105. package/dist/signer.d.ts +13 -0
  106. package/dist/signer.d.ts.map +1 -1
  107. package/dist/signer.js +80 -14
  108. package/dist/signer.js.map +1 -1
  109. package/dist/update-check.d.ts +83 -4
  110. package/dist/update-check.d.ts.map +1 -1
  111. package/dist/update-check.js +132 -17
  112. package/dist/update-check.js.map +1 -1
  113. package/package.json +10 -6
  114. package/skill/SKILL.md +108 -62
  115. package/skill/reference/code-projects.md +109 -13
  116. package/skill/reference/creator-token.md +86 -0
  117. package/skill/reference/decisions.md +87 -0
  118. package/skill/reference/hosting.md +48 -15
  119. package/skill/reference/operating.md +32 -8
  120. package/skill/reference/setup.md +10 -3
  121. package/skill/reference/troubleshooting.md +31 -4
  122. package/dist/inspect.d.ts +0 -48
  123. package/dist/inspect.d.ts.map +0 -1
  124. package/dist/inspect.js +0 -184
  125. package/dist/inspect.js.map +0 -1
  126. package/dist/migrate.d.ts +0 -65
  127. package/dist/migrate.d.ts.map +0 -1
  128. package/dist/migrate.js +0 -180
  129. package/dist/migrate.js.map +0 -1
  130. package/dist/onchain-uri.d.ts +0 -97
  131. package/dist/onchain-uri.d.ts.map +0 -1
  132. package/dist/onchain-uri.js +0 -243
  133. package/dist/onchain-uri.js.map +0 -1
  134. package/dist/upload.d.ts +0 -28
  135. package/dist/upload.d.ts.map +0 -1
  136. package/dist/upload.js +0 -41
  137. package/dist/upload.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,1991 @@
1
+ # @artblocks/abx-cli
2
+
3
+ ## 0.1.0-alpha.21
4
+
5
+ ### Patch Changes
6
+
7
+ - 528c6c6: Close most of the ERC-1155 edition CLI parity gaps (B28), give every command one unknown-flag posture
8
+ (B30), and add the per-holder edition balance read (B31).
9
+
10
+ Each B28 item was checked against the tree before any code was written: **none needed a Solidity
11
+ change.** The contracts and SDK ops were already per-id and standard-neutral, so all of this is CLI
12
+ plumbing and **no redeploy, address churn, or Etherscan re-verification is involved.**
13
+
14
+ **Edition custody is now symmetric with the 721 side** — each lane verified live on testnet, not just
15
+ by dry run:
16
+
17
+ - **`deploy-series --copies` gains pattern 2: off-chain image, on-chain JSON, nothing to run.** The 721
18
+ Series had the whole branch; `EditionImage` never got it, so a multi-artwork **raster** edition had no
19
+ no-server option at all. A uniform file extension uploads the folder as ONE directory and bakes a
20
+ single `{id}`-substituting collection field, so the collection costs one field instead of N; mixed
21
+ extensions fall back to per-id `url` fields. Verified at
22
+ `0x4e7e2d1aA951d99046323a620a2a5891b1195452`, where `uri(0|1|2)` resolve to `…/0.png`, `…/1.png`,
23
+ `…/2.png` off one template and all three fetch at their source byte sizes. This gap had real design
24
+ consequences: a cold agent asked for "3 artworks × 25 copies each", hit the SVG-only refusal, and
25
+ split the collection into **three separate single-artwork contracts** to get the permanence it wanted.
26
+ - **`--onchain-image` works on both edition lanes, on the hot AND wallet lanes.** It was refused
27
+ outright on `deploy-series --copies` and hot-lane-only on `deploy --copies`; one sign session now
28
+ covers the chunk writes plus the deploy, and the staging txs are counted in the `approvals` line the
29
+ session's own total is derived from. Verified at `0x0A71382207a980c23E26a54F2b815072da41761A`:
30
+ `uri(id)` returns a `data:image/svg+xml;base64` image with zero `http(s)` anywhere in the document.
31
+ `EditionImage` also now treats `--onchain-image` as implying on-chain resolution — every sibling lane
32
+ already did, and without it the flag would have demanded a resolver URL for a drop whose bytes are
33
+ already on-chain.
34
+ - **`deploy-code --copies` accepts on-chain `--dep`/`--dep-registry`.** `EditionCode` already inherited
35
+ the `Dependencies` extension and called `_initDependencies()`; the legs come from the SDK's shared
36
+ `dependencySetupCalls`, so the two lanes cannot encode `setDependency` differently. So "an edition of
37
+ my p5 sketch, with p5 coming from the chain" is a real lane now — verified on Sepolia at
38
+ `0xC403CB9B956Ca814F1934d8d2A44fc1D2023EFB3`, where `abx verify` reports **chain-complete — every
39
+ dependency resolves to proven on-chain bytes; no server, gateway, or CDN in the graph**, with a 233 KB
40
+ inline `animation_url`.
41
+ - **The cold lane stays refused for `--onchain-image`**, deliberately and identically on all four paths:
42
+ staging is a sequence where each chunk tx's receipt feeds the next, so it cannot be signed offline.
43
+ This replaces an edition-specific "tracked as a follow-up" note with the real, shared reason.
44
+
45
+ Still open on the edition lanes, and recorded in B28: `deploy-code --copies` has no `--code-dir` or
46
+ field renderers, and `deploy-code --resume` has no edition path (that one is genuinely new per-id diff
47
+ logic, not a port).
48
+
49
+ **Every command now has one unknown-flag posture.** Roughly 35 commands — `state`, `add`, `index`,
50
+ `migrate`, `configure-param`, `set-field`, `set-royalty`, `set-minter`, `pause`/`unpause`, `verify`, … —
51
+ silently ignored an unrecognized flag, so `abx set-royalty --bsp 500` did something quietly different
52
+ from what was asked. Allowlists now live in one file and are applied once in the dispatcher; it warns
53
+ rather than refuses (a false refusal would break a working script, and `deploy*` still refuses via its
54
+ own exhaustive sets).
55
+
56
+ The obvious implementation — deriving each allowlist from the command's own `usage` string — was tried
57
+ and **rejected on evidence**: shared helpers read flags that appear in no usage string anywhere
58
+ (`storageOverrides()` reads twelve, pulled in by six commands; `--remote-token` by five), so deriving
59
+ would have warned on valid flags. That is worse than the silence it replaces, because a notice people
60
+ learn to distrust stops working for the real cases too. The sets are explicit instead, composed from
61
+ named groups, and a test reads each command's `--help` to assert every flag it **documents** is accepted
62
+ — the direction that would produce false warnings.
63
+
64
+ **`abx tokens <addr> --holder 0x..`** adds a `held` column — `balanceOf(holder, id)` per id, a live head
65
+ read. It answers the one question supply/cap cannot, and the one a funded agent had to leave blank after
66
+ transferring a copy (it correctly declined to fabricate the recipient's balance rather than guess).
67
+ Refused on a 721, where the owner column already answers it. Also: `deploy-series`' `[2] On-chain
68
+ renderer` step now prints the renderer address instead of an empty header, and `abx verify` no longer
69
+ ends in two ⚠ on a healthy fully-on-chain **image** drop (the code-project checks are scoped to the code
70
+ twins; a static drop has no program and no `animation_url` by design).
71
+
72
+ - 528c6c6: ERC-1155 editions: the trust-anchor and on-chain-URI checks told the truth about 721s only, and four
73
+ membrane gaps around them (2026-08-05 wave-4 agent sweep — 12 cold agents, one funded end-to-end run
74
+ on Base Sepolia).
75
+
76
+ The 1155 contracts themselves were fine: a funded run took an edition from deploy through
77
+ `tokenuri` → mint more copies → transfer one copy → lower the per-id cap, verifying every step
78
+ against the chain. What shipped wrong was everything that _describes_ an edition.
79
+
80
+ - **`abx verify` reported EVERY canonical edition as `canonical: NO`.** `detectCanonicalFactory`
81
+ probed only the three 721 anchors and, on no match, fell back to the 721 1/1 factory — whose
82
+ `isAbxClone` answers **false**, not "unknown". So the one signal platforms allowlist against was
83
+ confidently inverted for the entire edition line (the function's own docstring promised
84
+ "canonicity just shows unverified, never wrong"). All six anchors are now probed. **Editions
85
+ registered before this fix keep the wrong factory in the local projection** — `abx forget <addr>`
86
+ then `abx add <addr>` re-detects it.
87
+ - **`abx verify`'s on-chain-URI probe called the ERC-721 `tokenURI` selector on editions**, which
88
+ exposes `uri(id)`. It reverted for every edition, so verify could never confirm that a
89
+ fully-on-chain edition resolves — and it printed the raw multi-line viem dump (Contract Call /
90
+ args / Docs / Version) into a creator-facing readout. The probe now switches on `contractType`
91
+ (matching `reconstruct.ts`, which already did), reports which accessor it read so an edition is
92
+ never described in 721 terms, and readout errors are trimmed to their first line.
93
+ - **An address with no contract was reported as a specific type: `OneOfOneImage`.**
94
+ `detectTokenKind`'s three probes are each `try/catch → false`, so nothing-deployed and
95
+ no-extensions-composed were indistinguishable and the ladder fell through to its `1of1` default.
96
+ `abx set-max-supply` and `abx minter buy` then told edition owners _"…is a OneOfOneImage (721) —
97
+ drop your edition flags"_ for a mistyped address or, far more often, the wrong `ABX_CHAIN` (the
98
+ default is base-sepolia, so any Sepolia contract hit this immediately) — advice that removes the
99
+ _correct_ flags. The existence check now lives inside `detectTokenKind`, so all 13 call sites
100
+ inherit it; it fails **open** on an unreadable `getCode`, so an RPC blip never becomes a
101
+ "no contract" claim.
102
+ - **`abx transfer --token-id <n>` silently transferred id 0.** `transfer` spells the id `--token`
103
+ while every sibling command (`mint`, `set-max-supply`, `minter …`) spells it `--token-id`, and an
104
+ unrecognized flag was simply ignored — so an agent that learned the name from `mint` moved the
105
+ **wrong artwork** with no warning. `--token-id` is now an accepted alias; disagreeing values are
106
+ refused rather than silently preferring one.
107
+ - **The edition deploy previews dropped lines their 721 twins print.** All three edition lanes
108
+ omitted `approvals N wallet approval(s)` from the dry-run readout — which the skill promises "every
109
+ preview" prints and tells the agent to state up front, so on an edition the agent had nothing to
110
+ tell the creator about how many wallet prompts were coming (`deploy-code --copies` had it only in
111
+ the `--confirm` sentence, which is off by default). `deploy --copies` and `deploy-series --copies`
112
+ also omitted the "resolves ON-CHAIN via the renderer — no resolver, no server" line, which is
113
+ precisely the guarantee an edition creator is asking about, and `deploy-series --copies` never
114
+ showed `paused`. All six lanes now print the approval count; the 1155 paths say `uri()`.
115
+ - **Owner-op writes now surface a stray flag instead of swallowing it.** `mint`, `transfer`,
116
+ `set-max-supply`, and `minter configure|show|buy` warn on an unrecognized flag (warn, not refuse —
117
+ per `unknownFlags`' contract, a false warning must never break a script). These are the commands
118
+ where the 1155 semantics live in _optional_ flags that default rather than fail: a typo'd
119
+ `--amount 50` minted 1 copy and a typo'd `--quantity 5` bought 1 and paid 1×, both in total
120
+ silence. The broader gap — ~35 commands with no unknown-flag notice at all — is filed as B30.
121
+
122
+ A second verification sweep (six more cold agents, one funded) confirmed each fix above from a fresh
123
+ start and turned up four more, all fixed here:
124
+
125
+ - **A supply cap could be raised in `--dry-run` and only revert on send.** Both twins —
126
+ `set-max-invocations` (721) and `set-max-supply` (1155) — printed the new cap as fact for a value
127
+ the chain forbids, then reverted for real. A clean dry run is read as permission to send. Both now
128
+ read the current cap first and refuse up front; the 1155 side also refuses a cap **below** live
129
+ supply (the second way it reverted). Both guards fail **open** on an unreadable getter, since the
130
+ chain enforces the invariant anyway. (The 721 guard needed the Series ABI — reading `maxInvocations`
131
+ through the 1/1 ABI that `read()` uses throws, which would have made the guard silently never fire.)
132
+ - **`abx refresh` told edition owners ERC-4906 had already pinged marketplaces.** An ERC-1155 edition
133
+ emits no ERC-4906 at all — that is precisely why `ping-uri` exists — so the one sentence a creator
134
+ reads after editing metadata said "already handled" when nothing had been, and `ping-uri --help`
135
+ said the opposite. Two commands contradicting each other on the new lane. `refresh` is now
136
+ kind-aware and names `ping-uri` for editions.
137
+ - **`abx verify` ended in two ⚠ on a perfectly healthy fully-on-chain image drop** — "generator
138
+ reports NO code" and "no animation_url" — because the code-project lane runs whenever a tokenURI
139
+ renderer is set, which is true for every on-chain project, image or code. A static image has no
140
+ program and no `animation_url` by design. Three separate agents named this as the worst thing about
141
+ `verify`, and one said plainly it would make a creator distrust future real warnings. The code-lane
142
+ checks are now scoped to the code twins; a static drop gets one informational line instead. (This
143
+ was pre-existing on the 721 path too, not an editions regression.)
144
+ - **`EditionImage`'s SVG-only refusal was a thinner copy of its 721 twin's** — the Series version names
145
+ three routes (`--onchain-image`, `--backend ipfs|arweave`, or host off-chain); the edition version
146
+ said only "drop `--onchain-uri`". A cold agent asked for "3 artworks × 25 copies each" read that,
147
+ concluded there was no no-server option for one contract, and **fanned the collection out into three
148
+ separate single-artwork contracts**. The refusal now states the real options for the lane and says
149
+ explicitly that the off-chain-image-URL-in-on-chain-JSON route exists only per-artwork via
150
+ `abx deploy --copies` — one contract per artwork, not one collection. The underlying capability gap
151
+ is recorded in B28.
152
+
153
+ One knock-on from the first fix: the `false` branch of the canonical readout said _"not a clone of the
154
+ **configured** factory"_ (singular), which now understates the check and misdirects the reader — six
155
+ anchors are probed, so a `false` means no trust anchor the CLI knows deployed this contract, and the
156
+ usual real cause is a **superseded** factory or a hand-deployed contract, not a misconfiguration. It
157
+ now says so, and points at `abx doctor` for the current anchors.
158
+
159
+ Also: `deploy --help` no longer calls the edition sale stack (`--minter`/`--primary-payee`/
160
+ `--unpaused`) "required" — it is optional, and an agent handed a creator hardcoded addresses as
161
+ mandatory because of it; `set-field --help` now lists all eleven `--representation` values (it showed
162
+ seven, omitting `url-template`, `renderer`, `sha256`, `inline-gzip`); and `abx doctor`'s label column
163
+ widened so `edition factory` no longer runs into its address.
164
+
165
+ The skill-drift ✗ now names **which** copy is stale and prescribes the command that actually clears
166
+ it — `abx skill install` writes the project-local copy only, so a stale **global** copy produced a ✗
167
+ whose own remedy could not fix it. Six agents hit it in one sweep; several re-ran the install
168
+ repeatedly and one left its sandbox to read `~/.claude/skills` to work out what the tool meant.
169
+
170
+ - 528c6c6: Wave-6 validation sweep: stop three help strings from denying features that work, make `storage show`
171
+ honor the flags it advertises, and name the byte custody on both Series lanes.
172
+
173
+ Nine cold agents re-drove the edition surface after the B28/B30/B31 work. **Every new lane is reachable
174
+ and no re-run regressed** — the situation that previously fanned "3 artworks × 25 copies" out into three
175
+ separate contracts now reports _"Contracts deployed: 1, not 3"_; `--onchain-image` + edition + browser
176
+ wallet went 4/5 at Haiku after being refused outright; the on-chain-dep edition lane went 3/5 → 4/5; the
177
+ wrong-chain diagnosis went 2/5 → 3/5 on the strength of the new monotonic-cap guard. **B30 produced zero
178
+ false warnings across all nine**, including a run that control-tested the mechanism with a deliberate
179
+ bogus flag, and a funded run that exercised ~10 commands with real writes.
180
+
181
+ What the sweep found was mostly **documentation still describing behavior the same branch had replaced** —
182
+ the failure mode a test suite cannot catch, because tests assert what the code does and none of them read
183
+ the prose that tells an agent what to try:
184
+
185
+ - **`deploy --help`** said `--onchain-image` is "hot lane only on an edition (not yet wired for `--sign`)".
186
+ It works on the wallet lane. Now states the real rule: hot and `--sign` both work; `--unsigned` is
187
+ refused everywhere, 721 and edition alike, because each chunk tx's receipt feeds the next.
188
+ - **`deploy-code --help`** listed `--dep`/`--dep-registry` among flags "refused in code" for `--copies`.
189
+ They work. An agent verified all four flags directly and called this out as _"the dangerous direction:
190
+ it would talk a creator out of a feature that actually works."_ Now says they work, and names the
191
+ Sepolia-only registry constraint.
192
+ - **The `EditionImage` SVG-only refusal** — a message rewritten earlier in the same branch — still said
193
+ `--onchain-image` "is not wired on the edition-series lane yet", after that branch wired it. It now
194
+ lists all four real routes for raster art in one contract.
195
+ - **`abx refresh`'s ERC-4906 claim had no edition caveat** in the skill or `site/using-abx/guides/operate`,
196
+ even though the CLI itself was made kind-aware earlier in the branch. The skill's _"`abx refresh` is the
197
+ only nudge"_ was flatly false for an edition, where `ping-uri` is required.
198
+
199
+ Two real product fixes, both surfaced by the new warning doing its job:
200
+
201
+ - **`abx storage show` now honors `--backend`/`--gateway`/the rest of the storage override set.** It read
202
+ env-only while its own output said _"choose per command: `--backend` … `--gateway`"_ — advertising flags
203
+ it then discarded and always reporting `backend: fs`. This was invisible until every command grew an
204
+ unknown-flag notice, which correctly flagged it (the flags really were ignored). A resolution preview
205
+ that can't preview a resolution is the one thing it exists to do. Provenance now reports `--backend` as
206
+ a source, alongside env and default.
207
+ - **Both `deploy-series` lanes now name the byte custody** (`storage: ipfs (--backend) — byte custody for
208
+ the image(s)`), as the 1/1 lane always has. A creator asking for "images on IPFS" had no way to confirm
209
+ from the preview that their collection would pin there; the flag was accepted in silence.
210
+
211
+ Also documented, not changed: the ids × copies arithmetic. `deploy-code --copies 100` takes its id space
212
+ from `--max`, which **defaults to 16** — so "an edition of 100" without `--max 1` means up to 16 different
213
+ variations × 100 copies each. The dry-run states the real shape (`up to N id(s), M copies each`) and always
214
+ did; nothing taught the translation, so the skill now does, for all three commands.
215
+
216
+ Three legibility nits were routed to B32 rather than fixed: `abx state` naming a contract "Series" where a
217
+ refusal calls it "SeriesCode"; the Base-Sepolia default appearing only in a subordinate clause of the
218
+ "no contract" error (three straight waves have lost time to it); and `--onchain-image` on a folder of large
219
+ rasters honestly reporting 35 wallet approvals with no nudge toward the cheaper lane.
220
+
221
+ Two agent claims were checked and **rejected** so they don't get "fixed" into regressions: the skill does
222
+ _not_ say `--onchain-image` is refused on an edition series (it documents the `--unsigned`-only refusal
223
+ correctly), and `abx predict` does _not_ silently pick the 721 factory without `--copies` — it labels the
224
+ lane, yields a different address, and echoes `--copies` in its reproduce line.
225
+
226
+ - Updated dependencies [528c6c6]
227
+ - @artblocks/abx-sdk@0.1.0-alpha.13
228
+ - @artblocks/abx-indexer@0.1.0-alpha.14
229
+ - @artblocks/abx-storage@0.1.0-alpha.13
230
+ - @artblocks/abx-token-api@0.1.0-alpha.16
231
+
232
+ ## 0.1.0-alpha.20
233
+
234
+ ### Minor Changes
235
+
236
+ - afa9dd4: ERC-1155 editions ship with full parity: a Series is many unique tokens (721); an **Edition** is
237
+ many copies of a token (1155). The creator's word is **copies** — `abx deploy img.png --copies 100`
238
+ (a single-artwork edition, `open` = uncapped), `abx deploy-series ./art --copies 50` (each image an
239
+ edition), `abx deploy-code sketch.js --copies 25` (code editions). Without `--copies`, the 721
240
+ lanes are unchanged.
241
+
242
+ Three new canonical contracts twin the 721 lineup — `OneOfOneEdition`, `EditionImage`,
243
+ `EditionCode` — plus `AbxFixedPriceMinter1155`, a per-id fixed-price sale singleton
244
+ (`minter configure/buy --token-id [--quantity]`). New owner ops: `mint --token-id --amount`,
245
+ `transfer --amount`, `set-max-supply` (per-id cap, only ever decreases), `ping-uri` (re-emit the
246
+ native `URI` event after a re-point). ERC-1155C creator-token enforcement is the same `--721c`
247
+ opt-in (same validator registry and ERC-165 ids as 721C). Editions announce one new extension
248
+ (`abx.extension.edition-supply`, per-id supply/cap); everything else — metadata fields, params,
249
+ royalties, renderer, storage, effects — is the same protocol surface on both standards.
250
+
251
+ **Integrator surface (audience line):** additive only. `ProjectState.contractType` gains
252
+ `'1of1-edition' | 'edition' | 'edition-code'`; `TokenState` gains `supply`/`maxSupply`/`holders`
253
+ (editions only; `minted` means supply > 0 there); the spine ABI now decodes
254
+ `TransferSingle`/`TransferBatch`/`URI`/`MaxSupplyUpdated`; `ChainDeployment` gains the four
255
+ edition anchors (recorded for Sepolia + Base Sepolia, CREATE2-identical); token-api summaries gain
256
+ an optional `copies` field. No existing export, route, column, or event shape changed.
257
+
258
+ ### Patch Changes
259
+
260
+ - 8c254d5: The self-host projection no longer stores or pre-fetches composed URI documents, and the event log
261
+ is append-only (from the abx-services 2026-08-05 projection memo, `docs/11`).
262
+
263
+ - **`contractURI`/`tokenURI` are live reads, never projected.** `reconstructProject`/
264
+ `reconstructIncremental` gain `readUriDocuments` (default **false**) — with it off, the whole
265
+ `contractURI`/`tokenURI` head-read batch is skipped entirely, not fetched-and-discarded. These
266
+ fields have no settled value (a renderer can change the composed document with no log at all) and,
267
+ on the on-chain lane, can run hundreds of KB per token — a real 32-token project measured 13.4 MB
268
+ serialized, 99.8% of it `tokenURI`. A stale-but-rendering `data:` URI is worse than a missing one,
269
+ because nothing about it looks wrong. `abx demo` is the one caller that passes `readUriDocuments:
270
+ true`, for its read-back teaching step; `abx deploy`/`deploy-series`/`deploy-code` don't, and
271
+ `abx verify`/`tokenuri`/`contracturi` were already doing independent live reads. **For
272
+ integrators:** `ProjectState.contractURI` / `TokenState.tokenURI` are now `null` unless you opt in
273
+ — including in a self-host node's `/api/project/:addr` responses (shape unchanged, values now
274
+ null). Nothing that composes served metadata reads either field (the settled `fields`/
275
+ `collectionFields` do); the settled scalars — `tokenURIRenderer`/`tokenURILocked`/
276
+ `contractURIRenderer`/`contractURILocked` — are unaffected and still projected.
277
+ - **The events table is append-only.** `seq` is now chain-derived (`(blockNumber << 32) |
278
+ logIndex`, BigInt-safe) instead of the array index at write time, so it's stable across re-folds —
279
+ a re-index appends via `ON CONFLICT DO NOTHING` instead of deleting and reinserting the whole
280
+ history on every delta. `putProject` upserts the project and token rows in place (a token whose id
281
+ vanishes from state is still removed) rather than delete-and-reinsert. `deregister` still deletes
282
+ a forgotten project's events in full; a routine re-fold does not. **A full replay (`abx index
283
+ --full`) purges the project's event log before rebuilding it** — append-only must not let a
284
+ reorg-replaced event survive as a stale row, so the documented repair path stays a real repair.
285
+ Existing stores migrate their positional `seq` values to the chain-derived form once, automatically
286
+ (`PRAGMA user_version`-gated, transactional).
287
+ - **Self-healing eventCount guard.** An incremental fold that would reduce `eventCount` below what's
288
+ already stored is discarded rather than written, with one automatic full-reconstruct fallback (the
289
+ memo's own war story: a bad fold once silently zeroed a live projection with nothing to catch it).
290
+ - **New self-host stores open with `auto_vacuum = INCREMENTAL`.** Existing stores are unaffected
291
+ (the pragma only takes on a database with no tables yet) — recorded as backlog B29.
292
+
293
+ - Updated dependencies [afa9dd4]
294
+ - Updated dependencies [8c254d5]
295
+ - @artblocks/abx-sdk@0.1.0-alpha.12
296
+ - @artblocks/abx-indexer@0.1.0-alpha.13
297
+ - @artblocks/abx-token-api@0.1.0-alpha.15
298
+ - @artblocks/abx-storage@0.1.0-alpha.12
299
+
300
+ ## 0.1.0-alpha.19
301
+
302
+ ### Minor Changes
303
+
304
+ - d40caf4: `abx remote <name|url> --conformance` — self-certify a resolver from the published CLI
305
+
306
+ The self-verify fixture that used to live only as a repo-local script
307
+ (`scripts/conformance-remote-service.mjs`, `pnpm conformance`) is now a mode on the existing `remote`
308
+ command rather than a new one (command-count discipline): `abx remote <name|url> --conformance
309
+ [--remote-token <t>] [--chain-id <n>] [--address <a>] [--from-block <n>]`.
310
+
311
+ This is what lets a third party self-certify a hosted resolver — or CI-gate on one — with nothing but
312
+ `npm i -g @artblocks/abx-cli` and a base URL: no repo checkout, and a bare URL + `--remote-token`
313
+ needs no `.env` setup. It reuses the same `--remote` name/URL/token resolution as every other remote
314
+ command, prints one ✓/✗/· line per assertion (descriptor shape, unauthenticated writes refused, the
315
+ read-plane's error taxonomy, every declared interface's routes, and — with `--chain-id` + `--address`
316
+ naming a contract you own — the full register → poll-to-live → status → reindex → deregister loop),
317
+ and sets a non-zero exit code on any failure.
318
+
319
+ The assertions themselves moved to `packages/cli/src/conformance.ts`, importable by both the CLI
320
+ command and the (now much thinner) root script, so `pnpm conformance` and
321
+ `scripts/e2e-remote-resolver.sh` keep working unchanged against the same logic.
322
+
323
+ ### Patch Changes
324
+
325
+ - d40caf4: Two first-party consumers adopt the backoff/gateway/data-URI helpers hoisted into `@artblocks/abx-sdk`
326
+ last round, and `mint-page` refuses a 1/1 target instead of scaffolding a page that hangs. Drop-in for
327
+ everyone — no behavior change in the adoption itself, and the mint-page refusal only fires for a
328
+ contract type the page never worked against.
329
+
330
+ **`token-api`'s chain watcher now calls `exponentialBackoffDelay`/`linearBackoffDelay` instead of
331
+ re-deriving them inline.** `dueForRetry`'s failed-project retry and the tick loop's repeated-failure
332
+ delay were the sdk helpers reimplemented by hand; both now call the shared functions, with the exact
333
+ index mapping (`min(attempts, 8) + 1` into the 1-indexed exponential helper) that reproduces identical
334
+ delays for every `attempts`/`failures` value — proved by an old-formula-vs-sdk-helper equality table
335
+ in `packages/token-api/test/watcher-backoff.test.ts` (attempts/failures 0..12, several `baseMs`
336
+ values, and the cap boundary explicitly).
337
+
338
+ **The `mint-page` template's generated app now reads gateway URLs and `data:` URIs through the sdk**
339
+ (`gatewayUrlFor`/`resolveGatewayBase`/`parseDataUri`) instead of a hand-rolled `ipfs://`-slicing
340
+ `toGateway` and a hand-parsed comma-split in `fetchJson`. Same output for every input (plain
341
+ `https://`, empty string, `ipfs://`, `ar://`, base64 and percent-encoded `data:` bodies) — verified
342
+ byte-for-byte before the swap, and pinned by a new mintpage.test.ts assertion that the emitted
343
+ `lib/meta.ts` imports the sdk helpers and no longer contains the old hand-rolled body.
344
+
345
+ **`mint-page` now refuses a non-Series target.** The generated page reads `maxInvocations`/`paused`
346
+ and mints through the shared fixed-price minter — a 1/1 (`OneOfOneImage`) has none of that, so pointing
347
+ `mint-page` at one silently wrote a page that sat on "Loading…" forever with no diagnostic. `cmdMintPage`
348
+ now probes the same ERC-165 `supportsInterface(IAbxSequentialMint)` `sdk/anchors.ts`'s
349
+ `isCurrentSeriesFactory` uses, and refuses with a `CliError` pointing at `abx deploy-series --dir
350
+ <folder> --count 1` (matching the existing guidance in `sales.mdx`). A probe that can't run at all (RPC
351
+ unreachable) is left undecided and does NOT refuse — scaffolding stays usable offline, same tolerance
352
+ the existing best-effort `name()` read already has.
353
+
354
+ Also folded in: `PUBLIC_RPC` (the mint-page template's keyless-RPC table) gains `base` and
355
+ `base-sepolia` — the toolkit's own default chain was missing from it, so every default-chain deploy
356
+ needed a manual `--rpc` just to scaffold its own mint page.
357
+
358
+ - Updated dependencies [d40caf4]
359
+ - Updated dependencies [d40caf4]
360
+ - Updated dependencies [d40caf4]
361
+ - @artblocks/abx-sdk@0.1.0-alpha.11
362
+ - @artblocks/abx-token-api@0.1.0-alpha.14
363
+ - @artblocks/abx-storage@0.1.0-alpha.11
364
+ - @artblocks/abx-indexer@0.1.0-alpha.12
365
+
366
+ ## 0.1.0-alpha.18
367
+
368
+ ### Minor Changes
369
+
370
+ - 11fa933: Phase 5 interface unifications: the self resolver becomes a named remote, doctor absorbs the
371
+ version/install/provenance ladder, deploy previews stop showing addresses they can't keep,
372
+ approvals are counted honestly, render×storage combos are validated once, and storage config gets
373
+ a real probe.
374
+
375
+ - **Self resolver is a named remote (breaking, client-side only)**: the CLIENT credential for the
376
+ local/self resolver moves from `ABX_RESOLVER_ADMIN_TOKEN` to the named-remote grammar
377
+ (`ABX_REMOTE_SELF_URL` / `ABX_REMOTE_SELF_TOKEN` — "self" is just a conventional remote name, zero
378
+ special-casing). The SERVER side (token-api control-plane auth, `provision.ts`, the Fly.io secret)
379
+ keeps its old name — it's the service's own config, not a client credential, so deployed resolvers
380
+ need no change. The old client var, if still set, is now a pointed `CliError` naming both new vars
381
+ (no silent fallback); `abx doctor` flags it too, and the near-miss `_KEY`-vs-`_TOKEN` detector keeps
382
+ working under the unified grammar.
383
+ - **`abx doctor` absorbs the version/install/provenance ladder** the skill used to only teach in
384
+ prose: one new ✓/✗ block for binary provenance (source checkout / npm install / npx — the silent-
385
+ stale-npx-cache trap), npm currency (reuses the existing cached update check), and skill↔CLI
386
+ version match (reuses `abx skill`'s own discovery). Each row names the exact fix.
387
+ - **Deploy previews stop showing addresses they can't keep**: `--dry-run` (`deploy` /
388
+ `deploy-series` / `deploy-code`) **without `--salt`** no longer prints a "deterministic address" —
389
+ that salt was just freshly, randomly reserved, so the address was real for that one preview and
390
+ never reproducible by a plain re-run. It now prints the salt itself, prominently, plus how to pin
391
+ it: re-run with `--salt <shown>` (guaranteed same address), or `abx predict --salt <shown> --for
392
+ <signer>`. **With `--salt`, the address prints exactly as before** (it IS stable) — `abx predict`
393
+ is unchanged. The same rule applies to `--json`: without `--salt`, `address` reports `null` rather
394
+ than a value the real deploy won't land at (`saltPinned` still says why).
395
+ - **`approvals` — a labeled, honest signature count**: every deploy-family `--dry-run` preview and
396
+ `--confirm` summary now carries one uniform `approvals N wallet approval(s)` line/clause — the
397
+ number of **wallet TX signatures** the real run will ask for (a connected-wallet storage upload,
398
+ e.g. Arweave via `--storage-signer eth`, is a message signature, not a transaction, and stays
399
+ listed separately, as it already was). It's derived from the exact same staging/setup-multicall
400
+ math that sizes the wallet-lane session's own `total`, so the two can never disagree — this caught
401
+ (and fixed) `deploy-code`'s wallet-lane session `total` being hardcoded to `2` even on the 1-tx path
402
+ (no chunks/schema/deps/on-chain-uri legs/setup-carried mints), which would have shown "transaction
403
+ 1 of 2" and then silently never asked for a second.
404
+ - **One validator for render×storage combos**: `@artblocks/abx-storage` gains
405
+ `validateRenderStorageCombo()` (`content-plan.ts`) — the single source of truth for two known-bad
406
+ configurations, each checked against the ACTUAL fact that makes it bad (not against an unrelated
407
+ flag): (1) `--image-base` needs a backend that can overwrite a stable per-token key in place; an
408
+ ipfs/arweave-_shaped URL_ is refused regardless of this deploy's own `--backend`, because those are
409
+ content-addressed — a re-upload gets a new address, so no fixed URL can point at it. (2) publishing
410
+ a render to a resolver that doesn't share this machine's disk needs a backend that can hand back a
411
+ public URL at all — `fs` (or `cloud` with no public base) can't. `deploy-code` wires this into BOTH
412
+ its real-run refusal (`--image-base`) and a new `render/storage ✓|✗ <reason>` dry-run row;
413
+ `requirePublishableBackend` (the existing `abx render --remote` / `abx effects` guard) now consults
414
+ the same validator for its ok/not-ok decision, so the two surfaces can't drift apart.
415
+ - **`abx storage show --check`**: a real read/write against the resolved storage config, not just
416
+ "is it configured." `cloud` PUTs a tiny object through the signed API and GETs it back over the
417
+ PUBLIC base with a plain unsigned fetch — the only check that catches the R2/S3
418
+ endpoint-vs-public-base trap (`health()` alone only proves the API credentials work); on failure
419
+ both URLs print, so the mismatch is visible. `ipfs`/`fs` reuse their existing `health()` verbatim
420
+ (gateway/API reachability, dir writability — no upload, no pin — reused, not reimplemented).
421
+ `arweave` adds an identity+balance READ, never a paid upload. Exit code is meaningful (0 ok / 1 any
422
+ ✗) so a script can gate a launch on it. `abx doctor`'s storage row now runs the same fuller probe
423
+ (bounded to 1.5s, matching its other fast network checks) instead of a bare `health()` call.
424
+
425
+ ### Patch Changes
426
+
427
+ - 11fa933: The remaining phases of the simplification refactor that hadn't yet gotten a changeset: the CLI's
428
+ internal module split, the token-api/effects/mint-page convergence on the SDK, the shipped skill's
429
+ rewrite for the simplified surface, and a new SDK README.
430
+
431
+ - **`abx`'s `main.ts` split into domain command modules** (`commands/{deploy,project,reads,service,
432
+ scaffold,storage}.ts`, shared `output.ts`/`errors.ts`), with one exit-discipline rule
433
+ (`process.exitCode` + return, or a typed `CliError`, everywhere — bare `process.exit` only at the
434
+ entry guard, the top-level catch, and the keep-alive SIGINT handler). Purely internal: a 207-fixture
435
+ byte-diff matrix (every help text, dry-run, error path, and exit code) confirmed identical output
436
+ before and after.
437
+ - **token-api / effects / mint-page converge on the SDK**: `@artblocks/abx-storage` gains one
438
+ `resolveGatewayBase` (`readiness.ts`), replacing three near-identical copies (two in token-api, one
439
+ inline in storage itself); token-api exports `buildGeneratorDocument` so the CLI's `abx preview`
440
+ consumes the real generator-document assembler instead of a hand-kept duplicate; the effects runner
441
+ now resolves its config via the SDK's `readEnv` and gets a `makePublicClient` fallback transport, so
442
+ `ABX_RPC_URL` accepts a comma-separated failover list like every other RPC var; the scaffolded
443
+ mint-page app now imports ABIs from `@artblocks/abx-sdk/abi` and a browser-safe `makePublicClient` +
444
+ typed `readSaleConfig` instead of hand-rolled fetch/decode, and pins its generated `package.json` to
445
+ the SDK's _resolved_ version via a new `@artblocks/abx-sdk/package.json` export (alpha version
446
+ counters diverge per package under changesets, so pinning the CLI's own number could produce an
447
+ unsatisfiable range).
448
+ - **The shipped skill (`.claude/skills/abx-self-host/`) is rewritten for the surface phases 0–5
449
+ actually shipped**: every warning made obsolete by an enforcement is deleted rather than softened —
450
+ predict-only deploy-preview addresses, the `approvals N` line, the single `ABX_REMOTE_SELF_*`
451
+ credential grammar, `doctor`'s version/provenance ladder, `storage show --check`, the render/storage
452
+ combo validator's dry-run row, and `ABX_DEPLOYER_PK` as the only key name. Retired names swept from
453
+ `dev-loop-test`, the agent-eval scenarios, and spec prose. The skill ships bundled inside this CLI
454
+ package (co-versioned via `SKILL.md` frontmatter), so it rides this same patch.
455
+ - **New `packages/sdk/README.md`**: what the SDK is, the send-injection model (`PreparedTx` +
456
+ `SendTx`, `makeHotSender` for a hot key, bring-your-own for a wallet/multisig), a complete
457
+ deploy → upload → mint → read walkthrough against real exports, and browser-use notes (explicit
458
+ `rpcUrls`, no env, the `/node` subpath is Node-only). Included in the npm tarball automatically
459
+ (README is one of the files npm always packs, regardless of the `files` allowlist).
460
+
461
+ - 11fa933: Two ripouts, no functional loss: the retired `params.keys` read fallback in `abx state`, and the `abx changelog` command.
462
+
463
+ **`abx state`'s legacy `params.keys` fallback.** On-chain param enumeration (`contractParamKeys` /
464
+ `tokenParamKeys` / `paramSchemaKeys`) shipped a while back and every project deployed since enumerates
465
+ its own param surface — nothing writes the old `params.keys` CSV any more. The only thing still reading
466
+ it was `abx state`, so it could keep describing the handful of pre-enumeration testnet projects that
467
+ predate the switch. That reader (`readLegacyParamsKeys`) and its `legacy`-branching in `cmdStateBody`
468
+ are gone; `abx state` now shows exactly what it reads from on-chain enumeration, an empty PostParams
469
+ list for a pre-enumeration contract included. (The repoint guard that refuses to point a legacy token's
470
+ field renderer at the canonical generator is a separate, still-live safeguard — untouched.)
471
+
472
+ **`abx changelog`.** A `CHANGELOG.md`-reading command that duplicated what any agent or human can do by
473
+ reading the file that already ships with the package (`package.json` `files`) — one more command to
474
+ document and maintain for a read `cat` already covers. `CHANGELOG.md` keeps shipping; the update-nag
475
+ banner and the docs now point at the file and at npm's version list
476
+ (`https://www.npmjs.com/package/@artblocks/abx-cli?activeTab=versions`) instead of a command.
477
+
478
+ - 11fa933: The extraction phase of the simplification refactor: business logic that lived only inside the CLI
479
+ is now importable — the CLI calls the same functions you can.
480
+
481
+ **Into the SDK:** the five trust-anchor bootstraps (`ensureFactory`, `ensureSeriesFactory`,
482
+ `ensureSeriesCodeFactory`, `ensureRenderer`, `ensureSeedSource` — `anchors.ts`, on the same
483
+ injected-`send` + `onEvent` pattern as `ensureChunkStore`, with a typed `AnchorUnavailableError`),
484
+ `detectCanonicalFactory`/`resolveScanFloor`, the on-chain-URI setup composer (`onchain-uri.ts`),
485
+ interrupted-deploy resume planning (`resume.ts`), migration plan/parity reconciliation
486
+ (`migrate.ts`), the `abx.js` static analyzer (`inspect.ts`), dependency setup legs (`deps.ts`),
487
+ content-staging plans (`staging.ts` — `planStagedContent`, `stageFieldContent` with `StagingEvent`),
488
+ `mintedTokenIds`, and a typed `readSaleConfig` for the fixed-price minter.
489
+
490
+ **Into storage:** `uploadAndLocate`, `repinNodeCustody` (the byte-custody half of migration),
491
+ `decideImageContentLane`, `assessStorageReadiness`/`assessTurboFunds` (the Arweave/Turbo funding
492
+ math), and `awaitLocatorReady` (poll a locator until it serves).
493
+
494
+ **CLI hardening that fell out of the dedup:** one risk gate (`gatedSend`) now guards every write —
495
+ `--dry-run` and `--confirm` mean the same thing on every command, all owner-ops gain `--confirm`,
496
+ and a write reaching the send lane under `--dry-run` is structurally impossible (grep-enforced by
497
+ test). One `CHAIN` source of truth; one memoized local-indexer accessor.
498
+
499
+ - 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.
500
+
501
+ **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.
502
+
503
+ **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.
504
+
505
+ **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.
506
+
507
+ **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.
508
+
509
+ - Updated dependencies [11fa933]
510
+ - Updated dependencies [11fa933]
511
+ - Updated dependencies [11fa933]
512
+ - Updated dependencies [11fa933]
513
+ - @artblocks/abx-sdk@0.1.0-alpha.10
514
+ - @artblocks/abx-storage@0.1.0-alpha.10
515
+ - @artblocks/abx-token-api@0.1.0-alpha.13
516
+ - @artblocks/abx-indexer@0.1.0-alpha.11
517
+
518
+ ## 0.1.0-alpha.17
519
+
520
+ ### Patch Changes
521
+
522
+ - df298d8: `abx attach` takes several `<key> <uri>` pairs and sends ONE transaction
523
+
524
+ The safety half of backlog B20. The CLI's most-documented flow — mint, then attach each artifact, then
525
+ refresh — sent one transaction per step with **no all-or-nothing boundary**, so a failure partway
526
+ through left a permanently half-written token, and a mint cannot be undone. An integrator hit exactly
527
+ that and folded 8 operations into 1 transaction (846,556 gas) using the SDK's `batchOps` — a primitive
528
+ the CLI already shipped and did not call. Now it calls it.
529
+
530
+ ```bash
531
+ abx attach 0x… stems ipfs://…/stems.wav score ipfs://…/score.pdf readme ar://…
532
+ # → 3 artifacts, one multicall: a revert lands NONE of them
533
+ ```
534
+
535
+ Three things make the batch safe rather than merely shorter:
536
+
537
+ - **Every pair is validated before anything is sent.** A bad locator in pair 5 stops pair 1 — otherwise
538
+ batching would defeat its own purpose.
539
+ - **A key repeated inside one batch is refused.** A field holds one active value, so the later write
540
+ would silently win — the same full-column-upsert hazard that was a real bug in the resolver's
541
+ `register` and a real trap in `lock-field`.
542
+ - **A single pair is unchanged.** `batchOps` passes a lone op through untouched, so the one-artifact
543
+ case sends the identical plain field transaction it always did — no multicall wrapper, no new gas.
544
+
545
+ An odd number of positionals is refused and names the dangling argument, rather than silently ignoring
546
+ it.
547
+
548
+ Still open in B20, and recorded there rather than quietly skipped: batching **`set-field` across several
549
+ fields** needs a flag-grammar decision first (`attach` batched cleanly because its arguments are
550
+ positional pairs; `set-field` takes one `--field` with a correlated `--text`/`--value`, and the parser
551
+ has no notion of correlated repeats). Same for an all-or-nothing "mint and configure" — mint and
552
+ configure are separate commands, so that needs a verb that owns both.
553
+
554
+ - df298d8: `abx deploy-code --resume <address>` — finish a deploy whose setup transaction failed
555
+
556
+ Closes backlog B15. A code project deploys in **two** transactions: create the clone, then one atomic
557
+ `multicall` carrying the script chunks, the PostParam schemas, the dependency declarations, the
558
+ on-chain-URI legs, and any reserve mints. There is no rollback. When the second one fails you own a
559
+ live-but-unusable contract — and the CREATE2 salt reserved for its address is **spent**, so the dry
560
+ run's pinned-salt reproduce command can never be run again. One reporter session produced three
561
+ orphaned contracts from three attempts; another produced five.
562
+
563
+ The useful half of that report is that those contracts were **recoverable, not lost**: resending the
564
+ setup with an adequate gas limit completed one, after which `abx verify` reported chain-complete and the
565
+ token returned its on-chain `animation_url`. The tester did it by hand with `cast`. We deliberately
566
+ never documented that as a recipe — telling a creator to hand-assemble a multicall is worse than telling
567
+ them nothing — so this is the verb.
568
+
569
+ ```bash
570
+ abx deploy-code --resume 0x… <the same content flags the original deploy used> --dry-run
571
+ ```
572
+
573
+ It deploys nothing. It reads what the contract already holds and sends only the missing legs, in one
574
+ transaction — safe to run twice, and if nothing is missing it sends nothing and says so. Three judgments
575
+ carry it:
576
+
577
+ - **Chunks compare by content, not by count.** A count check would call a chunk "present" when a hand
578
+ repair wrote different bytes at that index — and a hand repair with `cast` is exactly what happened.
579
+ - **A schema that already exists is left alone.** Re-writing one is an upsert that can strand values
580
+ already stored under it; that hazard belongs to `set-schema` and its guard, never to a repair.
581
+ - **Mints are a shortfall against current supply, never a re-send.** `mint` is the one non-idempotent
582
+ leg, and a token cannot be un-minted.
583
+
584
+ `--salt`, `--721c`, `--bootstrap-factory` and `--mint-all` are refused rather than ignored: they all
585
+ describe how a contract is _created_, and this creates nothing. ERC-721C especially — enrollment is
586
+ deploy-time-only and permanent, so silently accepting the flag would imply it can be added later.
587
+
588
+ It shares the deploy's own setup-leg builder, so a resume can never drift from what a fresh deploy would
589
+ have written — a second implementation of that sequence is the failure mode a repair verb most easily
590
+ introduces. Signing goes through the same choke point as every other write, so `--sign` / `--unsigned` /
591
+ the owner check / the chain-id guard all apply, and `--dry-run` previews the repair (one bug found and
592
+ fixed while building this: the deploy's dry-run block returned first, so `--resume … --dry-run` printed a
593
+ fresh-deploy plan with a newly-reserved salt and a different predicted address).
594
+
595
+ 15 tests, including every diff branch and the four refusals.
596
+
597
+ - df298d8: `--json` on every value-emitting command: `mint`, `deploy`, `deploy-series`, `deploy-code`, `state`, `verify`
598
+
599
+ Closes backlog B19. The rule it enforces, from an integrator who drove the CLI from a server: **a value
600
+ a program needs must be obtainable without parsing prose.** They had to regex-scrape ANSI-coloured
601
+ stdout for every value — and an escape code was captured into a locator, written into a _stored_ player
602
+ URL, and 404'd in production. The cause was found only by inspecting stored bytes.
603
+
604
+ Under `--json`, **stdout carries exactly one JSON document and nothing else.** Every narration line the
605
+ command would print for a human moves to **stderr** — diverted, not suppressed, because a human
606
+ watching a deploy still wants to see it while a program redirecting stdout still gets a clean parse.
607
+ (The update check already wrote to stderr for exactly this reason; this carries the instinct through to
608
+ the values themselves.)
609
+
610
+ - **`mint --json`** → `{tokenIds, txHash, blockNumber, sent, …}`. The token ids come from the mint's own
611
+ `Transfer(from=0x0)` logs, not from re-reading `nextTokenId` afterwards — a concurrent mint would make
612
+ that answer wrong, and a number that is usually right is worse than no number. A `--count N` batch
613
+ reports all N.
614
+ - **`deploy` / `deploy-series` / `deploy-code --json`** → the address, emitted _the moment it is known_
615
+ rather than at the end, so a failure in the indexing steps that follow still leaves the caller with
616
+ the address of a contract that really exists. That matters most for `deploy-code`, which is two
617
+ transactions: if the setup tx fails, the address of the live-but-incomplete contract is exactly what a
618
+ recovery needs (backlog B15). With `--dry-run` it reports the **predicted** address plus
619
+ `saltPinned` — false meaning a plain re-run reserves a fresh salt and lands elsewhere, so a caller
620
+ must not treat it as reserved.
621
+ - **`state --json`** → the on-chain snapshot as data, with canonical **names** for param types and auth
622
+ legs rather than raw Solidity enum indices (a caller must not have to know the enum ordering), and
623
+ `undefined` (getter absent) kept distinct from a zero address (present, deliberately unset).
624
+ - **`verify --json`** → the findings, with `ok` matching the exit code. This is the one command a CI job
625
+ would gate on, since it already exits non-zero on a byte mismatch. The tri-states stay tri-states:
626
+ `canonical` and each check's `verified` are `true | false | null`, because collapsing "couldn't check"
627
+ into "failed" would report a normal state as a failure.
628
+
629
+ The mechanism is a single shared helper (`withJson`) rather than a `quiet` flag threaded through every
630
+ command body — deliberately, since the alternative is touching dozens of call sites where the one that
631
+ gets missed is a stray line that corrupts a parse, i.e. the exact failure being fixed. A command that
632
+ emits no payload leaves stdout **empty** and exits non-zero rather than printing a `{}` a caller would
633
+ trust. 8 tests cover the channel contract itself, including that the swap is restored when a body
634
+ throws.
635
+
636
+ - df298d8: `abx storage status <locator>` — is it retrievable yet, or only accepted?
637
+
638
+ Backlog B21, and the second half of a gap two independent integrations hit eight days apart. An upload
639
+ service answers "accepted" the moment it holds your bytes; a gateway serves them only once they
640
+ propagate, and on Arweave that runs to minutes. Nothing in the upload result distinguished the two, so
641
+ the natural implementation — upload during a mint, write the locator into the token — mints a token
642
+ that renders broken for the first minutes of its life.
643
+
644
+ The first reporter rebuilt this layer themselves (ranged GETs, a propagating/ready model, retry ladders
645
+ lengthened after measuring real times) and concluded "every serious integrator will rebuild some
646
+ version of this." The second published 32 renders and found **32/32 404ing on `arweave.net` while 22/32
647
+ already served from `permagate.io` and `vilenarios.com`**, with the uploader reporting `CONFIRMED`
648
+ throughout — and the expensive part is what a creator does next, since a placeholder on a fresh drop
649
+ reads as a failed render, so you re-run `abx render --force` and re-upload everything for nothing.
650
+
651
+ That second observation shapes the design: propagation is **per-gateway**, so the check probes the
652
+ gateway your project actually uses _plus two others_, which buys a third verdict the reporters' own
653
+ two-state model couldn't express.
654
+
655
+ - **`ready`** — your gateway serves the bytes. Safe to reference.
656
+ - **`propagating`** — another gateway serves them, so the data **provably exists** on the network and
657
+ yours is merely behind. Waiting is the fix, and the command says plainly not to re-upload.
658
+ - **`unreachable`** — nothing probed serves them. Deliberately _not_ called propagating: from outside,
659
+ a locator that is still settling and one that is simply wrong look identical, and reporting the
660
+ friendlier of the two is how a tool teaches someone to ignore it. When every gateway rejects the id
661
+ itself (a 4xx that isn't 404) rather than just missing it, that _is_ evidence, and the output says
662
+ "malformed locator, waiting will not fix it" — the inverse mistake of waiting out a typo costs more
663
+ than a needless re-upload.
664
+
665
+ Accepts every form a locator arrives in (`ar://`, `ipfs://`, a gateway URL, a bare txid/CID, with a
666
+ directory path suffix), reads **headers only** via a ranged request with the body cancelled — so
667
+ checking a 40 MB asset doesn't download it — and **exits non-zero unless ready**, which makes waiting a
668
+ one-liner instead of a retry ladder: `until abx storage status <loc> --json; do sleep 10; done`.
669
+
670
+ The primitive is `locatorStatus()` in `@artblocks/abx-storage`, not CLI-only (per B20): anything
671
+ programmatic should call it in-process rather than spawning the CLI per check. `abx render`'s existing
672
+ propagation note now points at the command, so the advisory has an answer attached.
673
+
674
+ - df298d8: `abx tokens <address>` — every token's owner, seed, and params, from chain alone
675
+
676
+ The most obvious post-deploy question about a generative collection — _what did the seeds actually
677
+ deal?_ — had no command. `abx verify` reports per-token minted/render status but no seed; `abx
678
+ tokenuri` prints one token and the seed lives inside its base64 `animation_url`; `abx inspect` is
679
+ pre-deploy and static. An agent's workaround was to start `abx serve`, `GET /api/project/<addr>`,
680
+ base64-decode each `tokenURI`, base64-decode the `animation_url` inside it, then regex
681
+ `0x[0-9a-f]{64}` out of the resulting HTML — 32 times. Every input to that was already a plain
682
+ contract read.
683
+
684
+ `abx tokens <address> [--json] [--from <id>] [--limit <n>]` is chain-only: no indexer projection, no
685
+ running resolver, no event scan. The params store maintains its own key lists inside its write paths
686
+ (`tokenParamKeys` / `contractParamKeys`) and `seed` is a reserved param read by name, so the whole
687
+ listing is `eth_call`s — available to anyone with an RPC URL, including before any indexing has
688
+ happened. Seeds print in full (truncating the one value the command exists for would repeat the
689
+ `tokenuri` bug); `--json` emits `{tokenId, owner, seed, params}` per token, contract-scope params
690
+ once on the parent rather than copied into every row.
691
+
692
+ It reports honestly across all three token types rather than failing: a 1/1 has no params extension
693
+ and says so (owners still list), a pre-enumeration project still yields seeds and flags that params
694
+ can't be enumerated, and an id whose `ownerOf` reverts is `null` — not "not minted", because a
695
+ burned id and an unminted id revert identically and a chain-only read can't tell them apart.
696
+
697
+ Two things it deliberately is not. It is not a trait spread: a trait comes from running the script
698
+ against the seed, which is `abx render`'s job — the token-scope _param_ spread it does print is
699
+ labelled as such. And the reader is in the **SDK** (`listTokens`), not just the CLI, so a
700
+ programmatic integrator gets it without reimplementing it.
701
+
702
+ Backlog B25 (from the 2026-08-04 tester batch, feedback `dcfdc6f4`).
703
+
704
+ - df298d8: `abx tokenuri --fetch` — follow the URL the contract commits to and print what is actually served
705
+
706
+ Closes backlog B14. No command printed the served JSON body: `tokenuri` read the chain, `verify`
707
+ re-hashed bytes, `status` reported the indexing lifecycle — each a different slice, none of them the
708
+ document a marketplace actually reads. An agent in a cold sweep fell back to raw `curl` for exactly
709
+ this, which is the tell that a command was missing.
710
+
711
+ ```bash
712
+ abx tokenuri 0x… --fetch # GET the baked URL, print the served body + HTTP status
713
+ abx tokenuri 0x… --fetch --json # {tokenURI, onChain, served: {url, status, contentType, body}}
714
+ ```
715
+
716
+ It also answers the question a _warning_ could not, which is why the other half of B14 stays declined.
717
+ "Nothing tells you the provider you registered with isn't the base baked on-chain" is a real gap, but
718
+ the obvious check — compare the remote's base URL to the on-chain one — false-positives on the common
719
+ custom-domain case (a baked `meta.artist.xyz` fronting `api.provider.xyz`), and a warning that cries
720
+ wolf on the correct setup is worse than none. Fetching what the **baked** base returns makes the
721
+ mismatch self-evident instead: you see the other provider's 404, or another project's document, with no
722
+ guessing and no false positive.
723
+
724
+ Three judgments worth naming:
725
+
726
+ - **A `data:` URI is not a failure.** It IS the document, and a fully-on-chain project's whole point is
727
+ that there is no server to ask — so it reports "nothing to fetch" and exits 0. Treating it as an error
728
+ would punish the strongest configuration the protocol offers.
729
+ - **A dead host and a served error are different answers**, because they route to different fixes ("your
730
+ provider said no" vs "the URL a marketplace will ask is unreachable").
731
+ - **A non-2xx says it is about the SERVICE, never a mistyped path** — the URL came from the chain, so it
732
+ is right by construction, and the readout points at `abx status --remote` for the lifecycle.
733
+
734
+ Under `--json` the body is verbatim and untruncated and a non-2xx exits non-zero, so CI can gate on it;
735
+ the human readout clips at 1200 chars and says how many it clipped. The fetch itself lives in
736
+ `src/served.ts` with an injectable `fetch`, since `main.ts` exports nothing — the same reason
737
+ `scaffold.ts` was extracted.
738
+
739
+ This retired the last two `curl` recommendations in the shipped skill: verifying an attach is now
740
+ `abx tokenuri <addr> --fetch` (the served document carries the `artifacts` manifest), and the skill's
741
+ claim that `tokenuri` "follows" the URL — previously true only of `contracturi` — is now accurate.
742
+
743
+ - Updated dependencies [df298d8]
744
+ - Updated dependencies [df298d8]
745
+ - Updated dependencies [df298d8]
746
+ - @artblocks/abx-sdk@0.1.0-alpha.9
747
+ - @artblocks/abx-token-api@0.1.0-alpha.12
748
+ - @artblocks/abx-storage@0.1.0-alpha.9
749
+ - @artblocks/abx-indexer@0.1.0-alpha.10
750
+
751
+ ## 0.1.0-alpha.16
752
+
753
+ ### Patch Changes
754
+
755
+ - 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.
756
+
757
+ **`abx serve` / `abx preview` on an occupied port crashed with a raw Node stack trace.** `listen()` had
758
+ no `'error'` handler, so `EADDRINUSE` reached Node's default handler and printed a trace through
759
+ `node:net` and our own `dist/` paths. In a CLI where every other error is formatted, that reads as a
760
+ crash inside abx rather than a port conflict, and it leaks internal paths. Both commands now preflight
761
+ the port and name the port and the fix in one line — `demo` already did this, and the check it used is
762
+ now shared. (`preview`'s default 8788 colliding with a studio left running in another terminal was
763
+ reported as the harder-to-diagnose half of this.)
764
+
765
+ **`abx verify` printed the identical thumbnail advisory once per token.** On a 32-token project with no
766
+ local renders that was 32 consecutive copies of the same full sentence — ~4KB of text for one fact —
767
+ which pushed the four lines that answer "did my deploy work" off a default terminal. It now prints one
768
+ line per outcome with a count and the affected token ids (truncated past 12), so the same information
769
+ costs three lines at 32 tokens and three lines at 1000.
770
+
771
+ **`abx tokenuri <address> 0` silently ignored the `0`** (the token id is `--token`) and printed token 0 —
772
+ a _coincidentally correct_ answer, which is the dangerous kind: `… <address> 7` would have printed token 0
773
+ just as confidently and exited 0. A stray positional is now refused, and a numeric one names the
774
+ corrected invocation. The `positionalArgs` helper moved next to `parseFlags`, because the two must
775
+ consume argv by the same rule — the obvious hand-rolled version of this check reads `--token 0` as a
776
+ stray `0`.
777
+
778
+ **The update banner pointed at `github.com/ArtBlocks/abx/releases`, which 404s** for anyone outside the
779
+ org, and no changelog shipped in the package — so "what changed?" was unanswerable. A tester
780
+ reconstructed the diff by running the same dry run on two versions, which is how they discovered the
781
+ canonical singletons had moved and then had no way to tell whether it needed them to act.
782
+ `CHANGELOG.md` now ships with the package and **`abx changelog`** prints it (offline, version-matched;
783
+ `--all` for the full history), with npm's version list as the online pointer. The upgrading guide now
784
+ also states the thing they had to test for themselves: a singleton redeploy repoints the manifest for
785
+ _new_ deploys and leaves already-deployed contracts unaffected.
786
+
787
+ **Plus one propagation note.** `arweave.net` — the default gateway, and the one baked into the locator —
788
+ indexes new uploads on a delay, so a freshly published render can 404 there for minutes while Turbo has
789
+ already confirmed it. A tester saw 32/32 404ing on arweave.net while 22/32 already served from other
790
+ ar.io gateways. Unexplained, that reads as a failed render, and the natural next move is
791
+ `abx render --force` on everything — a full re-upload that fixes nothing. `abx render` now says so after
792
+ an Arweave publish, and names `ABX_ARWEAVE_GATEWAY` for baking a different gateway (it is fixed at
793
+ publish time, since the locator is what the resolver registers). The note stays quiet when the operator
794
+ has already chosen a non-default gateway.
795
+
796
+ _(From the 2026-08-04 tester batch: feedback `eb972036`, `57bee789`, `dcfdc6f4`, `5a052398`, `e0f30e22`.)_
797
+
798
+ - 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.
799
+
800
+ Both are the same defect wearing different clothes — the CLI made statements that were individually
801
+ true and collectively a promise it wasn't keeping.
802
+
803
+ **`lock-field` reported "permanent" on a PostParam key and left the param writable.** A tester welded
804
+ `grid` — a `Bytes` param holding the artwork — with `abx lock-field <addr> --field grid`. It printed
805
+ `Lock token #0 field "grid" — permanent`, `tokenFieldLocked(0,"grid")` returned `true`, and the very next
806
+ `configure-param grid <junk>` **succeeded** and overwrote the artwork. Nothing lied: fields and params
807
+ are separate namespaces that may share a name, and `lock-field` had locked the _field_. But the creator
808
+ was told their work was permanently protected when it had no protection at all, and permanence is the
809
+ pitch. `lock-field` now reads `paramSchema(<name>)` first and **refuses** a declared param key, naming
810
+ the mechanism that does weld a param (`abx set-schema … --schema <key>:<Type>:<Auth>:lock=now`), with
811
+ `--force-field` for the rare case where you really do mean the metadata field.
812
+
813
+ **A `Bytes` parameter stored whatever characters you typed, as UTF-8.** Passing base64 — reasonable,
814
+ since the params docs say a `Bytes` value "becomes base64" — stored 172 bytes of base64 _ASCII_ where
815
+ 128 packed bytes were meant; `0x`-prefixed hex was stored as its 258 literal characters too. Nothing
816
+ errored, and an in-chain renderer reading the param drew garbage from ASCII with no failure anywhere in
817
+ the chain. A `Bytes` value must now say what its bytes are: `0x`-prefixed hex, or `--file <path>`. A bare
818
+ string is **refused** rather than guessed at, because there is no safe guess between "these characters"
819
+ and "these bytes", and the wrong guess is invisible until an artwork renders wrong. (`String` is
820
+ unchanged — there, the characters _are_ the value.)
821
+
822
+ The docs' "becomes base64" describes the **read** side — how a program receives the value — and now says
823
+ so, next to the two write forms.
824
+
825
+ Also fixed: the byte count that was already the tell. The write echoed the _input string's_ length, so
826
+ the mismatch was visible at write time and printed as if it were fine (`Configure grid (data, 172
827
+ bytes)`). It now echoes the decoded byte count and where the bytes came from.
828
+
829
+ _(From the 2026-08-04 tester batch: feedback `e86081a1`, `b0e88b17`.)_
830
+
831
+ - 8f3c63c: The renderer scaffold's `IAbxParams` can now read `Bytes` and `String` parameters — the two types that can carry an actual payload.
832
+
833
+ The scaffold's interface declared only `tokenParam` / `contractParam`, both returning
834
+ `(bytes32 value, bool valueIsHash, bool isSet)`. So a renderer written against the documented interface
835
+ could not reach a `Bytes` or `String` value at all: it got a keccak commitment and no way to the blob.
836
+ The token has always exposed `tokenParamData(uint256,bytes32)` and `contractParamData(bytes32)` — a
837
+ tester found them by grepping the CLI's bundled ABI, declared them by hand, and it worked.
838
+
839
+ Since `Bytes` and `String` are precisely the types that can hold a real payload (~24KB per key), leaving
840
+ them out made the in-chain art lane look limited to scalars unless you went digging. Both readers are now
841
+ in `src/interfaces/IAbxParams.sol`, with the rule stated where the mistake happens: scalars come from the
842
+ `bytes32` reader, payloads from the data reader, and a `Bytes` param read through `tokenParam` hands you a
843
+ hash — which renders garbage without failing anywhere.
844
+
845
+ The scaffold's own tests carry the pattern rather than just describing it: `MockParams` now models a
846
+ payload param the way the real contract does (the scalar slot holds `keccak256(content)` with
847
+ `valueIsHash` true), and two tests show the read plus the commitment check, and that an unset payload key
848
+ returns empty bytes rather than reverting. The renderers page documents the pair alongside the scalar one.
849
+
850
+ _(From the 2026-08-04 tester batch: feedback `d2ac4f2e`.)_
851
+
852
+ - 8f3c63c: `abx scaffold-renderer` wrote **zero files** for every installed user while reporting success — fixed, and pinned by the test that was missing.
853
+
854
+ The command created the target directory, printed the full green-check walkthrough (`cd renderer`,
855
+ `forge soldeer install`, `forge test`, the deploy script, "full walkthrough: renderer/README.md") and
856
+ **exited 0**. The directory was empty. No `src/`, no `foundry.toml`, no `README.md`. Three testers
857
+ reproduced it independently — 3 of 3 and 4 of 4 attempts, across all three invocation forms (relative
858
+ name, absolute path, and the no-arg default) — on alpha.9, alpha.12 and alpha.14.
859
+
860
+ **The cause was a path filter that judged absolute paths.** The copy excluded build dirs with
861
+
862
+ ```js
863
+ filter: (s) =>
864
+ !/(^|\/)(out|cache|dependencies|broadcast|node_modules)(\/|$)/.test(s);
865
+ ```
866
+
867
+ `cpSync` hands `filter` **absolute** source paths, and an installed CLI lives at
868
+ `…/node_modules/@artblocks/abx-cli/assets/renderer-scaffold`. So the pattern matched the source
869
+ **root**; `cpSync` skips a directory's entire subtree when the directory itself is filtered out, and
870
+ does it _silently_ rather than erroring. A dev checkout's path
871
+ (`…/packages/cli/assets/renderer-scaffold`) contains no `node_modules`, so every test we ran passed and
872
+ every user got an empty directory. The scaffold assets were always in the published package — this was
873
+ never missing content, only a copy step that no-oped.
874
+
875
+ Fixed on both axes, because either one alone would have let this ship:
876
+
877
+ - **The filter judges paths relative to the scaffold root**, so `node_modules` in the _install_ path is
878
+ irrelevant while `out/`, `cache/`, `dependencies/`, `broadcast/` inside the scaffold are still skipped.
879
+ - **The command asserts its own output.** It now throws if `src/MyRenderer.sol` isn't there afterwards,
880
+ instead of printing a success banner over nothing. Exit 0 plus a green check is what made this
881
+ expensive: an agent has no reason to look back at a step that reported success, so the failure
882
+ surfaced far away — `forge soldeer install` dying in an empty directory.
883
+
884
+ The copy logic moved to `src/scaffold.ts` so it can be tested at all (importing `main.ts` runs the CLI),
885
+ and `test/scaffold.test.ts` copies **from a path containing `node_modules`** — the layout every user has
886
+ and the one case no previous test covered — plus an end-to-end run asserting the scaffolded project is
887
+ non-empty and contains the files the success message names.
888
+
889
+ This was the documented entry point to the in-chain Solidity lane and the only documented route to
890
+ `--image-renderer` / `--attributes-renderer`, so it blocked that lane outright. The full walkthrough
891
+ (`forge soldeer install` → `forge test`) now runs clean: 10 tests pass in a freshly scaffolded project.
892
+
893
+ _(From the 2026-08-04 tester batch: feedback `6f2f980a`, `b45bc0f8`, `d2ac4f2e`.)_
894
+
895
+ - 8f3c63c: The agent skill now names the both-worlds code lane, the payload-param reader, and the verb that welds a parameter.
896
+
897
+ Four corrections, all from the same batch that produced the CLI fixes — the skill described the lanes in a
898
+ way that made the strongest option invisible:
899
+
900
+ - **`--script` plus `--image-renderer` is now a lane in its own right.** The table and the in-chain
901
+ section both said "no `--script`", which is a true constraint of the _renderer-only_ shape and read as a
902
+ prohibition on combining them. A program with Solidity renderers gets `animation_url` from its on-chain
903
+ chunks _and_ an on-chain `image`/`attributes` — every marketplace surface in-chain with nothing to
904
+ render, host, or refresh. That is the best available answer for an interactive generative drop and it
905
+ was reachable only by ignoring the skill.
906
+ - **Renderers are told which reader a `Bytes`/`String` param needs.** The guidance said to read params via
907
+ `tokenParam`/`contractParam`, which for the two payload types returns a keccak commitment — so a
908
+ renderer written from the skill drew garbage from a hash with nothing failing. `tokenParamData` /
909
+ `contractParamData` are now named where the mistake happens.
910
+ - **`configure-param`'s payload encoding is explicit**: `String` takes text, `Bytes` takes `0x` hex or
911
+ `--file`, and a bare string on a `Bytes` key is refused.
912
+ - **Locking a param points at `set-schema … :lock=now`**, since `lock-field` freezes the same-named
913
+ _field_ and now refuses a declared param key.
914
+
915
+ _(From the 2026-08-04 tester batch: feedback `c283d767`, `d2ac4f2e`, `b0e88b17`, `e86081a1`.)_
916
+
917
+ - 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.
918
+
919
+ The `[5] Surfaces` block is the most-praised thing in the deploy preflight — a tester singled out its
920
+ "not backfillable" framing as the direct fix for a confusion they'd filed a version earlier. It also
921
+ told two lies in adjacent lines, both found while answering their follow-up question.
922
+
923
+ **"One or more surfaces resolve to NOTHING a marketplace can see" fired when none did.** An undeclared
924
+ param was folded into the broken-surface test, so a deploy with both renderers set printed
925
+ `thumbnail: ON-CHAIN ✓` and `traits: on-chain ✓` and then, two lines later, that a surface resolved to
926
+ nothing — re-recommending `--image-renderer` and `--attributes-renderer`, the exact flags already
927
+ passed. A dropped param is not a dead surface: the piece renders, that input takes its default. The
928
+ alarm now covers only the thumbnail and traits, names only the remedies for what is actually broken, and
929
+ says _which_ surface. Dropped params keep their own, milder line.
930
+
931
+ **An on-chain image renderer was told to stand up a render runner.** With `--image-renderer` _and_ a
932
+ script, the block recommended `abx deploy-effects`, a one-shot `abx render`, and a publish-capable
933
+ storage backend — none of which apply, because an in-chain SVG has no off-chain still to render, host,
934
+ or refresh. It now says `nothing to render` for any on-chain image lane, with the from-chain check.
935
+
936
+ **And the question the tester explicitly flagged as untested — does passing a script _and_ an
937
+ `--image-renderer` yield both a live `animation_url` and an on-chain thumbnail? — is yes.** Verified on a
938
+ dry run: `animation_url` still assembles on-chain from the script chunks while `image` and `attributes`
939
+ are computed by the Solidity renderers, so every marketplace surface has an on-chain home in one deploy
940
+ with nothing to run. The deploy guide now leads the content-lane section with that combination instead of
941
+ implying the two lanes are an either-or.
942
+
943
+ The guide also gains the correction to the _other_ half of that question. A resolver plus
944
+ `abx render --backend arweave` does **not** backfill a `--onchain-uri` drop's marketplace surfaces: it
945
+ sends no transaction, so `tokenURI` keeps returning the on-chain JSON with its placeholder `image` and
946
+ no `attributes`, which is what marketplaces read. Checked against the reporter's own live 32-token
947
+ contract — `tokenURIBase` is empty, the metadata renderer is authoritative, and `image` provenance still
948
+ reads `fallback — image field unset`. Publishing stills to a resolver is real and useful, and closing
949
+ those surfaces afterwards takes owner transactions that re-point resolution. The guide now says so
950
+ plainly, since "just render to a resolver later" would have been a new false promise in place of the
951
+ old one.
952
+
953
+ Three tests pin the block: an undeclared param never claims a dead surface, an on-chain image renderer
954
+ is never sent to a runner, and a script with no renderer and no resolver still reports both surfaces
955
+ dead.
956
+
957
+ _(From the 2026-08-04 tester batch: feedback `c283d767`, `991dc8e2`, `7936334b`, `a319e53f`.)_
958
+
959
+ ## 0.1.0-alpha.15
960
+
961
+ ### Minor Changes
962
+
963
+ - e325b46: Opt-in ERC-721C support across the toolkit — plain ERC-721 stays the transparent default.
964
+
965
+ SDK: `OneOfOneInitParams`/`SeriesInitParams` (and `SeriesCodeInitParams` by inheritance) gain
966
+ `transferValidator` immediately after `royaltyBps` — `zeroAddress` = plain ERC-721 forever (the
967
+ default), non-zero = permanent 721C enrollment with that validator. A new `creator-token` module
968
+ ships the per-chain `RECOMMENDED_TRANSFER_VALIDATOR` (OpenSea's
969
+ StrictAuthorizedTransferSecurityRegistry, verified live on Sepolia + Base Sepolia), the two
970
+ creator-token ERC-165 ids + the ABX extension id, `readCreatorTokenStatus()` (`{enrolled,
971
+ validator}`), and a `prepareSetTransferValidator()` write wrapper.
972
+
973
+ CLI: the deploy commands take `--721c [recommended|0x…]` — absent means zero behavior/output
974
+ change; `recommended` resolves the per-chain constant (refused, naming the chains that have one,
975
+ where none is known); an explicit address is EIP-55-validated and pre-checked for code before any
976
+ gas. Enrolling prints one plain statement of what enforcement means. A new owner op
977
+ `abx set-transfer-validator <address> <0x…|none|recommended>` re-points or suspends an ENROLLED
978
+ collection's validator (guards `--dry-run`; refuses plain ERC-721s up front — enrollment is a
979
+ deploy-time decision). `abx state` shows the validator for enrolled collections only.
980
+
981
+ ### Patch Changes
982
+
983
+ - Updated dependencies [e325b46]
984
+ - @artblocks/abx-sdk@0.1.0-alpha.8
985
+ - @artblocks/abx-indexer@0.1.0-alpha.9
986
+ - @artblocks/abx-storage@0.1.0-alpha.8
987
+ - @artblocks/abx-token-api@0.1.0-alpha.11
988
+
989
+ ## 0.1.0-alpha.14
990
+
991
+ ### Minor Changes
992
+
993
+ - 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.
994
+
995
+ The params store was unenumerable by design, so anything that wanted to know _which_ parameters a
996
+ project has had to be told. The answer was a convention: a `params.keys` contract param holding a
997
+ comma-separated list, composed by `deploy-code` from the `--schema` flags and hand-maintained
998
+ thereafter. It worked, and it had the defect every hand-maintained index has — a key configured but
999
+ not listed was **silently omitted from every render**. The schema existed, a collector could set it,
1000
+ the art never saw the value, and nothing anywhere reported a problem. The CLI grew a nudge, then a
1001
+ same-transaction companion write, and both were treatments for a design that should not have needed
1002
+ them.
1003
+
1004
+ **The token now maintains its own key lists.** `SeriesCode` gains `tokenParamKeys(tokenId)`,
1005
+ `contractParamKeys()`, and `paramSchemaKeys()` (plus `…Paged` variants for surfaces past an RPC's
1006
+ return cap). The lists are updated inside the write paths themselves, so `key ∈ list ⟺ the param is
1007
+ set` holds for every writer — raw owner writes, governed `configure-param` writes, hook-driven writes,
1008
+ all of them. No caller can forget, because no caller is involved. (`seed` is deliberately never listed:
1009
+ every consumer reads it as a tokenData coordinate, and indexing it would charge every seeded mint for
1010
+ nothing.) `paramSchemaKeys()` closes the other half — a chain-only frontend can now build a configure
1011
+ UI, including for keys declared but never yet written, which nothing off-chain could previously
1012
+ discover.
1013
+
1014
+ **The canonical `AbxGenerator` reads that surface instead of the CSV.** It no longer looks at
1015
+ `params.keys` at all, and a `params.keys` value set on a new project is simply an ordinary parameter —
1016
+ enumerated and emitted like any other, by both the generator and the SDK. A latent parity bug dies with
1017
+ the convention: the generator's no-CSV path emitted tokenData in insertion order while the canonical
1018
+ serializer sorts, so the two byte-forms could disagree; there is now one form.
1019
+
1020
+ **`AbxMetadataRenderer` is spec v4.** `tokenURI` gains a computed `abx_params` object — every set
1021
+ parameter, contract and token scope merged, token wins, sorted, decoded by the same rules the generator
1022
+ and the SDK use. A data-backed value over 2048 bytes is emitted as `{"keccak256":"0x…"}` — its on-chain
1023
+ commitment — rather than inline, so a large `Bytes` parameter cannot bloat `tokenURI` past a single
1024
+ `eth_call`; every key still appears, and an oversized one degrades self-describingly. Parameters are
1025
+ _not_ folded into `attributes`: that stays the creator's surface. Also in v4: an `animation_url` carried
1026
+ by the `inline` or `reader` representation is now wrapped as `data:text/html;base64,…`, exactly as
1027
+ `image` already was — the asymmetry was an oversight, and it meant a fully on-chain animation was
1028
+ handed to wallets as bare text.
1029
+
1030
+ Both changes are additive on the read side: pointed at a token that predates enumeration, a v4 renderer
1031
+ simply emits no params block. **Repointing the metadata renderer is safe anywhere.** Repointing the
1032
+ _generator_ is not, and `abx set-field` now refuses it: a legacy implementation plus the current
1033
+ generator means every parameter silently vanishes behind a `tokenURI` that still looks healthy, so the
1034
+ CLI stops you rather than warning you, and names both ways out (stay on the project's existing
1035
+ generator, or redeploy).
1036
+
1037
+ Everywhere else in the toolkit, the convention is simply gone:
1038
+
1039
+ - `deploy-code --onchain-uri` sets up in **three** legs, not four (animation field → generator, plus
1040
+ the two URI renderers). There is no key list to compose, report, or keep in sync, and the help text
1041
+ no longer teaches one.
1042
+ - `abx set-schema` is a single op again — no companion write, no multicall. This **supersedes**
1043
+ alpha.13, which shipped `set-schema` writing `params.keys` in the same transaction to stop a governed
1044
+ key going unlisted: the contract now maintains its own key list, so there is nothing to keep in step
1045
+ and the drift that fix guarded against is gone rather than mitigated. Same for that release's note
1046
+ that "there is no on-chain enumeration of schema keys" — `paramSchemaKeys()` is exactly that.
1047
+ - `abx state` reads the governed surface from `paramSchemaKeys()` and the collection-scope values from
1048
+ `contractParamKeys()`. A project deployed before enumeration falls back to reading its old
1049
+ `params.keys` list, read-only, so live testnet drops still describe themselves.
1050
+ - `abx verify` notes when a project's enumerated surface exceeds ~64 keys — the documented design
1051
+ envelope. The write side is unbounded; the read side is what grows, since `tokenURI` and `tokenData`
1052
+ assemble every parameter per call.
1053
+ - `abx configure-param <addr> - params.keys <csv>` no longer has a special path. Writing that key is
1054
+ now an ordinary schema-less contract param, and it shows up in tokenData as one — honest, and
1055
+ documented.
1056
+ - `abx_params` joins `artifacts` and `abx_provenance` as a computed key `set-field` and `attach` refuse.
1057
+
1058
+ The SDK needed no semantic change: `buildTokenData` has always been event-derived (coordinates + seed +
1059
+ every set param, both scopes, token wins, augment entries), and the contract enumeration implements
1060
+ exactly that rule. Its `deployments` manifest carries the new addresses.
1061
+
1062
+ ### Patch Changes
1063
+
1064
+ - 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.
1065
+
1066
+ **`abx tokenuri --json`.** The command abbreviated long values (`… (382 chars)`) with no way to turn it
1067
+ off, so for a token whose whole point is on-chain content it returned something that _looked_ like the
1068
+ metadata and wasn't. An integrator scraped it, stored a `data:` URI cut to 96 characters, and only
1069
+ found out in production; they abandoned the CLI as a read path and reimplemented `eth_call`. `--json`
1070
+ now emits the verbatim decoded document — no banner, no ANSI, no truncation — so
1071
+ `abx tokenuri <addr> --json | jq` is a supported read path. The human view still abbreviates, and now
1072
+ says `[--json for the full value]`.
1073
+
1074
+ **One Arweave identity, resolved in one place.** `arweaveConfigFromEnv()` read `ARWEAVE_JWK` and
1075
+ nothing else, while the CLI mints and manages `.abx-self-host/arweave-key.json`. Porting a working CLI
1076
+ flow to the SDK — same machine, minutes later — failed every upload with "Arweave via Turbo needs an
1077
+ identity", a message that says storage was never configured when the truth was that two layers
1078
+ disagreed about where the identity lives. `@artblocks/abx-storage` now exports `resolveArweaveJwk()`
1079
+ (env → managed key file) and the CLI delegates to it. Its diagnostics come with it: an empty key file
1080
+ now reports the **path** and the remedy instead of `Unexpected end of JSON input`, and a corrupt one
1081
+ says the same.
1082
+
1083
+ **`abx refresh` on the default chain.** The OpenSea slug map held only `sepolia` and `mainnet`, so
1084
+ `base-sepolia` — the CLI's own default — fell through to the raw key: the refresh POST went to a slug
1085
+ OpenSea doesn't know, and the printed link pointed at **mainnet** `opensea.io` for a testnet token.
1086
+ Slugs are now correct (`base_sepolia`), `testnet` comes from the chain registry rather than a second
1087
+ hand-maintained set, and a chain with no known slug produces **no link** instead of a wrong one. Same
1088
+ shape as the hardcoded explorer table that once sent every Base Sepolia link to Etherscan.
1089
+
1090
+ **`abx attach` names its dependency.** Attaching artifacts to a project that resolves on-chain now
1091
+ warns, before the send, that they will **not** appear in `tokenURI` — the on-chain renderer carries
1092
+ reserved fields only, and the artifacts manifest comes from a resolver. A team attached five audio
1093
+ stems to a fully-on-chain token and found them "paid for, stored on-chain, and invisible"; the note
1094
+ that existed was one dim line that read as a footnote rather than as a missing service.
1095
+
1096
+ **`ensureChunkStore` moved to the SDK.** The bootstrap every on-chain-content path needs existed only
1097
+ inside the CLI, so an SDK integrator got `resolveChunkStore()` (may return undefined) plus a separate
1098
+ `storeSupportsWriteContent()` they had to remember — forget it and an incapable store fails _deep
1099
+ inside a mint, after transactions have landed_. One team hand-rolled the guard for exactly that reason.
1100
+ `ensureChunkStore(publicClient, send, {chainId, override, onEvent})` is now exported; the SDK reports
1101
+ progress through `onEvent` instead of printing, and the CLI keeps its narration.
1102
+
1103
+ **`abx storage upload --json`.** The locator as data. They scraped this line, captured its ANSI colour
1104
+ codes along with the URL, wrote the result into a _stored_ player URL, and found out when it 404'd in
1105
+ production. In `--json` mode stdout carries the JSON and nothing else; progress moves to stderr.
1106
+
1107
+ **`--backend ipfs` no longer hides a missing credential.** Without `PINATA_JWT` the backend resolves to
1108
+ **kubo against a local node**, so a dry run looked fine and the real upload failed for anyone not running
1109
+ one. The preview now says so. Related correction: the skill claimed "a backend missing its secret falls
1110
+ back to `fs`" — it does not. `cloud` refuses up front naming the missing values, and `ipfs` goes to the
1111
+ local node; nothing silently degrades to local disk. Both sides now say the same thing.
1112
+
1113
+ Reported in the 2026-08-03 MXRR integration batch (feedback 869f27b1, a256217f, 119d7e8e, 975c363e,
1114
+ e38216db, e267078b).
1115
+
1116
+ - 1b50f9d: Unknown flags on a command that can send now **refuse** instead of warning — and `--chain` teaches `ABX_CHAIN`.
1117
+
1118
+ `abx deploy-code … --chain sepolia` ran to completion **on the default chain** (base-sepolia). There is
1119
+ no `--chain` flag — the chain comes from the `ABX_CHAIN` environment variable — and the generic
1120
+ stray-flag warning said so, accurately, and then the command carried on. On a dry run that is a
1121
+ confused minute; on a funded send it is a wrong-chain deploy with real artifacts at an address nobody
1122
+ is watching. Prose that gets ignored once gets ignored again, so:
1123
+
1124
+ - **`--chain` is refused on every command**, read-only ones included (a command that quietly ignored it
1125
+ would still teach the wrong model). The message names the mechanism, the chain that _is_ active, the
1126
+ known chain keys, and the corrected invocation: `ABX_CHAIN=sepolia abx deploy-code …`.
1127
+ - **`deploy`, `deploy-series` and `deploy-code` refuse any unrecognized flag**, naming the offender and
1128
+ pointing at `abx help <command>`. `--dry-run` refuses identically — a preview that accepts what the
1129
+ real send rejects is its own trap, since you would validate a command and have it fail at the one
1130
+ moment it matters.
1131
+ - **Read-only commands still only warn.** Nothing can be mis-sent, and a stray flag shouldn't stop a
1132
+ creator mid-iteration.
1133
+
1134
+ Refusing is safe rather than risky here, and this was verified rather than assumed: a flag absent from a
1135
+ command's allowlist is by construction one that command never reads. Every `flags.x` read inside
1136
+ `cmdDeploy`, `cmdDeploySeries` and `cmdDeployCode` was compared against its allowlist — no gaps — so
1137
+ refusal cannot break a working flag, only make an already-ignored one loud.
1138
+
1139
+ Five tests cover it, including the literal `--chain sepolia` repro, dry-run/real-send parity, and a
1140
+ no-false-refusal pass over the documented deploy flags.
1141
+
1142
+ - 1b50f9d: `abx preview --shoot --param key=value`, and `set-schema --force` says what it overrode.
1143
+
1144
+ **`--shoot` can render the collector-has-set-it state.** It only ever shot the _unset_ one, so
1145
+ answering "what does this collection look like when someone picks a theme?" meant hand-rolling a
1146
+ Playwright script against the preview server's `/view` — which is exactly what one agent did. `--param`
1147
+ is repeatable (`--param theme=Neon --param mood=Calm`) and forwards into every frame. An empty value
1148
+ (`--param theme=`) shoots the unset state explicitly, matching the wire shape production uses for a
1149
+ param nobody has written.
1150
+
1151
+ **`set-schema --force` no longer applies a value-stranding change silently.** It refuses such a change
1152
+ by default; with `--force` it now prints each risk it is overriding and notes that any token already
1153
+ holding a value keeps it, now outside what its schema allows. Applying that quietly was the one outcome
1154
+ worse than refusing — nobody, including the operator, got a record of what may have just been
1155
+ invalidated.
1156
+
1157
+ - Updated dependencies [1b50f9d]
1158
+ - Updated dependencies [1b50f9d]
1159
+ - @artblocks/abx-sdk@0.1.0-alpha.7
1160
+ - @artblocks/abx-storage@0.1.0-alpha.7
1161
+ - @artblocks/abx-indexer@0.1.0-alpha.8
1162
+ - @artblocks/abx-token-api@0.1.0-alpha.10
1163
+
1164
+ ## 0.1.0-alpha.13
1165
+
1166
+ ### Minor Changes
1167
+
1168
+ - 1158420: Expose the PostParam schema lifecycle: `abx set-schema`, `abx retire-param`, Address legs, and `lock=`.
1169
+
1170
+ Three capabilities the contracts have always had, that the toolkit could not reach — so they read to
1171
+ creators as protocol limitations. All three were reported in the 2026-08-03 tester batch.
1172
+
1173
+ **A project's param surface was never frozen at deploy.** `setParamSchema` is owner-gated with no
1174
+ deploy-time restriction and no `exists` check, so it is an upsert usable for the life of a project.
1175
+ There was just no command for it, and the CLI said so out loud ("Adding a param to an already-deployed
1176
+ contract isn't a CLI command yet"), which pushed designers toward guessing their full param surface up
1177
+ front or redeploying — losing the address, the mints, and the collectors. `abx set-schema <addr>
1178
+ --schema key:Type:Auth` attaches or replaces one key.
1179
+
1180
+ Because it is a **full-row upsert on a contract that never re-validates stored values**, the command
1181
+ carries a guard rather than a warning: it prints before/after, and _refuses_ a change that could strand
1182
+ values already written under the key — a narrowed bound, a dropped `Select` option, a changed type —
1183
+ unless you pass `--force`. It also flags an existing `lock=` you are about to drop by not restating it.
1184
+
1185
+ **A parameter can be retired.** There is no delete in the contract (`exists` is only ever set true), but
1186
+ a `lockAfter` in the past makes every later write revert `ParamLockExpired`, permanently. `abx
1187
+ retire-param <addr> <key>` does exactly that, reading the current schema and changing _only_ the lock so
1188
+ type/auth/bounds/options carry forward untouched. It does not remove the key and does not erase a stored
1189
+ value — a value written under a `TokenOwner`/`Address` leg came from a collector, and the artist
1190
+ deliberately cannot delete it.
1191
+
1192
+ **An `Address` auth leg is now expressible.** `--schema` previously rejected every Address-bearing leg
1193
+ with "set that schema post-deploy via the contract" — advice pointing at a command that did not exist.
1194
+ The auth token now names its holder inline (`board:Bytes:Address(0xabc…)`), and the error for a bare
1195
+ `Address` says what the leg is for: a **contract** may hold it, which is how open and multi-party
1196
+ participation is built today. `authAddress` and `lockAfter` were also hardcoded to zero at the
1197
+ deploy-time call site, so neither was reachable there either; both now flow through `--schema`.
1198
+
1199
+ `--schema` gains an optional 4th field, `lock=<when>` (ISO date, unix seconds, or `now`), sharing the
1200
+ Timestamp grammar the bounds already use. A 4th field that is not `lock=` now reports the spec-shape
1201
+ error instead of a mangled "malformed type", which is what a `:` inside a `Select` label used to produce.
1202
+
1203
+ New in the SDK: `prepareSetParamSchema`, `prepareRetireParam`, `readParamSchema`, `OnChainParamSchema`.
1204
+
1205
+ (feedback 4a0c213a, 5d530681, 381bdcbe)
1206
+
1207
+ > [Superseded 2026-08-03: params now enumerate **on-chain** (renderer spec v4, `abx_params`) and
1208
+ >
1209
+ > > `params.keys` is retired — the contract maintains its own key list, so nothing off-chain has to keep
1210
+ > > it in step and `abx state` reads the chain directly. See the on-chain param enumeration entry.]
1211
+
1212
+ `set-schema` also keeps **`params.keys` in step, in the same transaction**. On the on-chain URI lane
1213
+ the canonical generator builds tokenData from that CSV, so a key that is governed but not listed is
1214
+ silently omitted from every render — the schema exists, a collector can set it, and the art never sees
1215
+ the value. `deploy-code` composes the list from `--schema` for exactly this reason; without the
1216
+ companion write, a schema added later would have quietly half-worked. Projects not on that lane (where
1217
+ `params.keys` is unset) get no extra write.
1218
+
1219
+ > [Superseded 2026-08-03: params now enumerate **on-chain** (renderer spec v4, `abx_params`) and
1220
+ >
1221
+ > > `params.keys` is retired — the contract maintains its own key list, so nothing off-chain has to keep
1222
+ > > it in step and `abx state` reads the chain directly. See the on-chain param enumeration entry.]
1223
+
1224
+ And `abx state` now prints the governed PostParam surface — each key's type, auth, bounds/options, an
1225
+ upcoming lock date, and a `retired` marker for one whose lock has passed. There is no on-chain
1226
+ enumeration of schema keys, so it reads the project's own `params.keys` list, which is also what the
1227
+ generator reads; anything missing from it is invisible to renders anyway. It also names keys listed
1228
+ there with no schema. This is what makes `set-schema`'s upsert safe to use: you can see a key's current
1229
+ shape before overwriting it.
1230
+
1231
+ - 1158420: `abx deploy` (1/1) can finally do "image off-chain, JSON on-chain, no server" — the pattern the docs already recommended.
1232
+
1233
+ The decisions table calls this pattern 2 and presents it as the sweet spot for static art. It worked
1234
+ on `deploy-series`. On the 1/1 command it silently did not: `--onchain-uri` inlines the image only
1235
+ when it is an SVG, and a raster fell through to keccak256 custody with no URL wired anywhere, so the
1236
+ on-chain renderer held a hash it could not serve and `tokenURI` returned a **placeholder — forever**.
1237
+ Adding `--backend arweave` changed nothing, which was the cruel part: it looked exactly like the
1238
+ documented recipe. A cold agent asked for "no server, still there in ten years", followed the docs,
1239
+ and would have shipped a permanently broken token.
1240
+
1241
+ `deploy` now takes the same route the Series takes. When `--onchain-uri` meets a non-inlinable image
1242
+ and a backend that can serve a public URL (`arweave` · `ipfs` · `cloud`), the file is uploaded as a
1243
+ one-entry directory — `putDirectory`, the identical call `deploy-series` makes, so both commands
1244
+ produce the same URL shape from one code path — and its URL is baked on-chain as the image field. The
1245
+ bytes are still stored under their content hash as well, so `abx verify` keeps working. A backend that
1246
+ can only serve from this machine (`fs`) still falls through to custody, and still warns.
1247
+
1248
+ The readouts learned that this is a three-way distinction, not a binary, because "no server" and
1249
+ "on-chain" are different promises and a creator buying permanence is choosing between them:
1250
+
1251
+ - image genuinely on-chain (`--onchain-image`, or an inlined SVG) → **"Done — fully on-chain."**
1252
+ - image at a durable URL the on-chain JSON points at → **"Done — metadata on-chain, image on ipfs."**
1253
+ plus a line naming whose permanence it actually is (Arweave paid-once-forever; IPFS while pinned).
1254
+ - neither → the placeholder warning, before the spend, naming both routes that would fix it.
1255
+
1256
+ Verified end to end on Base Sepolia (`0xdE5aCD35b74B6d002781De51217590a9c5B53EDC`): `tokenURI(0)` read
1257
+ straight from chain with `cast` — no `abx`, no server — returns an IPFS gateway URL that serves HTTP
1258
+ 200 with bytes identical to the source file, and reports itself honestly in `abx_provenance` as
1259
+ `source: url · onChain: false`.
1260
+
1261
+ Closes B17. Found by the 2026-08-03 parallel agent sweep.
1262
+
1263
+ ### Patch Changes
1264
+
1265
+ - 1158420: `abx inspect` no longer reports dependencies, params, or runtime problems that aren't in the code.
1266
+
1267
+ Every detector in the analyzer was a regex over the raw file, so prose counted as code. One
1268
+ tokenizer pass now gives the detectors a comments-stripped, string-blanked view:
1269
+
1270
+ - **A comment or string mentioning a library is no longer a dependency.** A dependency-free
1271
+ vanilla-canvas sketch whose header read `// no p5` was reported as `libraries: p5`, and the lane
1272
+ recommendation then said `--dep p5@<version>` — advice an agent adopts verbatim, which bloats the
1273
+ stored on-chain document and can push a drop to a chain whose dependency registry it needs.
1274
+ Real `p5`/`THREE`/`Tone` usage is still detected (pinned by tests).
1275
+ - **Reserved coordinates are never listed as PostParams.** `tokenId`, `chainId`, and
1276
+ `contractAddress` (like `seed` before them) are injected by the runtime and cannot be declared,
1277
+ but they landed in the "declare EACH at deploy or it's silently dropped" warning with
1278
+ `--schema tokenId:<Type>:<Auth>` advice that must not be followed.
1279
+ - **A dotted param key read through an alias is detected.** `const d = abx.tokenData;` then
1280
+ `d['collapse.index']` was invisible, producing a false "dropped at render" warning on a correct
1281
+ program — and ABX's own output-naming convention is dotted (`effect.render.image`), so the
1282
+ documented idiom tripped the analyzer.
1283
+
1284
+ Reported in the 2026-08-03 tester batch (feedback 3b66e443, f69e15d6, 8184f7dd, 6d32f874).
1285
+
1286
+ - 1158420: `abx deploy --onchain-uri` no longer claims "fully on-chain" over a token whose image isn't.
1287
+
1288
+ `--onchain-uri` puts the metadata JSON on-chain. It inlines the _image_ only when the image is an
1289
+ SVG; a raster falls through to keccak256 custody, and the on-chain renderer then serves a
1290
+ **placeholder** image. The success banner printed "Done — fully on-chain … no server or hosting
1291
+ needed" for that configuration, so a creator would believe they had permanence they did not have —
1292
+ and only discover it later via `abx tokenuri` (`source: "fallback"`, `onChain: false`).
1293
+
1294
+ Two changes, both computed from the actual file rather than the flag:
1295
+
1296
+ - The dry run (and the real run) now warn **before the spend** that the image is a keccak256 anchor
1297
+ and `tokenURI` will serve a placeholder, pointing at `--onchain-image --compress fastlz` or a
1298
+ served base URL. The code lane's dry run already warned about this case; the 1/1 lane shipped it
1299
+ silently.
1300
+ - The success banner says "Done — metadata on-chain" and names the image's real status. An SVG (or
1301
+ `--onchain-image`) still gets the unqualified "fully on-chain" banner, because that one is true.
1302
+
1303
+ Reported in the 2026-08-03 tester batch (feedback a8921f18).
1304
+
1305
+ - 1158420: Fix `deploy-code` reverting `DeploymentFailed()` — the setup transaction was sent with a gas limit estimated against a contract that did not exist yet.
1306
+
1307
+ Every `deploy-code` attempt in a reporter's Base Sepolia session reverted with Solady's
1308
+ `DeploymentFailed()` (`0x30116425`), in both the on-chain and hosted-resolver lanes, with a minimal
1309
+ case of storing a single 3,563-byte script chunk. It was not a defect in the chunk path: the two
1310
+ transactions simply **ran out of gas**.
1311
+
1312
+ ```
1313
+ 0xcad74d07… gasLimit 201,616 gasUsed 198,870 (98.6%)
1314
+ 0xf4350724… gasLimit 169,301 gasUsed 166,810 (98.5%)
1315
+ ```
1316
+
1317
+ A code project deploys in two transactions: create the clone, then one setup `multicall`. The second
1318
+ targets the contract the first just created — and `eth_estimateGas` for that call, taken while the
1319
+ answering node has not yet seen the deploy block, returns the **calldata cost alone**. Replaying both
1320
+ payloads against a codeless address reproduces the sent limits _to the gas_ (201,616 and 169,301);
1321
+ against the real contract the same calls need 941,331. A setup multicall's cost is dominated by
1322
+ CREATE code deposit (~200 gas per stored byte), so the underfunded CREATE inside `SSTORE2.write`
1323
+ returned 0 and reverted. The 1/1 lane was unaffected because its setup fits inside a calldata-sized
1324
+ budget.
1325
+
1326
+ This is the same read-after-write lag the deploy loop already pins the **nonce** against, one field
1327
+ over. The fix has two halves, and deliberately does not include a third:
1328
+
1329
+ - **Every leg after the first waits for the target's code to be visible** to the client doing the
1330
+ estimating, so a lagging node cannot produce a meaningless estimate in the first place. This is the
1331
+ actual repair.
1332
+ - **An impossible estimate is detected and refused, not replaced.** `PreparedTx` gained an optional
1333
+ `gasFloor` carrying only the _provable_ part of a payload's cost — EVM code deposit at exactly 200
1334
+ gas per stored byte. An estimate below that is not "low", it is proof the node is on stale state, so
1335
+ the sender retries and then errors out with what it saw.
1336
+ - **What we did NOT do: substitute a computed gas limit.** Only the deposit is derivable; the same
1337
+ setup multicall also carries schema writes, dependency legs, URI legs and mints whose cost cannot be
1338
+ known without simulating them. A "probably enough" constant is tuned to whoever's example was in
1339
+ front of its author — it would have covered the reported single-chunk case and then under-funded a
1340
+ three-schema deploy by ~200k, reproducing the identical `DeploymentFailed()` with a fresh mystery
1341
+ attached. Refusing to send is strictly better than sending a transaction we can prove is
1342
+ under-funded, which would burn the gas and orphan the contract.
1343
+
1344
+ All three signing lanes carry this, not just the hot one: the env-key lane pins the limit before
1345
+ `sendTransaction`, the wallet lane waits for code and hands the browser an explicit `gas` (a wallet
1346
+ estimates against its own RPC, which we don't control and which lags the same way), and the cold lane
1347
+ prints `gasMustExceed` — labelled a floor, not a limit — plus a note telling an external signer to
1348
+ re-estimate rather than send if their own number comes back below it. The rule lives in one place
1349
+ (`packages/cli/src/gas.ts`) so the lanes cannot drift apart on it.
1350
+
1351
+ Reported in the 2026-08-03 tester batch (feedback 156ea0fb, 172111ae), root-caused from the full
1352
+ transaction hashes supplied in the follow-up addendum.
1353
+
1354
+ - 1158420: `abx preview` stops fabricating param values, and `--shoot` stops blaming the program for its own timeouts.
1355
+
1356
+ - **An unset PostParam is now absent from the preview's `tokenData`, exactly as on-chain.** Preview
1357
+ injected a per-type default for every declared key — `Select` got its FIRST option — so an
1358
+ optional `theme:Select[Newsprint|…]` override rendered every frame as `Newsprint` with nobody
1359
+ having set anything: nine seed-distinct pieces collapsed into one palette, while the deployed drop
1360
+ (where the key is genuinely absent) would take the program's other branch entirely. Production
1361
+ (`buildTokenData`) only injects params that actually have a value; preview now matches it, so the
1362
+ program's own `?? fallback` runs in both places. The studio's `Select` control gained an explicit
1363
+ "— unset (program fallback) —" default position, so unset is now expressible rather than
1364
+ indistinguishable from the first option.
1365
+ - **`--shoot` distinguishes "the program reported no traits" from "we stopped waiting."** A wait
1366
+ that expired was swallowed, so a loaded machine turned a correct, trait-reporting program into
1367
+ "NO frame reported traits" — inverting the one line agents are told to trust and costing a full
1368
+ diagnostic cycle. Timed-out frames now report `timed out — traits unknown` and a warning that
1369
+ names it a measurement failure and suggests `--timeout-ms`; the silent-killer alarm only fires
1370
+ when the program really did report nothing.
1371
+ - **A missing Chromium build names the install command for the Playwright that actually loaded**
1372
+ (`node <resolved>/cli.js install chromium`). The stock hint (`npx playwright install`) can resolve
1373
+ a different Playwright version than the one that just launched, so you download a browser
1374
+ revision it won't use, get the identical error, and run the same command again.
1375
+
1376
+ Reported in the 2026-08-03 tester batch (feedback f843c952, 994f1d67, 534835d9).
1377
+
1378
+ - 1158420: Teach the agent skill how to handle audio and time-based work.
1379
+
1380
+ A "can ABX host a music tool?" session found zero mentions of audio, music, or sound anywhere in the
1381
+ skill. The protocol supports it — `animation_url` is an HTML document, so Web Audio works, and
1382
+ `attach` handles `.wav`/`.mp3`/`.mid` — but five judgments a sound piece needs were unauthored, so an
1383
+ agent had to guess or decline: browser autoplay policy (a marketplace iframe cannot start audio
1384
+ without a gesture), what the thumbnail _is_ for non-visual work, `abx.done()` semantics for a
1385
+ duration-based piece (settle the visual, don't wait out playback), the dependency lane for audio
1386
+ libraries (`tone` needs a registry entry ⇒ Sepolia, like `p5`), and the fact that there is no
1387
+ `render/audio` output declaration to reach for.
1388
+
1389
+ Reported in the 2026-08-03 tester batch (feedback fd6109db).
1390
+
1391
+ - 1158420: `abx skill install` can no longer overwrite a newer skill with an older one, or delete the skill it is installing.
1392
+
1393
+ Two ways the same command could destroy the thing it exists to install, both hit while working in the
1394
+ abx repo itself:
1395
+
1396
+ - **A stale prepack bundle shadowed the canonical skill.** `<pkg>/skill` is gitignored build output
1397
+ written at `prepack`; the canonical copy lives at `.claude/skills/abx-self-host`. Resolution
1398
+ preferred the bundle unconditionally, so a leftover `skill/` from an old `npm pack` was installed
1399
+ **over** the canonical skill — silently replacing v0.1.0-alpha.12 with v0.1.0-alpha.4, after which
1400
+ the CLI's own drift check reported the stale version as if the user had put it there. When running
1401
+ from source (the repo working tree) the canonical copy now wins; the published layout, which has no
1402
+ repo and no canonical copy, still uses the bundle.
1403
+ - **Installing onto the source deleted it.** `installSkillTo` removes the destination before copying,
1404
+ so when destination _was_ the source (a cwd-relative install inside the repo) it deleted the
1405
+ canonical skill and then had nothing to copy from. Same-path installs are now a no-op that reports
1406
+ `already the canonical copy — left as is`.
1407
+
1408
+ - 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.
1409
+
1410
+ **An unknown `ABX_CHAIN` printed a raw Node stack trace — from every command.** Chain-derived values
1411
+ were resolved at module scope, in `token-api` (which the CLI imports) and in the CLI itself, so the
1412
+ throw happened during module evaluation, before `main()` existed to catch it. `ABX_CHAIN=mainnet abx
1413
+ doctor` dumped an internal source path and exited 1 — including from the one command whose job is to
1414
+ tell you what is wrong with your environment. Those resolutions are lazy now, and the CLI validates
1415
+ the variable up front with an answer rather than a crash: unknown values list the shipped chains, and
1416
+ a mainnet-shaped value says plainly that ABX is testnet-only today.
1417
+
1418
+ **`abx inspect` reported "(no PRNG)" for a hand-written seeded generator — with the _stronger_
1419
+ reproducibility verdict attached.** The `seeded` check only recognized p5's `randomSeed(`, so a
1420
+ vanilla LCG or xorshift matched no branch and fell through to "traits look derived from the
1421
+ seed/params directly". That is the common case, not an edge one — the skill's own canonical
1422
+ dependency-free example hand-rolls an LCG, and all three sketches written by agents in the sweep hit
1423
+ it. A hand-rolled generator now gets the `careful` verdict and is told the truth: deterministic and
1424
+ reproducible on-chain, but only by porting that exact generator and call order into Solidity.
1425
+
1426
+ Also: `--yes` is now documented in `deploy-code --help` (its own placeholder-identity refusal already
1427
+ told you to pass it), and the `--onchain-uri` raster warning now names the two routes that actually
1428
+ deliver a no-server image instead of only one.
1429
+
1430
+ Found by the 2026-08-03 parallel sweep (8 cold Sonnet/Haiku agents, isolated sandboxes).
1431
+
1432
+ - Updated dependencies [1158420]
1433
+ - Updated dependencies [1158420]
1434
+ - Updated dependencies [1158420]
1435
+ - @artblocks/abx-sdk@0.1.0-alpha.6
1436
+ - @artblocks/abx-token-api@0.1.0-alpha.9
1437
+ - @artblocks/abx-indexer@0.1.0-alpha.7
1438
+ - @artblocks/abx-storage@0.1.0-alpha.6
1439
+
1440
+ ## 0.1.0-alpha.12
1441
+
1442
+ ### Minor Changes
1443
+
1444
+ - feba8c2: A resolver is no longer an object store: effect outputs split into **bound** and **referenced**
1445
+ (`specs/protocol/effects.md → Bound vs referenced`), and the artifact registry enforces the split.
1446
+
1447
+ An output is **bound** iff a binding stitches its _content_ into the metadata JSON (today exactly
1448
+ `render/traits` → `attributes`); everything else is **referenced** — the projection carries its URL,
1449
+ or it only appears in the `artifacts` manifest. That one distinction decides who holds the bytes, and
1450
+ it is now the wire rule rather than a runner constant.
1451
+
1452
+ - **`POST /v1/effect-artifacts` derives the mode from the binding, and refuses both mismatches.**
1453
+ Bytes for a referenced output → `400` (the resolver redirects either way, so the bytes buy no
1454
+ capability and cost it storage, retention and egress). A locator for a bound output → `400` (its
1455
+ content is assembled into `tokenURI`; a pointer there used to be recorded and then silently never
1456
+ stitch — a wrong answer served confidently). Bound content is capped at **64 KB**, and a locator
1457
+ that only the producer could resolve (loopback/private host, presigned expiring URL) is rejected.
1458
+ The resolver never fetches a locator while handling the write, and serves registered locators by
1459
+ `302` — never by proxying.
1460
+ - **Bound content moved out of byte custody** into the artifact row (`effect_artifacts.bytes`). Two
1461
+ distinct rules, deliberately not one: a node **MUST** serve and stitch bound content only at the
1462
+ token's current settled `inputsHash`, and it **MAY** drop superseded content whenever it likes
1463
+ (nothing may read it, and it is re-creatable). The reference drops eagerly, on each bound
1464
+ registration, so it holds at most `64 KB × minted × bound outputs` — but retention is a service
1465
+ policy, not an obligation. Either way "conforming means holding a bounded amount of JSON in the
1466
+ database you already run" is now literally true: a resolver in the publish topology needs no object
1467
+ storage at all.
1468
+ - **`abx-effects-publish/v1` is gone** (not deprecated): the two routes ride `abx-control-plane/v1`.
1469
+ Once referenced output is locator-only, accepting a registration is a database insert, so the
1470
+ capability flag described a distinction that no longer exists. A service that won't take a caller's
1471
+ artifacts refuses on the credential (`403`) — interfaces describe wire grammar, tokens describe
1472
+ permission. The interface ids are also explicitly **all-or-nothing**, and the conformance fixture
1473
+ now checks that every route a declared interface names actually answers.
1474
+ - **The runner declares bound outputs** (`EffectOutputDecl.bound`), refuses to start when it has a
1475
+ publish token but a backend that can't name a locator, preflights the descriptor + credential
1476
+ before spending a render, and **latches** on a permanent (4xx) publish failure instead of
1477
+ re-rendering every sweep forever. Skips now re-register rows, so a transient publish failure heals
1478
+ without a re-render.
1479
+ - **The CLI refuses the impossible combination up front**: `abx render --remote`, `abx effects`
1480
+ against a remote resolver, and `abx deploy-effects` all require a backend that can name a reachable
1481
+ URL — `cloud` (S3/R2 + public base), `ipfs`, or `arweave`, named as **peers**. Derived output is
1482
+ re-creatable, so the protocol has no preference among schemes: a chosen `https://` gateway or
1483
+ bucket URL is exactly as legitimate as `ipfs://`/`ar://`, and reachability — not durability — is
1484
+ the requirement. Rendering **co-located** with the resolver remains fully supported on any backend,
1485
+ including `fs`.
1486
+
1487
+ Breaking for producers that relied on pushing media bytes to a resolver: publish a locator instead,
1488
+ or co-locate. Breaking for clients that read `abx-effects-publish/v1` from a descriptor.
1489
+
1490
+ ### Patch Changes
1491
+
1492
+ - Updated dependencies [feba8c2]
1493
+ - @artblocks/abx-sdk@0.1.0-alpha.5
1494
+ - @artblocks/abx-token-api@0.1.0-alpha.8
1495
+ - @artblocks/abx-indexer@0.1.0-alpha.6
1496
+ - @artblocks/abx-storage@0.1.0-alpha.5
1497
+
1498
+ ## 0.1.0-alpha.11
1499
+
1500
+ ### Minor Changes
1501
+
1502
+ - 67b686b: `abx contracturi`, and the read plane stops answering a bare 404 to three different problems.
1503
+
1504
+ Both halves come from one real failure: an agent driving a hosted resolver wanted collection
1505
+ metadata, pattern-matched off `/t/{chainId}/{address}/{id}`, dropped the token id, got a bare `404`,
1506
+ and reported the service as broken. The documented route (`/c/{chainId}/{address}`) was right there —
1507
+ but there was also no command to just _ask_, and the 404 gave it nothing to correct.
1508
+
1509
+ - **New `abx contracturi <address>`** — the collection-level counterpart of `tokenuri`. Reads
1510
+ `contractURI()` (ERC-7572) from the contract, **follows it**, and decodes: a `data:` URI inline
1511
+ (the on-chain lane), an `https://` URL by fetching it (the off-chain lane). A contract commits its
1512
+ own metadata base on-chain (`contractURIBase`), so the chain — not a doc, not a service
1513
+ descriptor — is the authoritative answer to where a project's metadata lives. Nobody needs to
1514
+ hand-build a resolver URL. When the fetch fails, the message says so plainly: the URL came from
1515
+ the chain, so a bad status is about the _service_ (unregistered project · wrong chain · down),
1516
+ never a mistyped path.
1517
+ - **Read-plane responses now carry a machine `code`**, so the three causes of "no metadata came
1518
+ back" are distinguishable — they were one indistinguishable `{"error": "…"}` `404`:
1519
+ - `400 invalid_request` — a real route, wrong shape. Names the correct template, and carries
1520
+ `didYouMean` when the fix is obvious (a `/t/…` missing its token id → `/c/{chainId}/{address}`).
1521
+ - `404 unknown_route` — this node serves nothing at that path; the body lists what it does serve.
1522
+ - `404 not_registered` — the path and chain were fine; this node doesn't index that contract.
1523
+ - `400 unsupported_chain` — wrong chain, plus the `chains` this node does serve. Was a bare `404`;
1524
+ now matches what the control plane already answered for the same condition.
1525
+ - `ServiceErrorCode` gains `unknown_route`. The spec's Errors section now covers the read plane too,
1526
+ with a **MUST** on distinguishing the three misses — and an explicit **MUST NOT** on treating
1527
+ route templates as per-node discoverable configuration. The route grammar is fixed by the
1528
+ `abx-token-api/v1` interface; these responses are diagnostics, not a discovery mechanism.
1529
+ - **The conformance fixture checks all of it** (`pnpm conformance <base-url>`), so any provider can
1530
+ self-verify in one command. Also fixed: the documented `pnpm conformance -- <base-url>` form
1531
+ parsed `--` as a flag and swallowed the base URL, printing usage instead of running.
1532
+
1533
+ ### Patch Changes
1534
+
1535
+ - Updated dependencies [67b686b]
1536
+ - @artblocks/abx-sdk@0.1.0-alpha.4
1537
+ - @artblocks/abx-token-api@0.1.0-alpha.7
1538
+ - @artblocks/abx-indexer@0.1.0-alpha.5
1539
+ - @artblocks/abx-storage@0.1.0-alpha.4
1540
+
1541
+ ## 0.1.0-alpha.10
1542
+
1543
+ ### Minor Changes
1544
+
1545
+ - a72723d: A standard indexing lifecycle, and registration that no longer blocks on a slow chain RPC
1546
+ (specs/self-host-toolkit/remote-services.md → The indexing lifecycle).
1547
+
1548
+ - **Fixed: a slow register triggered a retry storm.** The SDK's per-attempt timeout (30s) plus its
1549
+ retry ladder meant a cold reconstruct that outran one request was **re-POSTed up to four times**,
1550
+ each starting another full replay against the RPC that was already too slow to answer — and the
1551
+ caller then saw "nothing responded" even though the registration was durable and indexing was
1552
+ underway. A timed-out register now asks whether it landed (a status read) instead of re-POSTing, and
1553
+ the resolver coalesces concurrent catch-ups for one project into a single run.
1554
+ - **`POST /v1/projects` answers in two conformant shapes, discriminated by HTTP status:** `200` with
1555
+ the completed summary, or `202` + `{accepted, project: {status}}` when catch-up is deferred. The
1556
+ registration is normatively **durable before catch-up** and visible on the list immediately, so a
1557
+ flaky RPC makes for a slower backfill rather than a lost add. No `?wait=`/`Prefer:` negotiation — the
1558
+ status code is the discriminator, and clients handle both. The reference resolver answers _by
1559
+ deadline_ (`ABX_REGISTER_DEADLINE_MS`, default 8s): the common case (a fresh deploy) stays
1560
+ synchronous with real counts; only the pathological case defers.
1561
+ - **Closed lifecycle enum + error classes, on the status and list routes:**
1562
+ `queued | backfilling | live | stale | failed`, plus credential-free
1563
+ `error.class ∈ {rpc_unavailable, rpc_rate_limited, not_abx_contract, internal}` (fixed per-class
1564
+ messages, never a scrubbed upstream string). Status gains top-level `headBlock` (so lag / % complete
1565
+ is computable without knowing a service has a watcher) and `attempts`; the list carries `status` +
1566
+ the error class, so a client renders "3 live, 1 backfilling, 1 failed (rpc_rate_limited)" in one
1567
+ request. SDK: `IndexStatus`, `IndexErrorClass`, `isAccepted()`, `indexProgress()`,
1568
+ `classifyIndexError()`, and `AbxServiceClient.awaitIndexed()` — one wait loop for the CLI, the
1569
+ effects runner, and any hosted agent.
1570
+ - **The same five words on your own node.** `abx status [address] [--remote [name|url]] [--watch]`:
1571
+ bare is the node summary (now with each project's state), an address gives lifecycle + scan floor +
1572
+ blocks-indexed-vs-head + cause, and `--remote` asks a service. (`status` = who is serving it and how
1573
+ fresh; `state` = what the chain says. Both `--help` texts now say so.)
1574
+ - **New observability the self-hosted node never had:** the chain watcher marks projects `stale` when
1575
+ it falls far behind head or its ticks keep failing (previously visible only in the node's log),
1576
+ re-queues a backfill interrupted by a restart (previously left registered-but-empty until a manual
1577
+ `abx index`), and retries a `failed` catch-up on exponential backoff instead of hammering a
1578
+ rate-limited RPC every tick. Lifecycle rows live in their own table: they survive a projection wipe
1579
+ and are never clobbered by a re-add.
1580
+ - **CLI:** `abx add|index --remote` prints `registered — backfilling…`, polls to `live`, then prints
1581
+ the same summary a synchronous service would have given; `--no-wait` returns at the 202 and names
1582
+ the command to check later. A post-op nudge (`ownerops`) never blocks on someone else's backfill.
1583
+ A caught-up project with **0 events** now warns instead of printing ✓ (a real ABX clone always emits
1584
+ a spine, so zero means wrong chain/floor or an RPC that didn't serve the logs).
1585
+ - **Conformance fixture** accepts either register shape, asserts durable-before-catch-up, lifecycle
1586
+ membership, `headBlock`, that a deferred catch-up actually reaches `live`, and that no error message
1587
+ carries a URL.
1588
+ - Fixed `scripts/mock-remote-service.mts`, which imported the token API by a path that resolved
1589
+ against `scripts/` and could silently fall back to a _published_ build outside the repo — the
1590
+ fixture was testing the last release instead of the working tree. The fixture also re-points
1591
+ scenarios by their fixture header now, so a new one can't keep a dead contract address.
1592
+
1593
+ Found by a cold-agent sweep over the above (10 parallel clean rooms, haiku + sonnet) and fixed here:
1594
+
1595
+ - **`abx status --remote <name>` with no address** parsed the flag itself as the address and sent it
1596
+ as a URL path segment.
1597
+ - **A register whose catch-up already failed** was announced as "registered — failed (…is catching
1598
+ up…)", and with `--no-wait` it exited 0 and then claimed the provider "now serves" the project. A
1599
+ known failure is now an error in both lanes — there is nothing left to wait for.
1600
+ - **A `failed` status said what broke but not whose problem it was.** Both the failure error and
1601
+ `abx status` now carry a per-class action line ("the SERVICE can't reach its chain RPC — not your
1602
+ key, address, or chain…"), plus a `follow` line naming `--watch`, so a red word isn't a dead end.
1603
+ - **A `live` project showed a misleading completion percentage.** `toBlock` only advances when a
1604
+ project has _events_, so a fully current project on a busy chain read as `2/202 (0%)`.
1605
+ `indexProgress()` now returns a ratio only while `backfilling`; `live` reads "caught up", `stale`
1606
+ reads "not tracking head right now".
1607
+ - **`--remote-token` was misattributed on a 401** — the error blamed `ABX_REMOTE_<NAME>_TOKEN` even
1608
+ when the caller passed an override, making the override look ignored at exactly the moment someone
1609
+ is testing a replacement key.
1610
+ - **`not_registered` on a read** (status/reindex) now names the register command instead of echoing a
1611
+ 404, and a 5xx carrying a failure `class` becomes a wait-vs-broken error.
1612
+ - **`abx verify`'s summary** read `✓ 0/1 up to date` for a project with no off-chain renders at all —
1613
+ "zero of one succeeded" to two independent reviewers. It now says "nothing to render for this
1614
+ project", and otherwise leads with polarity ("N of M token(s) current").
1615
+ - **`abx doctor` now reports named remotes** and flags a credential stored under a name the CLI does
1616
+ not read (`ABX_REMOTE_<NAME>_KEY`). That fault presents as "it acts like I never gave it a key" and
1617
+ previously only surfaced from `abx remote <name>` — which a creator reaches _after_ doctor.
1618
+ - **Skill: the `npx --no-install abx version` probe was documented as failing cleanly.** It doesn't —
1619
+ npm will run any `abx` binary already in the npx cache, which in a real sweep reported a months-old
1620
+ build as the project's CLI (and if a plain `npx abx` ever ran on that machine, the bare name is a
1621
+ squatted package). The skill now probes `./node_modules/.bin/abx` directly.
1622
+ - Also documented: how a multi-word provider name folds into `ABX_REMOTE_<NAME>_*`, and what
1623
+ `watching: no` means on a status readout.
1624
+
1625
+ A second sweep round over those fixes caught three more, including one the first round's fix created:
1626
+
1627
+ - **`abx verify --remote` never checked byte integrity at all** — both of its lanes only ask "is there
1628
+ a current render / is this a placeholder", and a green ✓ from that was standing in for "the served
1629
+ bytes match the on-chain commitment". A reviewer hit the worst version of this: `--remote` (the form
1630
+ the skill tells you to use for a hosted project) reported ✓ on a token whose bytes genuinely did NOT
1631
+ hash-match, while bare `abx verify` on the same project reported `✗ keccak256 MISMATCH`. It now calls
1632
+ the service's own purpose-built `GET /api/project/:addr/verify` (which holds both the bytes and the
1633
+ chain) and reports that verdict separately from the render summary — and when it _can't_ run that
1634
+ check (no credential, older node) it says "byte integrity NOT checked" instead of leaving a ✓ to
1635
+ imply it passed. The remedy names both real causes (an unbridged durable locator vs. bytes that only
1636
+ exist on the creator's machine, which a hosted resolver can never serve).
1637
+ - **`abx verify` exited 0 while printing a byte MISMATCH**, in both lanes — nothing could gate on it.
1638
+ An integrity mismatch now fails the command; a missing render or placeholder is a normal state and
1639
+ still exits 0.
1640
+ - **`abx add --dry-run` silently ignored the flag and performed the registration**, local or remote.
1641
+ It now refuses and names the read-only commands (`abx state`, `abx status`) instead. Silently doing
1642
+ the thing when the caller asked to preview is the one outcome that must never happen.
1643
+ - **`PRAGMA busy_timeout` was set third in the store schema**, after the WAL switch it needs to
1644
+ protect — so two processes opening the same store at once (parallel CLI runs, or a co-located
1645
+ effects runner starting alongside the resolver) could fail outright with `database is locked`
1646
+ instead of waiting the moment out. It is now the first statement.
1647
+
1648
+ A third round, re-running the scenario that found the verify bug (it now catches it) turned up:
1649
+
1650
+ - **`abx add --remote` ended on "it now serves <url>"** — true about indexing, silent about whether
1651
+ the bytes are right, and two reviewers stopped there and reported a blank page as fixed. It now names
1652
+ the byte check (`abx verify <addr> --remote <name>`) in the same breath.
1653
+ - **`canonical:` collapsed a tri-state.** `isCanonical` is `true | false | null`, and both readouts
1654
+ printed "unverified" for the last two — so "the chain says this is NOT a clone of the configured
1655
+ factory" (a trust finding) looked identical to "the check never ran" (no factory on this chain, normal
1656
+ on a dev chain). Two reviewers read the collapsed word as a second failure sitting next to a real one.
1657
+ - **`abx verify --remote` gave a bare `fetch failed`** for an endpoint that was down, where
1658
+ `abx status --remote` names the host and asks whether it's running. Two commands, one condition, two
1659
+ error qualities — now consistent.
1660
+ - `abx status <addr>` printed the address twice when the project has no name.
1661
+ - Skill: registering with a provider on **their** hostname vs. a domain you control decides whether
1662
+ leaving later costs a transaction — now stated in the managed-provider section, before you bake it.
1663
+
1664
+ ### Patch Changes
1665
+
1666
+ - Updated dependencies [a72723d]
1667
+ - @artblocks/abx-sdk@0.1.0-alpha.3
1668
+ - @artblocks/abx-indexer@0.1.0-alpha.4
1669
+ - @artblocks/abx-token-api@0.1.0-alpha.6
1670
+ - @artblocks/abx-storage@0.1.0-alpha.3
1671
+
1672
+ ## 0.1.0-alpha.9
1673
+
1674
+ ### Minor Changes
1675
+
1676
+ - 3745bd3: Remote services are first-class: a provider-neutral control plane, named remotes, and a service
1677
+ descriptor (specs/self-host-toolkit/remote-services.md).
1678
+
1679
+ - **Control plane moves to `/v1`** (hard cutover; `/admin/*` is gone — redeploy self-hosted nodes):
1680
+ `POST/GET /v1/projects`, `DELETE|reindex|status /v1/projects/{chainId}/{address}`,
1681
+ `POST /v1/effect-artifacts|effect-status`. `chainId` is explicit and validated everywhere; every
1682
+ error carries a machine `code` (`unauthorized` 401 · `forbidden` 403 · `unsupported_chain` ·
1683
+ `not_registered` · `disabled`) replacing the old prose-sniffed 404. One bearer guard replaces the
1684
+ four inline copies; OPTIONS preflight now answers so browser clients can send `Authorization`.
1685
+ - **`GET /.well-known/abx-service`** — the public service descriptor: `interfaces` (present iff
1686
+ actually enabled), `chains`, `auth` (with optional provider-set `signupUrl`/`docsUrl` via
1687
+ `ABX_SERVICE_*` env), and `render.attached` (managed rendering, probed from the runner's
1688
+ `/health`) — so an agent can match a project to a provider before registering.
1689
+ - **Named remotes in the CLI**: `--remote <name>` reads `ABX_REMOTE_<NAME>_URL`/`_TOKEN`
1690
+ (a managed provider's per-account key — never falls back to `ABX_RESOLVER_ADMIN_TOKEN`);
1691
+ `--remote <url> [--remote-token <t>]` for ad-hoc targets; bare `--remote` stays the self-host
1692
+ default. New `abx remote [name|url]` inspects a service's descriptor and the projects a token
1693
+ sees. `migrate --from/--to` accept names; only the destination needs a credential.
1694
+ - **The SDK gains its first HTTP surface**: `AbxServiceClient` (endpoint + injected bearer, retry
1695
+ on 5xx/network, immediate typed `AbxServiceError` on 4xx) — shared by the CLI and the effects
1696
+ runner's publish lane. `envSuffix()` is the shared env-name normalization.
1697
+ - **Conformance fixture**: `pnpm conformance -- <base-url> [--token …]` self-verifies any
1698
+ implementation; the e2e suite runs it against the reference container.
1699
+
1700
+ ### Patch Changes
1701
+
1702
+ - 3745bd3: Membrane fixes found by a 20-run cold-agent regression sweep (sonnet + haiku, black-box clean rooms).
1703
+
1704
+ - **A 500 no longer leaks the node's own credentials.** An upstream RPC failure surfaced viem's
1705
+ message, which embeds the endpoint URL — and a keyed RPC URL _is_ a credential, so on a
1706
+ multi-tenant provider any tenant who could provoke a 500 got the operator's RPC key. The cause now
1707
+ goes to the node's log; the wire gets a generic message, an `internal_error` code, and a
1708
+ credential-free hint about the failure class. Normative in the remote-services spec.
1709
+ - **The service client no longer discards a 5xx body.** The service's own words survive the retry
1710
+ ladder, and an exhausted ladder says "failed — last response …" rather than mislabelling a
1711
+ server that answered as "unreachable". The descriptor probe drops to 2 attempts, so a typo'd
1712
+ provider URL fails in ~1s instead of grinding 5s, with distinct "nothing responded" vs
1713
+ "answered, but serves no descriptor" messages.
1714
+ - **Conflicting duplicate `.env` keys are reported.** First-wins is unchanged, but a stale second
1715
+ `ABX_RPC_URLS_<CHAIN>` line silently pointed the CLI at another network while every check read
1716
+ green — the symptom surfaced far away as "no contract at that address". Only genuinely
1717
+ _conflicting_ duplicates warn (identical repeats stay quiet).
1718
+ - **"No contract at …" errors now name the endpoint they asked** (redacted), because a chain key
1719
+ can't distinguish two RPCs that both claim it.
1720
+ - **A misnamed remote credential is called out.** `ABX_REMOTE_<NAME>_KEY` (or `_API_KEY`, `_SECRET`)
1721
+ is not read, so it previously reported as "no token" while the value sat in `.env`; both
1722
+ `abx remote` and the register path now name the near-miss and the correct `_TOKEN` name.
1723
+ - **`--dry-run` explains a missing trust anchor instead of crashing.** On a chain where the
1724
+ configured factory has no code, `deploy`/`deploy-series` previews died inside
1725
+ `predictDeterministicAddress` with a raw `returned no data ("0x")`; they now report it the way
1726
+ `abx predict` and a real deploy already did, and name the two ways forward. The keyless
1727
+ `--for` requirement also fails fast instead of after several steps of output.
1728
+ - **The placeholder-identity guard is one shared predicate** across all three deploy commands
1729
+ (it was copy-pasted, and one copy's comment claimed coverage it didn't have), pinned by a new
1730
+ regression test: a real send refuses tool defaults, a preview only warns.
1731
+ - **The served dashboard's empty state no longer prints `pnpm abx demo`** — a contributor-only
1732
+ invocation on a page a published user sees.
1733
+
1734
+ - Updated dependencies [3745bd3]
1735
+ - Updated dependencies [3745bd3]
1736
+ - @artblocks/abx-sdk@0.1.0-alpha.2
1737
+ - @artblocks/abx-token-api@0.1.0-alpha.5
1738
+ - @artblocks/abx-indexer@0.1.0-alpha.3
1739
+ - @artblocks/abx-storage@0.1.0-alpha.2
1740
+
1741
+ ## 0.1.0-alpha.8
1742
+
1743
+ ### Patch Changes
1744
+
1745
+ - 4074766: Fix the dashboard's block-explorer links, which were hardcoded to `https://sepolia.etherscan.io`. Every
1746
+ link on the page — contract, owner, implementation, each event's tx — pointed at Ethereum Sepolia no
1747
+ matter which chain was being served, so a dashboard for a normal `abx demo` (Base Sepolia by default)
1748
+ sent you to an explorer where the contract does not exist. The SDK now derives the explorer from viem's
1749
+ own chain metadata (`explorerUrl`/`chainById`), so adding a chain brings its explorer along and no
1750
+ hand-maintained table can drift. The CLI's separate copy of that table is collapsed into the same
1751
+ helper; `signer.ts` was already doing it correctly.
1752
+
1753
+ Drop the demo's opening "trust anchor" step. It asserted that only the canonical factory can make a
1754
+ token that _is_ an ABX token, which is false — anything following the protocol's event spine is an ABX
1755
+ token, and the factory is one route to that, not the definition. The same overclaim in the index step
1756
+ ("verified real") now reports the fact instead: made by the canonical factory, or not. The demo opens
1757
+ on the renderer step, and resolving the factory no longer prints a line of its own there.
1758
+
1759
+ - Updated dependencies [4074766]
1760
+ - @artblocks/abx-sdk@0.1.0-alpha.1
1761
+ - @artblocks/abx-token-api@0.1.0-alpha.4
1762
+ - @artblocks/abx-indexer@0.1.0-alpha.2
1763
+ - @artblocks/abx-storage@0.1.0-alpha.1
1764
+
1765
+ ## 0.1.0-alpha.7
1766
+
1767
+ ### Patch Changes
1768
+
1769
+ - c16c0f0: Shorten the update check's cache from 24h to **6h**. A day-long cache let someone work a whole
1770
+ session — deploys included — against a CLI that had been superseded that morning, without ever being
1771
+ told. In a fast-moving alpha line that's the common case, not the edge one.
1772
+
1773
+ The agent skill now **acts** on version drift instead of reporting it. It already reconciled
1774
+ skill⇄CLI drift, but nothing told it what to do when the CLI's own `update available` notice fired —
1775
+ so on an agent-driven surface that notice landed as a human-shaped message and got relayed or
1776
+ ignored. The skill now upgrades the CLI itself (matching how it was installed), resyncs the skill, and
1777
+ reloads before deploying, with the reason stated: a stale CLI can hold canonical addresses that have
1778
+ since moved, and a mid-run `… is not a function` is usually exactly this.
1779
+
1780
+ Docs: the Upgrading page now leads with the two install shapes in the same order as the quickstart
1781
+ (global, then per-project) instead of opening on a one-off `npx` invocation, drops the update-check
1782
+ silencing details (they live in the CLI reference, alongside `version`), and answers the question the
1783
+ old "Upgrade the SDK" section provoked — upgrading the CLI upgrades the SDK it pins, so a CLI user has
1784
+ nothing separate to maintain; you install the SDK only when writing TypeScript against ABX directly.
1785
+
1786
+ ## 0.1.0-alpha.6
1787
+
1788
+ ### Patch Changes
1789
+
1790
+ - c21ea30: `abx demo` now runs **fully on-chain** by default, and speaks plainly.
1791
+
1792
+ Its generative SVG is inlined into the contract, so no `http://localhost:8787` is baked into the
1793
+ on-chain `tokenURI` base. The old default shipped a first-ever token that resolved for nobody but its
1794
+ author — broken on every marketplace, dead the moment `abx serve` stopped — and taught that as the
1795
+ normal shape of an NFT. It also undercut the demo's own claim: with the art on-chain, "rebuilt from the
1796
+ chain alone" now covers the image, not just the metadata. The final dashboard still starts, but is
1797
+ framed as a local viewer rather than infrastructure. Hand the demo an `--image`, a `--backend`, or a
1798
+ `--public-base-url` and it switches back to off-chain custody, where that split is real and worth
1799
+ teaching.
1800
+
1801
+ Fixed alongside it: on every on-chain lane (1/1 and Series, wallet and `--onchain-image`) the deploy
1802
+ step announced `URIs point at http://localhost:8787` even though nothing was baked — in the demo, two
1803
+ lines after promising no localhost anywhere.
1804
+
1805
+ The walkthrough's teaching text is rewritten in plain language, with the protocol vocabulary kept as a
1806
+ dim aside instead of the headline: steps are now "Who vouches for this token?", "Mint it", "What the
1807
+ chain knows now", and "The moment of truth · delete it all". The read-back step reads from the real
1808
+ source per lane — `tokenURI(0)` on the contract when fully on-chain (what a marketplace actually does,
1809
+ and it proves no server is involved), the resolver when custody is off-chain.
1810
+
1811
+ The browser-wallet signing prompt now says to connect a wallet holding testnet ETH and links a faucet.
1812
+ That was the one funding surface with no guidance: on `--sign` without `--for` the address isn't known
1813
+ until the wallet connects, so the up-front balance check never ran and an empty wallet's first signal
1814
+ was a failed transaction.
1815
+
1816
+ - c21ea30: Make the update check independent of which npm dist-tag prereleases are published under. It now
1817
+ resolves the `latest` tag and, when the running version is a prerelease, that version's own channel
1818
+ tag (`alpha`, `beta`, …), reporting whichever is newer. Previously it only asked for `latest`, which
1819
+ works today only because `ci:publish` passes no `--tag`; the day a stable release ships and
1820
+ prereleases move to `--tag alpha`, the nudge would have gone silent for prerelease users with
1821
+ nothing erroring. The on-disk cache is now keyed by release channel too, so switching between the
1822
+ alpha and stable lines re-checks instead of serving a day-stale answer.
1823
+ - Updated dependencies [c21ea30]
1824
+ - @artblocks/abx-indexer@0.1.0-alpha.1
1825
+ - @artblocks/abx-token-api@0.1.0-alpha.3
1826
+
1827
+ ## 0.1.0-alpha.5
1828
+
1829
+ ### Patch Changes
1830
+
1831
+ - afb36a3: Fix three bugs that made the documented first run (`abx demo`) look broken.
1832
+
1833
+ **A 0-event index was reported as success, and served.** `eth_getLogs` is
1834
+ read-after-write inconsistent on load-balanced RPCs: `waitForTransactionReceipt`
1835
+ resolves against a node that has the block, then the log query lands on one that
1836
+ doesn't yet and returns nothing for a block we _know_ contains our deploy. The CLI
1837
+ took that single read at face value, printed `✓ reconstructed 0 events`, stored the
1838
+ empty projection and served an empty dashboard — no events, no token, nothing to
1839
+ look at. This reproduced 100% of the time against `https://sepolia.base.org`, which
1840
+ is the **default endpoint when there is no `.env`** — so the documented first run
1841
+ was the path that broke. Every post-deploy index (`deploy`, `demo`, `deploy-series`,
1842
+ `deploy-code`, `add`) now re-scans with backoff instead of trusting one read, since
1843
+ having just minted means the spine cannot legitimately be empty. If it still comes
1844
+ back empty, that is now reported as a failure naming `abx index <addr> --full` as
1845
+ the recovery, rather than dressed up as a ✓.
1846
+
1847
+ **The demo told you to press a button that does not exist.** It ended with _"Open
1848
+ the dashboard, then hit 'Re-index from chain'"_. The dashboard is read-only —
1849
+ re-index and verify are admin actions that 404 unless the node has an
1850
+ `ABX_RESOLVER_ADMIN_TOKEN` — so that control isn't there to find. It now points at
1851
+ the spine table (which _is_ the reconstruction) and at `abx index <addr> --full` to
1852
+ replay it. The dashboard's own note also stopped printing a **shortened** address
1853
+ inside a copy-pasteable command, and no longer suggests `--remote` for a local node.
1854
+
1855
+ **Re-running the demo crashed after spending a transaction.** With port 8787 already
1856
+ busy — an `abx demo` or `abx serve` in another terminal, i.e. exactly what happens
1857
+ when you run the demo twice — the deploy went through, was paid for, and _then_ the
1858
+ serve step died with an unhandled Node `EADDRINUSE` stack trace. The port is now
1859
+ preflighted before anything irreversible, so it refuses with "Nothing was deployed"
1860
+ and suggests `--port <n+1>`.
1861
+
1862
+ **`abx demo` is now a walkthrough rather than a smoke test.** The docs point a
1863
+ first-time reader here, but it asserted its interesting claims without ever showing
1864
+ them — "reconstructed 9 events — no provider involved" with the events invisible.
1865
+ It now teaches, continuously and without pauses (so agents and CI behave
1866
+ identically): it explains why the trust anchor is the factory and not a spoofable
1867
+ event, names what goes on chain versus what stays a keccak256 commitment, prints
1868
+ the reconstructed event spine with what each event told us (tagged ABX vs plain
1869
+ ERC-721/7572), then **deletes its own local projection and replays it from the
1870
+ deploy block**, comparing a sha256 fingerprint of every chain-derived field to prove
1871
+ it lands on identical state. Finally it reads the token back the way a marketplace
1872
+ would. `abx deploy` is unchanged — it stays terse.
1873
+
1874
+ Adds `Store.dropProjection(address)` / `SelfHostIndexer.dropProjection()`: discard a
1875
+ project's reconstructed projection while keeping its registration, so the next index
1876
+ rebuilds from the deploy block. That's the primitive the rebuild proof needs, and it
1877
+ makes "the projection is a disposable cache" a checkable claim rather than a comment.
1878
+
1879
+ - Updated dependencies [afb36a3]
1880
+ - @artblocks/abx-token-api@0.1.0-alpha.2
1881
+
1882
+ ## 0.1.0-alpha.4
1883
+
1884
+ ### Minor Changes
1885
+
1886
+ - 48d96c5: Add **`abx preview`** — the studio lane for code projects: run the program on
1887
+ localhost, live, while it's still being made. No chain, no key, no deploy.
1888
+
1889
+ `abx preview --script art.js --schema "palette:HexColor:TokenOwner"` serves a
1890
+ studio on `localhost:8788` — shuffle seeds, drive every declared PostParam from a
1891
+ real typed input (a color picker for `HexColor`, a dropdown for `Select`), read the
1892
+ traits the program actually reported, and `/grid` to see N seeds at once. `/view`
1893
+ is the bare document. The program is re-read from disk on every render, so the loop
1894
+ is edit-and-refresh with no watcher and no restart.
1895
+
1896
+ It serves the **same template-mode document the generator serves** — the real
1897
+ `abx.js`, the real canonical `tokenData` shape, the real dependency script tags —
1898
+ with a synthetic bytes32 seed in place of a minted one, so what you approve is what
1899
+ deploys. There is no second copy of the runtime to drift from. (`@artblocks/abx-token-api`
1900
+ now exports `ABX_JS` and the inline-safety escapes so the CLI can build that exact
1901
+ document rather than reimplement it.)
1902
+
1903
+ `--shoot <dir>` drives the same server headlessly to PNGs plus a `traits.json` and
1904
+ exits — that's how an agent, which can't open a browser, sees what the creator sees.
1905
+ It also flags the two silent killers: no frame reporting traits (⇒ no marketplace
1906
+ `attributes` on any lane) and identical traits across every seed (⇒ the program
1907
+ isn't reading `abx.tokenData.seed`, so the drop mints N identical tokens).
1908
+
1909
+ Why a live server rather than a screenshot sweep: a still flattens every time-based
1910
+ piece, and `abx.done()` exists precisely because stills need a settle point — so a
1911
+ proof sheet of an animated piece is a set of arbitrary frozen frames presented as
1912
+ the work. Frames render into a fixed 1000×1000 viewport and are scaled to fit their
1913
+ slot, so a program that hardcodes its canvas size is never clipped to its own corner.
1914
+
1915
+ Skill: add **Phase 0**, an explicit authoring phase that puts every deploy decision
1916
+ (hosting, thumbnail, traits, storage, wallet, supply, royalties, name/symbol) off
1917
+ the table until the creator says ship, and points at `abx preview` for the loop. The
1918
+ skill previously went straight from "the creator brought an idea" to "pick a deploy
1919
+ lane", so an agent helping someone _design_ a piece front-loaded infrastructure
1920
+ questions while there was still nothing to look at.
1921
+
1922
+ Skill: also teach CLI resolution — probe project-local (`npx --no-install abx`)
1923
+ before global, install `@artblocks/abx-cli` (not `@artblocks/abx-sdk`, which ships
1924
+ no binary), and default to a per-project install. The skill previously assumed `abx`
1925
+ was already on PATH and gave no bootstrap path at all, so agents improvised — one
1926
+ installed the SDK, then went global unprompted.
1927
+
1928
+ ### Patch Changes
1929
+
1930
+ - 48d96c5: `abx demo` now honors the signing lane instead of silently discarding it. It
1931
+ hard-forced the hot (env-key) lane, so `abx demo --sign` failed confusingly when
1932
+ no key was configured — and, worse, signed from the env key when one _was_
1933
+ present, even though the operator had explicitly asked for their browser wallet.
1934
+ `--sign` (and `--for` to pin the connecting wallet) now work on `demo` exactly as
1935
+ they do on `deploy`.
1936
+
1937
+ `abx demo --unsigned` and `abx demo --dry-run` are now refused with an
1938
+ explanation rather than ignored: both skip the broadcast, and `demo` indexes and
1939
+ serves the contract it just deployed, so there would be nothing to index. Use
1940
+ `abx deploy --unsigned` / `abx deploy --dry-run` for those lanes.
1941
+
1942
+ Also documents the `demo` signing flags in `abx help demo`.
1943
+
1944
+ - Updated dependencies [48d96c5]
1945
+ - @artblocks/abx-token-api@0.1.0-alpha.1
1946
+
1947
+ ## 0.1.0-alpha.3
1948
+
1949
+ ### Patch Changes
1950
+
1951
+ - 6fbb62e: Restructure `abx doctor` for clarity. Two tiers now: PASS/FAIL checks (✓/✗) for
1952
+ things that are working or broken — agent skill (shown first and prominently; a
1953
+ missing/stale skill is a ✗ since agent-driven use is the primary UX), RPC,
1954
+ factory, storage — and an "Optional — depends how you deploy" block (·) for
1955
+ path-dependent setup (signing lane, resolver URL, Arweave key). `⚠` is no longer
1956
+ used for values that are unset-but-fine (it read as noise); it's reserved for a
1957
+ genuine gotcha such as a range-capped-only RPC. The RPC report collapses to one
1958
+ line.
1959
+ - 6fbb62e: Fix the update-check hint to invoke the CLI by its scoped package name
1960
+ (`npx @artblocks/abx-cli@latest <command>`). The bare `npx abx` resolves an
1961
+ unrelated squatted `abx` package on npm, not this CLI, so the old hint pointed
1962
+ users at a command that fails. (Installed users — global or per-project — run
1963
+ `abx` / `npx abx` as before; only the zero-install invocation needs the scoped
1964
+ name.)
1965
+
1966
+ ## 0.1.0-alpha.2
1967
+
1968
+ ### Minor Changes
1969
+
1970
+ - 02fc0e0: Make `abx skill install` agent-aware and strictly version-lock the skill to the CLI.
1971
+
1972
+ `skill install` now installs to the directories every supported agent actually reads: by default
1973
+ both `.claude/skills` (Claude Code) and the neutral `.agents/skills` (Cursor, Codex CLI, Gemini CLI,
1974
+ GitHub Copilot), so one command covers the whole ecosystem. `--agent claude|cursor|codex|gemini|copilot`
1975
+ narrows it to one; `--target <dir>` now writes the skill folder straight under `<dir>`.
1976
+
1977
+ The skill's version now lives in its `SKILL.md` frontmatter (`metadata.version`), stamped at release
1978
+ to equal the CLI version (replacing the old `.abx-skill-version` sidecar). Because the version travels
1979
+ inside the skill file, the drift check finds a stale copy no matter how it was installed — including
1980
+ `npx skills add` — and `abx doctor` reports the skill/CLI version match explicitly. A publish-time gate
1981
+ (and `pnpm ci:version` stamping) keeps the two from ever shipping out of lockstep.
1982
+
1983
+ ## 0.1.0-alpha.1
1984
+
1985
+ ### Patch Changes
1986
+
1987
+ - b5d201f: Add `abx version` and a notify-only update check. On startup `abx` now checks npm at most once a
1988
+ day and, when a newer release is published, prints an upgrade hint to stderr (never stdout, so it
1989
+ never corrupts machine-readable output an agent is parsing). It also nudges to reinstall the agent
1990
+ skill when the installed copy has drifted behind the CLI. Opt out with `ABX_NO_UPDATE_CHECK=1` or
1991
+ `--no-update-check`; it is a no-op in CI and when offline.