@artblocks/abx-cli 0.1.0-alpha.20 → 0.1.0-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1081 -0
- package/assets/renderer-scaffold/README.md +2 -2
- package/assets/renderer-scaffold/src/MyRenderer.sol +2 -2
- package/assets/renderer-scaffold/src/interfaces/IAbxFieldRenderer.sol +1 -1
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +2 -2
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +1 -1
- package/dist/commands/deploy.d.ts +30 -17
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +523 -117
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/project.d.ts +16 -0
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +171 -15
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reads.d.ts.map +1 -1
- package/dist/commands/reads.js +35 -5
- package/dist/commands/reads.js.map +1 -1
- package/dist/commands/scaffold.d.ts +3 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +81 -29
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/service.js +1 -1
- package/dist/commands/service.js.map +1 -1
- package/dist/commands/storage.d.ts.map +1 -1
- package/dist/commands/storage.js +11 -3
- package/dist/commands/storage.js.map +1 -1
- package/dist/commands/submit-app.d.ts +58 -0
- package/dist/commands/submit-app.d.ts.map +1 -0
- package/dist/commands/submit-app.js +512 -0
- package/dist/commands/submit-app.js.map +1 -0
- package/dist/config.d.ts +1 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/flag-allowlists.d.ts +53 -0
- package/dist/flag-allowlists.d.ts.map +1 -0
- package/dist/flag-allowlists.js +152 -0
- package/dist/flag-allowlists.js.map +1 -0
- package/dist/flags.d.ts +6 -2
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +23 -0
- package/dist/flags.js.map +1 -1
- package/dist/kind.d.ts +8 -3
- package/dist/kind.d.ts.map +1 -1
- package/dist/kind.js +25 -0
- package/dist/kind.js.map +1 -1
- package/dist/main.js +166 -53
- package/dist/main.js.map +1 -1
- package/dist/mintpage.d.ts.map +1 -1
- package/dist/mintpage.js +29 -4
- package/dist/mintpage.js.map +1 -1
- package/dist/output.d.ts +32 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +74 -9
- package/dist/output.js.map +1 -1
- package/dist/ownerops.d.ts +101 -13
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +421 -71
- package/dist/ownerops.js.map +1 -1
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +1 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +8 -2
- package/dist/remote.js.map +1 -1
- package/dist/schema.d.ts +18 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +37 -2
- package/dist/schema.js.map +1 -1
- package/dist/served.js +1 -1
- package/dist/update-check.d.ts +25 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +49 -9
- package/dist/update-check.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +64 -32
- package/skill/reference/code-projects.md +98 -22
- package/skill/reference/creator-token.md +16 -3
- package/skill/reference/decisions.md +83 -11
- package/skill/reference/hosting.md +1 -1
- package/skill/reference/operating.md +35 -12
- package/skill/reference/setup.md +1 -1
- package/skill/reference/troubleshooting.md +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,1086 @@
|
|
|
1
1
|
# @artblocks/abx-cli
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.22
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1b6b741: Rename "artist" to "creator" throughout, including the protocol vocabulary
|
|
8
|
+
|
|
9
|
+
ABX is a tool for creators generally. Art remains a first-class use case, but it should not be baked
|
|
10
|
+
into the product's framing — and it was, down to the on-chain identifiers.
|
|
11
|
+
|
|
12
|
+
**Breaking, and deliberately a clean break** (pre-launch, folded into the one redeploy that four
|
|
13
|
+
rounds of security remediation already required — deferring would have meant either living with the
|
|
14
|
+
old vocabulary permanently or spending a second deployment on a word):
|
|
15
|
+
|
|
16
|
+
- `AuthOption` is now `Creator`, `TokenOwner`, `Address`, `CreatorOrTokenOwner`, `CreatorOrAddress`,
|
|
17
|
+
`TokenOwnerOrAddress`, `CreatorOrTokenOwnerOrAddress`. Order is unchanged, so the `uint8` values
|
|
18
|
+
are identical — only the names moved. Users type these: `--schema key:Type:Creator`.
|
|
19
|
+
- The on-chain collection metadata field keys `artist` / `artist_links` are now `creator` /
|
|
20
|
+
`creator_links`. These are `bytes32` keys written on-chain and emitted verbatim into every
|
|
21
|
+
collection's `contractURI` JSON.
|
|
22
|
+
- CLI flags `--artist` / `--artist-links` are now `--creator` / `--creator-links`. No aliases.
|
|
23
|
+
- `@artblocks/abx-token-api` renames its `art` module to `content`: `generateArt` →
|
|
24
|
+
`generateContent`, `artContentHash` → `contentHash`.
|
|
25
|
+
- `AbxMetadataRenderer.SPEC_VERSION` is **7**, and `isCurrentRenderer` gates on it. A v6 renderer
|
|
26
|
+
emits a different member key, so it is genuinely behind, not cosmetically so.
|
|
27
|
+
|
|
28
|
+
Package CHANGELOGs are left alone on purpose: they record what past alpha versions actually shipped,
|
|
29
|
+
and rewriting them would make the release history lie about flag names that really were `--artist`.
|
|
30
|
+
|
|
31
|
+
- 1b6b741: A purchase now carries the terms the buyer accepted — closing the one path by which a project owner
|
|
32
|
+
could take more than the buyer agreed to.
|
|
33
|
+
|
|
34
|
+
Both shared fixed-price minters take a mandatory **terms guard** — not a slippage allowance; see the note
|
|
35
|
+
below — and revert a new `SaleTermsChanged()` when the live sale doesn't match:
|
|
36
|
+
|
|
37
|
+
```solidity
|
|
38
|
+
purchase(address token, address expectedPaymentToken, uint256 maxPrice)
|
|
39
|
+
purchaseTo(address token, address to, address expectedPaymentToken, uint256 maxPrice)
|
|
40
|
+
// 1155 — the bound is the TOTAL, since qty multiplies it
|
|
41
|
+
purchase(address token, uint256 id, uint256 qty, address expectedPaymentToken, uint256 maxTotalPrice)
|
|
42
|
+
purchaseTo(address token, uint256 id, uint256 qty, address to, address expectedPaymentToken, uint256 maxTotalPrice)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`configure` takes effect immediately and has no timelock. On the ETH lane that was largely contained
|
|
46
|
+
(`msg.value` must equal `price`), but an ERC-20 sale settles `safeTransferFrom(buyer, payee, price)`
|
|
47
|
+
against a standing allowance — so an owner who front-ran a pending purchase with a huge price spent the
|
|
48
|
+
buyer's whole approval, and one who switched the sale to a different ERC-20 reached an allowance granted
|
|
49
|
+
somewhere else. `address(0)` as `expectedPaymentToken` means ETH. There is deliberately no "no maximum"
|
|
50
|
+
sentinel: a bound that can be defaulted away is the bug being fixed.
|
|
51
|
+
|
|
52
|
+
SDK (breaking): `preparePurchase` / `preparePurchase1155` take the live terms — `sale`, exactly what
|
|
53
|
+
`readSaleConfig` / `readSaleConfig1155` returns — instead of a hand-computed `value`. They derive the
|
|
54
|
+
ETH to attach (`price`, or `price × quantity`) and the bound from those terms, so the payment and the
|
|
55
|
+
guard cannot desync. Pass `maxPrice` / `maxTotalPrice` to state a wider ceiling (a UI that tolerates a
|
|
56
|
+
small move rather than making the buyer re-sign); it widens the guard only, never the payment. New
|
|
57
|
+
exported type `PurchaseTerms`.
|
|
58
|
+
|
|
59
|
+
**`maxPrice` is not slippage tolerance on the ETH lane, and the docs no longer imply it is.** The minter
|
|
60
|
+
requires `msg.value == price` (an equality) and V1 has no refund path, so an in-flight ETH purchase
|
|
61
|
+
reverts if the price moves in _either_ direction, however wide the ceiling. Real tolerance exists only on
|
|
62
|
+
the ERC-20 lane, where the live `price` is pulled from the buyer's allowance and anything at or below the
|
|
63
|
+
ceiling settles.
|
|
64
|
+
|
|
65
|
+
CLI: `abx minter buy` hands the terms it just read to the SDK (the price × quantity math now lives in
|
|
66
|
+
one place), and the scaffolded `abx mint-page` binds each mint to the terms the page is displaying —
|
|
67
|
+
if the owner re-prices mid-click the mint reverts, nothing is charged, and the page tells the buyer to
|
|
68
|
+
review the new price. Re-pricing a live sale therefore fails the buys already in flight, by design:
|
|
69
|
+
pause first if you want a clean cutover.
|
|
70
|
+
|
|
71
|
+
**Deploy note:** this is a runtime change to two shared singletons, so both need a redeploy on every
|
|
72
|
+
supported chain (CREATE2 + canonical salts, so the new addresses are identical cross-chain) and the new
|
|
73
|
+
addresses recorded in `packages/sdk/src/deployments.ts` +
|
|
74
|
+
`site/content/docs/reference/deployments.mdx`. Until then the CREATE2-prediction-vs-manifest tests fail
|
|
75
|
+
on purpose — that is the drift detector doing its job, not a broken test.
|
|
76
|
+
|
|
77
|
+
- 1b6b741: The configure hook can see a blob's size and contents — so creators can police it instead of the protocol
|
|
78
|
+
|
|
79
|
+
`IAbxConfigureHook.onParamConfigured` gains two arguments:
|
|
80
|
+
|
|
81
|
+
```solidity
|
|
82
|
+
function onParamConfigured(
|
|
83
|
+
uint256 tokenId,
|
|
84
|
+
bytes32 key,
|
|
85
|
+
bytes32 value, // the literal on the scalar path; keccak256(data) on the blob path
|
|
86
|
+
address updatedBy,
|
|
87
|
+
uint256 dataLength, // 0 on the scalar path; data.length on the blob path (never 0 there)
|
|
88
|
+
address dataBlobAddress // address(0) on the scalar path; the live SSTORE2 pointer otherwise
|
|
89
|
+
) external;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Why.** On a multi-copy edition params belong to the id, so a holder-writable `String`/`Bytes` key is
|
|
93
|
+
shared: one holder could store large values and make the shared id's document expensive for every
|
|
94
|
+
co-holder. An independent audit asked for a protocol byte ceiling. A ceiling would cap every project
|
|
95
|
+
to police a configuration almost nobody wants — but the alternative we had documented, "the creator
|
|
96
|
+
constrains it in their own hook", was not actually possible: the hook only ever saw `keccak256(data)`.
|
|
97
|
+
It could not learn the length, could not read the content, and could not read the new value from
|
|
98
|
+
storage either, because nothing had persisted yet. Now it can, and no protocol limit is imposed.
|
|
99
|
+
|
|
100
|
+
**The bytes are not forwarded** — a blob can be nearly a full contract's worth of data, and putting it
|
|
101
|
+
in calldata would make every configure call pay for it whether the hook looks or not. A hook enforcing
|
|
102
|
+
a size ceiling reads `dataLength`; a hook that needs content calls `SSTORE2.read(dataBlobAddress)` and
|
|
103
|
+
pays for exactly what it asked for. Guaranteed: `keccak256(SSTORE2.read(dataBlobAddress)) == value`.
|
|
104
|
+
|
|
105
|
+
**Two things to know when writing one.** `tokenParamData(tokenId, key)` still returns the OLD value
|
|
106
|
+
inside the hook (that is what "before the value persists" means — and it lets you veto a regression).
|
|
107
|
+
And the blob is written _before_ the hook, because an address is meaningless until the contract behind
|
|
108
|
+
it exists; so a veto happens after the writer paid to store the bytes. The accepted path costs the
|
|
109
|
+
same as before, only rejection wastes, and it wastes only the rejecting writer's own gas.
|
|
110
|
+
|
|
111
|
+
`ConfigurableParams` is extension **v3** — the beacon's `extensionVersion` is how an integrator tells
|
|
112
|
+
which calling convention a deployed token uses. A hook written for the 4-argument form will revert
|
|
113
|
+
against a v3 token, and vice versa.
|
|
114
|
+
|
|
115
|
+
Addresses: `AbxParamsLib` moved, `AbxEditionLib` with it (it links it), and the four token types that
|
|
116
|
+
compose `ConfigurableParams` moved with their factories — `SeriesCode`, `EditionCode`,
|
|
117
|
+
`OneOfOneEdition`, `EditionImage`. The two ERC-721 image types and their factory, `AbxMetadataLib`,
|
|
118
|
+
`AbxCodeLib`, the renderer, chunk store, seed source and both minters keep their addresses.
|
|
119
|
+
|
|
120
|
+
- 1b6b741: Renderer spec v10 projects four long-reserved keys; edition trust anchors move
|
|
121
|
+
|
|
122
|
+
**Renderer spec v10.** Four keys the spec reserved from the start were projected on chain by nothing:
|
|
123
|
+
`background_color` and `youtube_url` (token), `banner_image` and `featured_image` (collection). A
|
|
124
|
+
documented reserved key that no surface emits is a hole rather than a saved byte — a creator sets it,
|
|
125
|
+
observes nothing, and cannot tell whether the tool or the marketplace is at fault. All four now project
|
|
126
|
+
when their representation is chain-reachable, and are omitted when unset. `isCurrentRenderer` gates on
|
|
127
|
+
10; a v9 renderer silently drops them.
|
|
128
|
+
|
|
129
|
+
**`collaborators` is no longer a reserved key.** It was in the registry, projected by neither plane, and
|
|
130
|
+
because reserved keys are excluded from the resolver's `artifacts` listing, setting it on chain did
|
|
131
|
+
nothing observable anywhere. It is now an ordinary creator key — which means it **does** list in
|
|
132
|
+
`artifacts`. Put a collaborator list in a named artifact (`abx attach`) or in `description`.
|
|
133
|
+
|
|
134
|
+
**The resolver's two collection image keys are now symmetric with the renderer.** `featured_image` is a
|
|
135
|
+
top-level `contractURI` key instead of artifacts-only, and `banner_image` projects for every
|
|
136
|
+
chain-reachable representation instead of only `url` (an `inline` or `reader` banner used to vanish).
|
|
137
|
+
|
|
138
|
+
**Docs now carry the three-list split** — which keys both planes project, which are the off-chain
|
|
139
|
+
resolver's alone by design (`abx_provenance.status`, operator overlay, effects/live-view, the
|
|
140
|
+
`artifacts` listing, collection `image` courtesy, `image_data`, off-chain-decode values), and which are
|
|
141
|
+
not reserved at all. That split is the thing to read before adding a key to either surface: drift on a
|
|
142
|
+
key both planes _can_ emit is what produced three earlier remediations.
|
|
143
|
+
|
|
144
|
+
**Addresses.** The three edition factories move (trust anchors) along with their implementations, and
|
|
145
|
+
the renderer moves. `AbxEditionLib`, `AbxMetadataLib`, `AbxCodeLib`, `AbxParamsLib`, all three ERC-721
|
|
146
|
+
factories, the chunk store, the seed source, both minters and both generators keep their addresses.
|
|
147
|
+
Existing clones keep running their frozen implementations; re-point a live collection at the new
|
|
148
|
+
renderer with `abx set-renderer` rather than redeploying it.
|
|
149
|
+
|
|
150
|
+
- f64a31f: Expose `lockScript` above the contract layer — the lock that actually freezes a code project's work.
|
|
151
|
+
|
|
152
|
+
`SeriesCode`/`EditionCode` have always had `lockScript()` (and `scriptLocked()`), but nothing above
|
|
153
|
+
the ABI surfaced it: the SDK had no `prepareLockScript`, the CLI had no `lock-script` command, and the
|
|
154
|
+
skill's immutability guidance told creators that `lock-field` + `lock-uri` made a project "provably
|
|
155
|
+
immutable". For a generative/code drop that is false — those freeze only the _metadata_; the owner can
|
|
156
|
+
keep rewriting the on-chain program (`setScriptChunk`/`removeLastScriptChunk`) until the script is
|
|
157
|
+
locked. Agents building on ABX repeatedly missed that the program could be frozen at all.
|
|
158
|
+
|
|
159
|
+
- **SDK:** new `prepareLockScript({contract, chainId})` (mirrors `prepareLockDependencies`).
|
|
160
|
+
- **CLI:** new `abx lock-script <address>` command (any signing lane), plus `abx verify` now reports
|
|
161
|
+
the script and dependency lock state for code projects (`script: locked/UNLOCKED`, with a nudge to
|
|
162
|
+
`lock-script` when the program is still mutable) — read from the already-indexed state, no extra RPC.
|
|
163
|
+
- **Skill:** the immutability guidance now names the full freeze for a code drop
|
|
164
|
+
(`lock-script` + `lock-dependencies` + `lock-field`/`lock-uri`, plus `set-schema … lock=now` for a
|
|
165
|
+
param value), corrects the "provably immutable" claim, and surfaces PostParams as a first-class
|
|
166
|
+
`deploy-code` capability (not only a generative feature) in the `SKILL.md` description and decision
|
|
167
|
+
tree so collector-settable on-chain parameters are discoverable.
|
|
168
|
+
|
|
169
|
+
- 1b6b741: Independent-audit remediation: `abx_provenance` reshaped, `abx_params` removed, hook ABI widened
|
|
170
|
+
|
|
171
|
+
An independent adversarial audit of the contracts found no Critical or High issue, no theft path and
|
|
172
|
+
no cross-project reach — but four Mediums, eight Lows and five architectural notes. Everything is
|
|
173
|
+
addressed. Consumer-visible changes:
|
|
174
|
+
|
|
175
|
+
**`abx_provenance` entries are now `{field, source, note}`.** They lost `onChain`, which was wrong in
|
|
176
|
+
both directions — a `url` reported `false` although the URL string is stored on chain, while an
|
|
177
|
+
`inline` value of `"https://…"` reported `true` for a pure pointer — and `verifiedAgainstChain`,
|
|
178
|
+
which was hardcoded `null` on every entry. `source` says where the bytes came from; whether a value
|
|
179
|
+
resolves on chain is visible in the value, and that judgment belongs to the reader. The off-chain
|
|
180
|
+
resolver drops `onChain` for the same reason (it was exactly `status === 'on-chain'`) and keeps
|
|
181
|
+
`status`, which it can genuinely compute.
|
|
182
|
+
|
|
183
|
+
**`abx_params` no longer appears in `tokenURI`.** It was emitted by the on-chain renderer AND the
|
|
184
|
+
resolver and parsed back by nobody. Params enumerate directly from the contract — `tokenParamKeys`,
|
|
185
|
+
`tokenParam`, `paramSchemaKeys` — which is canonical, needs no indexer, and is what a chain-only
|
|
186
|
+
reader should use; a code project's script still receives them through `tokenData`. Traits for
|
|
187
|
+
marketplaces belong in `attributes`, unchanged.
|
|
188
|
+
|
|
189
|
+
**A computed `image` locator now lands verbatim.** A field renderer returning
|
|
190
|
+
`("text/uri-list", "ipfs://…")` was being data-wrapped into `data:text/uri-list;base64,…`, which no
|
|
191
|
+
marketplace dereferences. `image` and `animation_url` now share one implementation of the rule.
|
|
192
|
+
|
|
193
|
+
**`IAbxTransferHook.onTokenTransfer` gains `operator` and `amount`.** Without them a hook on a
|
|
194
|
+
shared-supply ERC-1155 cannot tell a real transfer from a zero-amount no-op — which let any address
|
|
195
|
+
fire the param lifecycle for an id it held no copy of. The token also refuses to notify on
|
|
196
|
+
zero-amount and self-transfers.
|
|
197
|
+
|
|
198
|
+
**`pingURI` is owner-only**, and `paramSchemaHead` / `selectOption` are new: resolving one selected
|
|
199
|
+
option no longer copies the whole option table on every render.
|
|
200
|
+
|
|
201
|
+
`AbxMetadataRenderer.SPEC_VERSION` is 8. Every canonical address moves — every token type is now
|
|
202
|
+
library-linked, including the two ERC-721 image factories, because the metadata field store was
|
|
203
|
+
externalized into the new `AbxMetadataLib` to restore EIP-170 headroom before any fix was stacked on
|
|
204
|
+
top of 187 bytes of margin.
|
|
205
|
+
|
|
206
|
+
The CLI now warns when `--copies` meets a holder-writable `--schema`: on an edition, params belong to
|
|
207
|
+
the id, so every holder shares one value and the last writer wins.
|
|
208
|
+
|
|
209
|
+
- 1b6b741: The custom seed source is now a real choice, not a documented one
|
|
210
|
+
|
|
211
|
+
We tell creators — in the docs, in `AbxSeedSource`'s own NatSpec, and in `deploy-code --help` — that the
|
|
212
|
+
canonical mint seed is pseudorandom and that the answer for anything lottery-like is to point
|
|
213
|
+
`seedSource` at their own `IAbxSeedSource` over commit-reveal or a VRF oracle. On-chain that was always
|
|
214
|
+
true. In the toolkit it was not: there was no flag, no owner command, and the only route was an
|
|
215
|
+
`ABX_SEED_SOURCE` env var validated as nothing more than "has code at this address". A doc/code
|
|
216
|
+
contradiction of our own making, now closed at both ends.
|
|
217
|
+
|
|
218
|
+
**Deploy:** `abx deploy-code --seed-source <0x…|canonical>` (and the `--copies` edition lane). Omitting
|
|
219
|
+
it is unchanged — the canonical `AbxSeedSource` from the manifest, bootstrapped on a chain that lacks
|
|
220
|
+
one — and `--no-seed` still means no mint-time seed at all. Passing both is refused rather than resolved
|
|
221
|
+
in someone's favour. `ABX_SEED_SOURCE` keeps working and is no longer the unchecked lane.
|
|
222
|
+
|
|
223
|
+
**After deploy:** `abx set-seed-source <address> <0x…|canonical|none>` — owner-only, same positional
|
|
224
|
+
grammar as `abx set-transfer-validator`. It surfaces the on-chain `setSeedSource(address)` that nothing
|
|
225
|
+
reached before. **Future mints only**: a seed settles the instant it is assigned, so nothing already
|
|
226
|
+
minted changes. The command says so, names the count of tokens already carrying a seed from the old
|
|
227
|
+
source, and points out that a part-sold drop then spans two sources — public on the spine
|
|
228
|
+
(`SeedSourceSet`) but not something an early buyer is told, so pause and say so.
|
|
229
|
+
|
|
230
|
+
**`abx state` now prints the seed source**, labelled: `none`, the canonical singleton, or `CUSTOM`. It is
|
|
231
|
+
the one setting an owner can re-point mid-sale that changes what a later buyer receives, and it was
|
|
232
|
+
readable nowhere.
|
|
233
|
+
|
|
234
|
+
**Both writes probe the address first, and `code.length > 0` is not the check.** The SDK gained
|
|
235
|
+
`probeSeedSource` — one `eth_call` to `seed(uint256,address)`, which must return 32 bytes — plus
|
|
236
|
+
`readSeedSource`, `prepareSetSeedSource`, `assertSeedSourceUsable`, and a `SeedSourceUnusableError`
|
|
237
|
+
carrying a structured verdict. Four shapes are refused, each with its own message: **no code**;
|
|
238
|
+
**empty return** (the permissive-fallback shape — a Safe, an uninitialised proxy, an EIP-7702-delegated
|
|
239
|
+
EOA; pasting your own wallet is the common way in); **a return under 32 bytes**; and **a revert** (either
|
|
240
|
+
the wrong address entirely, or a real source not yet armed — `seed()` is called synchronously inside the
|
|
241
|
+
mint and its revert bubbles, so a source that cannot answer now cannot answer at mint either). The
|
|
242
|
+
interface is non-`view` on purpose, but an `eth_call` simulates a state-keeping source fine, so
|
|
243
|
+
commit-reveal and oracle-fed sources probe without sending anything. This matters because a
|
|
244
|
+
misconfigured seed source is **completely silent**: the write succeeds, `seedSource()` reads back what
|
|
245
|
+
you set, the event fires, `abx state` shows it — and then every mint of the collection reverts in the
|
|
246
|
+
token's `bytes32` decode. Same spirit as `CreatorToken._requireHasCode`.
|
|
247
|
+
|
|
248
|
+
**Naming fix: a `seed` param schema is not a "re-roll", and no longer described as one.** The governed
|
|
249
|
+
path is `configureTokenParam(tokenId, "seed", value)` — **the caller supplies the value**. Nothing is
|
|
250
|
+
re-randomized and the seed source is never consulted again; with an unbounded `Uint256Range` the
|
|
251
|
+
authorized party may set any 32-byte value, repeatedly, until they like the output. "Re-roll" promises a
|
|
252
|
+
buyer a fresh random draw they could reasonably expect to be fair, which is not what they get. It is a
|
|
253
|
+
collector-chosen (or creator-chosen) seed, and the docs, the specs, and the skill now say that. The
|
|
254
|
+
substance that was correct is unchanged: the schema is a pre-sale commitment (declarable only before the
|
|
255
|
+
collection's first seed exists), `paramSchema("seed")` is the buyer's read, and the type must be a
|
|
256
|
+
literal/scalar one.
|
|
257
|
+
|
|
258
|
+
**Dropped a stale warning** in the skill about a `seed` schema changing how the seed decodes off-chain
|
|
259
|
+
while the on-chain generator injects raw hex — `tokendata.ts` returns the raw hex for `seed`
|
|
260
|
+
unconditionally, ahead of the schema branch, so the wire format is invariant across both surfaces and
|
|
261
|
+
there is nothing left to warn about.
|
|
262
|
+
|
|
263
|
+
**Also corrected: `maxPrice` is a terms assertion, not slippage tolerance.** On the ETH lane the minter
|
|
264
|
+
requires `msg.value == price` (an equality) and V1 has no refund path, so an in-flight ETH purchase
|
|
265
|
+
reverts if the price moves in _either_ direction, however wide the ceiling — widening `maxPrice` on an
|
|
266
|
+
ETH sale buys the buyer nothing. Real tolerance exists only on the ERC-20 lane, where the live price is
|
|
267
|
+
pulled from the buyer's allowance. `specs/protocol/minter-spine.md` and
|
|
268
|
+
`site/content/docs/protocol/minting.mdx` said "a price cut still settles" without that distinction.
|
|
269
|
+
|
|
270
|
+
- 1b6b741: Renderer spec v9: a computed image is carried once, not twice
|
|
271
|
+
|
|
272
|
+
A field-renderer-produced image was emitted as the reserved `image` key **and again** inside the
|
|
273
|
+
`artifacts` manifest, then the whole document was base64-encoded around both copies. On the
|
|
274
|
+
on-chain-SVG lane that roughly doubled the inner payload of `tokenURI` — the one read this protocol
|
|
275
|
+
most wants to stay within ordinary RPC limits.
|
|
276
|
+
|
|
277
|
+
The data-plane spec always allowed an on-chain renderer to omit entries duplicating reserved keys it
|
|
278
|
+
already emits ("an EVM-efficiency reduction, never a semantic one"). The renderer now takes that for
|
|
279
|
+
every reserved key, so on a fully-on-chain project `artifacts` is absent rather than empty. Nothing is
|
|
280
|
+
lost, because the entry's only unique contribution was a `mimeType` and a `data:` URI states its own.
|
|
281
|
+
The off-chain resolver still emits the complete listing, duplicates included — that asymmetry is the
|
|
282
|
+
spec's, and it is now documented on both surfaces.
|
|
283
|
+
|
|
284
|
+
Also removed: three orphaned param getters on the renderer (`paramKeysOf`, `paramSchemaOf`,
|
|
285
|
+
`paramDataOf`), stranded when v8 took params out of `tokenURI`, whose NatSpec still claimed `tokenURI`
|
|
286
|
+
called them.
|
|
287
|
+
|
|
288
|
+
`AbxMetadataRenderer.SPEC_VERSION` is **9** and `isCurrentRenderer` gates on 9. Only the renderer
|
|
289
|
+
moves — nothing links it, so no factory or implementation address changes. Existing on-chain-URI
|
|
290
|
+
projects keep resolving through the v8 renderer until re-pointed with `abx set-renderer`.
|
|
291
|
+
|
|
292
|
+
- 1b6b741: Upgrading from alpha.12/alpha.13 — what breaks, in one place
|
|
293
|
+
|
|
294
|
+
This release lands the contract-audit branch. It is a **compatibility break with earlier alphas**, which
|
|
295
|
+
is fine at this stage (greenfield alpha, no backwards-compatibility promise) but is worth having in one
|
|
296
|
+
list rather than spread across a dozen changesets. Nothing here is a deprecation with a migration
|
|
297
|
+
window; the old shapes are gone.
|
|
298
|
+
|
|
299
|
+
**Every canonical address moved**, and four manifest fields are new (`metadataLib`, `paramsLib`,
|
|
300
|
+
`codeLib`, `editionLib` — the delegatecalled write-path libraries). Resolve through `getDeployment` and
|
|
301
|
+
the upgrade is transparent; anything you have _stored_ keyed by a factory address is not. A project
|
|
302
|
+
indexed from a superseded anchor reads `isCanonical: false` against the new manifest — correct, since it
|
|
303
|
+
is what a creator sees if they don't redeploy, but it is a data question, not a version bump.
|
|
304
|
+
|
|
305
|
+
**The metadata document changed: renderer spec v4 → v9.** If you assemble or assert on it:
|
|
306
|
+
|
|
307
|
+
- `abx_provenance` entries lost `onChain` (it was exactly `status === 'on-chain'`, and wrong in both
|
|
308
|
+
directions) and `verifiedAgainstChain` (hardcoded `null` on every entry). `source` and `status` stay.
|
|
309
|
+
- The `abx_params` block is gone from **both** lanes. Params enumerate from the contract —
|
|
310
|
+
`tokenParamKeys` / `tokenParam` / `paramSchemaKeys` — which is canonical and needs no indexer; a code
|
|
311
|
+
project's script still receives them through `tokenData`.
|
|
312
|
+
- `artist` / `artist_links` are now `creator` / `creator_links` (reserved on-chain collection field
|
|
313
|
+
keys), and `AuthOption.Artist*` is `AuthOption.Creator*` (numeric values unchanged).
|
|
314
|
+
- A computed `image` locator (`text/uri-list`) lands **verbatim** instead of being wrapped into a
|
|
315
|
+
`data:` URI no marketplace dereferences.
|
|
316
|
+
- The on-chain renderer no longer duplicates a computed image into `artifacts`. The off-chain resolver
|
|
317
|
+
still emits the complete listing — that asymmetry is specified, not a bug.
|
|
318
|
+
- Four long-reserved keys now project on chain: `background_color`, `youtube_url` (token) and
|
|
319
|
+
`banner_image`, `featured_image` (collection). `featured_image` also becomes a top-level resolver key
|
|
320
|
+
instead of artifacts-only, and `banner_image` projects for every chain-reachable representation
|
|
321
|
+
rather than `url` alone.
|
|
322
|
+
- `collaborators` is **no longer reserved** — projected by neither plane and excluded from `artifacts`
|
|
323
|
+
by its own reserved-ness, so it did nothing. It is now an ordinary creator key that does list.
|
|
324
|
+
|
|
325
|
+
**`tokenData` divergences fixed, so rendered output can change:** a contract-scope `seed` no longer
|
|
326
|
+
leaks into a token's `tokenData` (the on-chain generator never read one), `seed`'s wire format is always
|
|
327
|
+
raw 32-byte hex regardless of schema, and `</script` escaping is case-insensitive to match the on-chain
|
|
328
|
+
generator. Cached renders for affected code projects should be re-derived.
|
|
329
|
+
|
|
330
|
+
**Removed:** `paramKeysOf`, `paramSchemaOf`, `paramDataOf` on `AbxMetadataRenderer` — orphaned when
|
|
331
|
+
params left `tokenURI`. Read the token directly instead.
|
|
332
|
+
|
|
333
|
+
**Two hook interfaces widened** (`ConfigurableParams` is extension **v3**; a hook built for the old
|
|
334
|
+
signature reverts against a v3 token): `IAbxTransferHook.onTokenTransfer` gained `operator` + `amount`;
|
|
335
|
+
`IAbxConfigureHook.onParamConfigured` gained `dataLength` + `dataBlobAddress`.
|
|
336
|
+
|
|
337
|
+
**Additive:** `ParamHooksFrozen` folds into `ParamHooks.locked`; `paramSchemaHead` + `selectOption`
|
|
338
|
+
replace pulling a whole option table to resolve one index; `readParamHooks`; `abxJs()` / `gunzipScript()`
|
|
339
|
+
on the generator for piecewise document assembly; and the three ERC-1155 ABIs now carry
|
|
340
|
+
`MetadataUpdate` / `BatchMetadataUpdate` / `DefaultMaxSupplySet`, which were emitted but not decodable.
|
|
341
|
+
|
|
342
|
+
- 1b6b741: Transfer-validator authorization has one home, and the five affected trust anchors move
|
|
343
|
+
|
|
344
|
+
`CreatorToken1155.setTransferValidator` performed no authorization at all, and the library it
|
|
345
|
+
delegates to performed none either. What protected the three edition token types was that each one
|
|
346
|
+
_overrode_ the setter to run the caller check first — one guard in three copies, sitting in front of
|
|
347
|
+
an unguarded mixin whose own comment claimed the tokens carried no override. Nothing deployed was
|
|
348
|
+
vulnerable, but a fourth edition type that simply inherited the mixin would have shipped an
|
|
349
|
+
unauthenticated `setTransferValidator`, which decides whether collectors' tokens can move at all.
|
|
350
|
+
|
|
351
|
+
The guard now runs in the mixin, the overrides are gone, and the function is no longer `virtual` —
|
|
352
|
+
so the arrangement is enforced by the compiler rather than by a comment. A dead
|
|
353
|
+
`_requireMinterOrOwner` in `ExternalMinter`, whose NatSpec wrongly advertised it as the mint-time auth
|
|
354
|
+
check (the real one also enforces `paused()`), is deleted in the same pass.
|
|
355
|
+
|
|
356
|
+
**Both changes are source-only** — the compiled runtime bodies are byte-identical before and after,
|
|
357
|
+
measured across all ten affected artifacts. Only the metadata hash differs, and since that is part of
|
|
358
|
+
the initcode, the CREATE2 addresses move. Five factories and their implementations are redeployed and
|
|
359
|
+
re-verified on Sepolia and Base Sepolia: `SeriesImageFactory`, `SeriesCodeFactory`,
|
|
360
|
+
`OneOfOneEditionFactory`, `EditionImageFactory`, `EditionCodeFactory`. Everything else — the 1/1 image
|
|
361
|
+
factory, all four libraries, the renderer, chunk store, seed source, both minters, both generators —
|
|
362
|
+
keeps its address.
|
|
363
|
+
|
|
364
|
+
Projects deployed from the superseded factories keep working, but `abx` will report their anchor as
|
|
365
|
+
an older version. Redeploy from the anchors in the refreshed manifest.
|
|
366
|
+
|
|
367
|
+
### Patch Changes
|
|
368
|
+
|
|
369
|
+
- 1b6b741: `ABX_CHAIN` in a `.env` now actually selects the chain
|
|
370
|
+
|
|
371
|
+
Found by a cold sandbox agent: the creator asked for Sepolia, put `ABX_CHAIN=sepolia` in `.env`, and
|
|
372
|
+
got a **Base Sepolia** preview with no error. The agent scored it 3/5 and called it "a serious trust
|
|
373
|
+
problem for a tool whose entire pitch is 'here's exactly what would be deployed'". It was right.
|
|
374
|
+
|
|
375
|
+
`config.ts` derives `CHAIN` as a module-level const, so it evaluated while the import graph loaded —
|
|
376
|
+
before `main()` called `loadDotEnv()`. Every other setting escaped this by being read lazily inside a
|
|
377
|
+
function (`storageOptions()`, the signing key at send time), by which point `.env` was loaded.
|
|
378
|
+
`ABX_CHAIN` was the one eager read, and the worst one to lose: on a funded send it is a wrong-chain
|
|
379
|
+
deploy with real artifacts at an address nobody meant.
|
|
380
|
+
|
|
381
|
+
The same ordering hid a second symptom: `assertKnownChainEnv()` also runs at module scope, so a typo
|
|
382
|
+
like `ABX_CHAIN=mainnet` in `.env` read as "unset" and the CLI quietly used the default instead of
|
|
383
|
+
printing its "not a chain this toolkit ships / mainnet is not supported yet" refusal. Both are fixed
|
|
384
|
+
by loading `.env` before the chain is derived, and both are covered by tests.
|
|
385
|
+
|
|
386
|
+
- 1b6b741: Externalize the code-custody READ surfaces into `AbxCodeLib` — B22 step 2, buying back the bytes the ERC-4906 audit fix spent
|
|
387
|
+
|
|
388
|
+
The audit remediation that made `OnChainMetadata`'s field writes emit ERC-4906 (closing a real finding:
|
|
389
|
+
that surface mutated `tokenURI` silently, so a correct indexer served the pre-edit document forever) cost
|
|
390
|
+
~96 B per token, and `SeriesCode` and `EditionCode` had no room for it — both fell under the 150 B EIP-170
|
|
391
|
+
floor `contracts/test/CodeSize.t.sol` guards. That is precisely the trigger condition `docs/10-backlog.md`
|
|
392
|
+
B22 was written for, and step 2 was the prescribed answer.
|
|
393
|
+
|
|
394
|
+
The `IAbxOnChainScript` and `IAbxDependencies` view bodies now live in `AbxCodeLib`, next to the write
|
|
395
|
+
paths that were already there — `scriptChunkCount`/`scriptChunk`/`scriptLocked` and
|
|
396
|
+
`dependencyCount`/`dependencyByIndex`/`dependencyRegistry`/`dependenciesLocked`. The mixins keep the
|
|
397
|
+
functions (same signatures, same ABI, same ERC-165 ids) as **raw-calldata passthroughs** through a new
|
|
398
|
+
shared `AbxCodeDelegate` base, which forwards the call's exact calldata to the library and returns the
|
|
399
|
+
return data untouched. Storage still resolves in the token's own ERC-7201 namespaces and events still log
|
|
400
|
+
from the token — that is what `delegatecall` buys.
|
|
401
|
+
|
|
402
|
+
Raw, not typed, on purpose: B22 step 1 measured a _typed_ shell (`return AbxCodeLib.f(...)`) **growing**
|
|
403
|
+
the token by ~855 B, because decoding the library's return value and re-encoding it at the call site costs
|
|
404
|
+
more than the extracted body saves. The same mistake was made again during this remediation, on
|
|
405
|
+
`setTokenField`/`setContractField`, and reverted. The price of the raw form is that `AbxCodeLib`'s read
|
|
406
|
+
signatures are now part of every composing token's external ABI — same signature ⇒ same selector is the
|
|
407
|
+
whole mechanism — so they must track the interfaces verbatim; both the library and the mixins now say so.
|
|
408
|
+
|
|
409
|
+
EIP-170 margins, before → after:
|
|
410
|
+
|
|
411
|
+
| contract | before | after | floor |
|
|
412
|
+
| ------------- | ------ | ------- | ----- |
|
|
413
|
+
| `SeriesCode` | 125 | **529** | 150 |
|
|
414
|
+
| `EditionCode` | 132 | **507** | 150 |
|
|
415
|
+
|
|
416
|
+
`AbxCodeLib` grew 3,905 → 4,961 B and still has ~19.6 KB to spare. Nothing else moved: `OnChainScript` and
|
|
417
|
+
`Dependencies` are composed **only** by `SeriesCode` and `EditionCode`, and both already linked
|
|
418
|
+
`AbxCodeLib`, so no contract gained a library dependency and the other four tokens are byte-identical.
|
|
419
|
+
|
|
420
|
+
No behavior change and no ABI change — the read signatures, return types, mutability, ERC-165 ids and the
|
|
421
|
+
`ScriptIndexOutOfRange`/`DependencyIndexOutOfRange` error selectors are all preserved (the errors stay
|
|
422
|
+
declared on the mixins so they remain in each token's ABI, and now revert from the library's
|
|
423
|
+
identically-named, identically-selectored twin). This does move the implementation bytecode, so it rides
|
|
424
|
+
the redeploy this remediation already requires: `AbxCodeLib`, both code tokens, and their factories (and
|
|
425
|
+
therefore the factory trust anchors) all get new addresses.
|
|
426
|
+
|
|
427
|
+
- 1b6b741: CREATE2 for library deployments, as the standard process — and delete the false belief that said it was impossible
|
|
428
|
+
|
|
429
|
+
An audit found that two ERC-1155 token types were held library-free, and an EIP-170 size floor relaxed, on
|
|
430
|
+
the recorded belief that linking a delegatecalled library costs a factory its CREATE2-deterministic address.
|
|
431
|
+
That belief is false, and the committed broadcast artifacts always refuted it: every recorded library deploy
|
|
432
|
+
went through the keyless CREATE2 proxy, `AbxParamsLib` landed at ONE address on Sepolia and Base Sepolia
|
|
433
|
+
from deployer nonces **215 apart** (a nonce is not an input to a CREATE2 address), and both library-linked
|
|
434
|
+
factories have held chain-identical addresses across four deploy generations.
|
|
435
|
+
|
|
436
|
+
What was true was narrower and self-inflicted: `deploy.ts` deployed its libraries with `send({to: null})` —
|
|
437
|
+
a plain EOA `CREATE` — **by its own choice**, so _it_ could not predict them. `create2.ts` and both code
|
|
438
|
+
factory deploy functions then wrote that local limitation up as a property of libraries in general, and the
|
|
439
|
+
repo proceeded to contradict itself (`deployments.ts` and `reference/deployments.mdx` describe the correct
|
|
440
|
+
mechanism a few files away).
|
|
441
|
+
|
|
442
|
+
The SDK now does what the docs already claimed:
|
|
443
|
+
|
|
444
|
+
- Both bootstraps (`deploySeriesCodeFactory`, `deployEditionCodeFactory`) deploy every write-path library
|
|
445
|
+
through the keyless proxy at the canonical `AbxSalts` salts — `abx.lib.params.v1`, `abx.lib.code.v1`,
|
|
446
|
+
`abx.lib.edition.v1`, mirrored in `ABX_SALT` and pinned against `AbxSalts.sol` by a new test — and then
|
|
447
|
+
CREATE2 the linked factory at its own salt. **An SDK-bootstrapped chain and a forge-bootstrapped chain now
|
|
448
|
+
produce identical addresses** (verified: the SDK's predictions equal what
|
|
449
|
+
`DeployLibraries.s.sol --sig 'predict()'` prints).
|
|
450
|
+
- Idempotent, out of necessity rather than politeness: the proxy _reverts_ on an occupied address, so a
|
|
451
|
+
library already on-chain is linked against instead of redeployed — which is also what makes a partially
|
|
452
|
+
bootstrapped chain recoverable by re-running.
|
|
453
|
+
- New: `predictParamsLib`, `predictCodeLib`, `predictEditionLib`, `predictSeriesCodeFactory`,
|
|
454
|
+
`predictEditionCodeFactory`, plus exported `libPlaceholder` / `linkLibraries` / `LIB_FQN`. Both code
|
|
455
|
+
factories therefore gained the CREATE2 self-heal every other canonical anchor has — and need no version
|
|
456
|
+
probe for it, because code at a CREATE2 address is bytecode-bound to the build that predicted it.
|
|
457
|
+
|
|
458
|
+
**Fixes a real crash on the way past.** `deployEditionCodeFactory` could not succeed at all: it deployed
|
|
459
|
+
`abxEditionLibBytecode` raw, but `AbxEditionLib` itself delegatecalls `AbxParamsLib`, so its shipped
|
|
460
|
+
bytecode carries an unlinked `__$…$__` placeholder — not hex, so the RPC rejected it with an opaque
|
|
461
|
+
`Invalid byte sequence`. The old guard only checked the _factory's_ bytecode, never the library's. The
|
|
462
|
+
library is now linked before it is deployed, and `linkLibraries` throws with the offending placeholder named.
|
|
463
|
+
|
|
464
|
+
**The trap that IS real, now written down** (`create2.ts`, `reference/deployments.mdx`): a library's address
|
|
465
|
+
is a function of its creation bytecode, so build settings move it. Linking _after_ compilation — substituting
|
|
466
|
+
into solc's placeholder, what forge's automatic linking and the SDK both do — cannot change that bytecode.
|
|
467
|
+
Handing solc the addresses at build time (`--libraries src/…:Lib:0x…`) writes the map into
|
|
468
|
+
`settings.libraries` in every artifact's metadata, and the metadata hash is appended to the creation
|
|
469
|
+
bytecode: measured here, that moves the library's own address and the linked factory's initcode. Optimizer
|
|
470
|
+
profile does the same (the code tokens compile at 200 runs). Both failures are invisible until two chains
|
|
471
|
+
disagree, so the docs now say to compare `predict()` against the SDK's predictions rather than assume.
|
|
472
|
+
|
|
473
|
+
Docs and specs swept for the old claim: `reference/deployments.mdx` (the salts, and a "libraries go first"
|
|
474
|
+
bootstrap step naming `DeployLibraries.s.sol` and its `predict()` dry run), `reference/contracts.mdx`
|
|
475
|
+
(`AbxEditionLib` was missing from the library taxonomy entirely), `reference/sdk.mdx`,
|
|
476
|
+
`specs/self-host-toolkit/deployment.md`, and the `abx-self-host` skill's code-projects reference. No
|
|
477
|
+
addresses were invented: nothing is redeployed yet, and the two manifest-drift tests still stand as the
|
|
478
|
+
detectors waiting on it.
|
|
479
|
+
|
|
480
|
+
- 1b6b741: `abx doctor` stops calling a dust balance "funded", and its provenance line stops guessing how you invoked it
|
|
481
|
+
|
|
482
|
+
Both found by cold agents in the clean room, one of them twice.
|
|
483
|
+
|
|
484
|
+
**Funding had two states where it needed three.** A wallet holding `0.000000000848940602 ETH` — 848
|
|
485
|
+
gwei, enough for nothing — reported as `funded`, in full wei precision, with no faucet link, because the
|
|
486
|
+
test was `balance > 0n`. An agent then spent its whole run preparing a deploy it could not send and
|
|
487
|
+
named the quiet funding signal as its top friction. Now: zero is `empty`, below 0.001 ETH is
|
|
488
|
+
`too little to deploy` with a faucet link, and anything above reads `funded` at six decimals instead of
|
|
489
|
+
eighteen. The floor is a readout threshold, **not** a refusal — we don't estimate anyone's gas, and a
|
|
490
|
+
cheap L2 deploy below it is the caller's call.
|
|
491
|
+
|
|
492
|
+
**The `binary` line said `source checkout (pnpm abx)`** even when the caller had typed a bare `abx` that
|
|
493
|
+
happened to resolve to a checkout. `source` was right; the parenthetical named an invocation that never
|
|
494
|
+
happened. It now says what is running rather than how you typed it. A provenance line that misstates the
|
|
495
|
+
invocation undermines every readout sitting next to it.
|
|
496
|
+
|
|
497
|
+
Not changed, deliberately: `deploy --dry-run` still refuses to print an address without `--salt`. An
|
|
498
|
+
agent asked for a non-final example address to skip the two-step dance, but the salt is the only thing
|
|
499
|
+
that pins the address, and printing a real-looking one that won't be the deployed address is the exact
|
|
500
|
+
failure that refusal exists to prevent.
|
|
501
|
+
|
|
502
|
+
- 1b6b741: Editions: the buyer picks the work, and the skill stops promising a freeze we withdrew
|
|
503
|
+
|
|
504
|
+
**Say what the edition mint flow actually is.** The 721 Series lane and the edition lane answer "who
|
|
505
|
+
picks the work?" in opposite ways, and only the 721 half was written down. `IAbxSequentialMint` takes a
|
|
506
|
+
recipient and mints `nextTokenId` — nothing is selectable. The edition minter is keyed `(token, id)`, so
|
|
507
|
+
`purchase(token, id, …)` _names the work_, and on an `EditionCode` project each id carries its own
|
|
508
|
+
`seed`, drawn at that id's **first** mint because the seed belongs to the work rather than to a copy.
|
|
509
|
+
Choosing the id therefore chooses the seed. That is the product, not a leak: an edition is copies of a
|
|
510
|
+
known work, and seeing what you are buying is the normal case.
|
|
511
|
+
|
|
512
|
+
Two consequences are now documented rather than left to be discovered:
|
|
513
|
+
|
|
514
|
+
- Buying an **already-minted** id is choosing among settled, publicly readable works.
|
|
515
|
+
- Buying an **unminted** id draws its seed with the buyer's chosen `id` in the preimage — a real
|
|
516
|
+
selection surface (compute each candidate id's seed in a view loop, buy the best), bounded by which ids
|
|
517
|
+
have a configured sale and by `maxInvocations`. This is the same grinding surface the recipient `to` was
|
|
518
|
+
removed from the preimage to avoid, and `id` cannot be removed the same way, because it _is_ the
|
|
519
|
+
work. So `AbxSeedSource`'s "accept-or-decline, not choose" is now stated as the **721-lane** property
|
|
520
|
+
it is, everywhere it appeared (`protocol/code-projects`, `specs/protocol/code-projects`,
|
|
521
|
+
`specs/protocol/event-spine`, the skill).
|
|
522
|
+
|
|
523
|
+
**Creator mints the supply, then lists on secondary** is documented as a first-class way to run an
|
|
524
|
+
edition: no minter, no allocation, no pause to manage, and every seed settles under the creator's own
|
|
525
|
+
transactions. It needs nothing new — the owner may always mint, paused or not, up to the id-space and
|
|
526
|
+
per-id caps — and it composes with a sale (reserve to self, price the rest).
|
|
527
|
+
|
|
528
|
+
**The shipped skill's frontmatter no longer advertises "freeze/lock a project so it can never change"** —
|
|
529
|
+
the exact promise `SKILL.md`'s own body walks back. It now names what a lock actually freezes (fields, URI
|
|
530
|
+
config, script, dependencies) plus the new param-hook freeze, so the sentence that gets the skill _picked_
|
|
531
|
+
agrees with the sentence that tells the creator the truth. The router stayed a router: the seed paragraph
|
|
532
|
+
and the Solidity-lane paragraph were compressed to pay for the new material, with the depth in
|
|
533
|
+
`reference/code-projects.md`.
|
|
534
|
+
|
|
535
|
+
- 1b6b741: Stop promising immutability the locks don't deliver — and name the residual owner powers in one place
|
|
536
|
+
|
|
537
|
+
**The claim that was wrong.** With the field lock, URI lock, contract-URI lock, script lock and
|
|
538
|
+
dependency lock _all_ engaged, the rendered output can still change, two ways. **Params have no lock at
|
|
539
|
+
all** — they aren't fields, so no field lock reaches them, and the renderer projects the whole enumerated
|
|
540
|
+
set into `tokenURI` as `abx_params` (a program reads the same set as `tokenData`). And a
|
|
541
|
+
**`Resolution.Registry` dependency is re-fetched from the registry contract on every read** —
|
|
542
|
+
`lockDependencies` freezes the ref and the registry pointer, not another contract's storage. `abx verify`
|
|
543
|
+
reported `chain-complete` on both sides of a registry swapping its library bytes, and the CLI was telling
|
|
544
|
+
people "a fully immutable code drop is lock-script + lock-dependencies + lock-field/lock-uri".
|
|
545
|
+
|
|
546
|
+
Every unqualified verdict is now qualified rather than deleted, because locking a pointer _is_ part of the
|
|
547
|
+
story: `lock-uri`, `lock-script` and `lock-dependencies` narration, their `--help` entries, `abx verify`'s
|
|
548
|
+
chain-complete pass (which now says the flag describes **where** bytes come from, not that they're frozen),
|
|
549
|
+
`GeneratorStatus.chainComplete`'s doc comment, the `lockScript` SDK docs, `protocol/metadata`,
|
|
550
|
+
`protocol/params`, `protocol/code-projects`, `reference/cli`, the operate guide, `specs/protocol/*`, and the
|
|
551
|
+
skill's SKILL.md · `reference/decisions.md` · `reference/code-projects.md` · `reference/operating.md`. The
|
|
552
|
+
line the toolkit now gives a creator is **"your metadata is locked"**, with the stronger claim reserved for
|
|
553
|
+
a project whose graph is on-chain `0x…` deps plus locked fields. Stated once per place the decision is made,
|
|
554
|
+
not restated everywhere.
|
|
555
|
+
|
|
556
|
+
Framed as a capability as well as a caveat: a token that _deliberately_ live-adapts to on-chain conditions
|
|
557
|
+
is a legitimate and interesting thing to build, and it's the same mechanism that makes it possible.
|
|
558
|
+
|
|
559
|
+
**New page: [What a project owner can do](https://abx.docs.artblocks.io/protocol/owner-powers/).** There was
|
|
560
|
+
nowhere a collector could read what powers the creator retains. It states both halves plainly, with the read
|
|
561
|
+
for each. What an owner **can** do: re-point or suspend an enrolled 721C/1155C transfer validator (and block
|
|
562
|
+
transfers entirely with one that reverts — though if they renounce ownership, anyone may suspend it and
|
|
563
|
+
nobody can re-arm it); change price, payee, allocation and pause on a live sale with no timelock; keep
|
|
564
|
+
receiving primary-sale proceeds after selling the project, because `primaryPayee` doesn't move on
|
|
565
|
+
`transferOwnership`; mint reserves outside the minter's allocation, since assigning a minter grants the whole
|
|
566
|
+
remaining cap rather than a slice; change royalty receiver and bps forever; re-point metadata subject to the
|
|
567
|
+
locks above; swap the seed source; reassign a token's seed _iff_ a `seed` schema was declared before the
|
|
568
|
+
collection's first seed existed; and walk away. What they provably **cannot** do: take a buyer's funds beyond
|
|
569
|
+
the terms that buyer signed for (`purchase`/`purchaseTo` require `expectedPaymentToken` and a maximum, and
|
|
570
|
+
revert `SaleTermsChanged`); exceed a supply or per-id edition cap; rewrite or clear a settled seed; reach
|
|
571
|
+
another project through any shared singleton (the renderer and chunk store hold no storage, the generator's
|
|
572
|
+
wiring is `immutable` with no setters, the seed source is a stateless view namespaced by caller, and the
|
|
573
|
+
minters key everything per token and hold no funds); or take a token out of a collector's wallet — there is no
|
|
574
|
+
owner-only transfer, burn, or clawback anywhere in the protocol.
|
|
575
|
+
|
|
576
|
+
Framing, deliberately: ABX is self-serve, the creator is a privileged role, the protocol's job is to keep
|
|
577
|
+
that role away from a collector's funds and tokens, and the residual powers are disclosed so a collector can
|
|
578
|
+
price them. Not an accusation — a creator who destroys their own collection has destroyed what they were paid
|
|
579
|
+
for. The point is that "trust me" should be optional.
|
|
580
|
+
|
|
581
|
+
- 1b6b741: `minter configure` no longer tells an edition its minter is unassigned when it isn't
|
|
582
|
+
|
|
583
|
+
The readiness footer `abx minter configure` prints after configuring an edition sale read
|
|
584
|
+
`minter()`, `primaryPayee()` and `paused()` through the ERC-721 1/1 ABI, which has none of them. The
|
|
585
|
+
call threw client-side, the `.catch` swallowed it into a zero address, and a correctly-configured
|
|
586
|
+
edition was told to go run `set-minter` and `set-primary-payee` — both of which it had already done.
|
|
587
|
+
|
|
588
|
+
This is the same bug `minter show` had, fixed on 2026-08-05; only the show path was corrected then.
|
|
589
|
+
The footer now reads through the edition ABI, and a live regression test covers it, so the two paths
|
|
590
|
+
can't drift apart again.
|
|
591
|
+
|
|
592
|
+
- 1b6b741: Gate on-chain content by what a node will READ, not by what it costs to write
|
|
593
|
+
|
|
594
|
+
The documented on-chain envelope — ≲24 KB/file, ≲256 KB/project — was a _storage-cost_ figure sold as a
|
|
595
|
+
support envelope, and the CLI's only loud warning fired on the project total. Measurement says the
|
|
596
|
+
binding constraint is somewhere else entirely: **`tokenURI` reassembles the whole document on every
|
|
597
|
+
call**, and that cost is **superlinear** — EVM memory expansion is quadratic, so the per-KB rate climbs
|
|
598
|
+
with size. Across the 10–100 KB range that decides most projects it measures **~360,000–405,000 gas per
|
|
599
|
+
KB**, rising to ~460,000/KB at 187 KB and ~510,000/KB at 256 KB. `inline` and `reader` land within ~1% of
|
|
600
|
+
each other up to 75 KB (the cost is the renderer's string building, not the storage mechanism) and
|
|
601
|
+
diverge above it (~5% at 187 KB, ~10% at 256 KB, as the chunk store's quadratic read loop takes over), so
|
|
602
|
+
`--compress fastlz` makes a field cheaper to write and not one gas cheaper to read.
|
|
603
|
+
|
|
604
|
+
Measured with `forge` (`OneOfOneImage` + `AbxMetadataRenderer`, callee execution gas for one `tokenURI`
|
|
605
|
+
call, `reader` staging at 22,000-byte chunks):
|
|
606
|
+
|
|
607
|
+
| on-chain content | `tokenURI` gas | per KB | | on-chain content | `tokenURI` gas | per KB |
|
|
608
|
+
| ---------------- | -------------- | ------- | --- | ---------------- | -------------- | ------- |
|
|
609
|
+
| 3 KB | 1,123,327 | 374,000 | | 100 KB | 40,254,159 | 403,000 |
|
|
610
|
+
| 10 KB | 3,588,993 | 359,000 | | 128 KB | 53,559,735 | 418,000 |
|
|
611
|
+
| 23 KB | 8,346,220 | 363,000 | | 187 KB | 86,021,071 | 460,000 |
|
|
612
|
+
| 40 KB | 14,740,366 | 369,000 | | 256 KB | 131,269,134 | 513,000 |
|
|
613
|
+
| 50 KB | 18,759,333 | 375,000 | | | | |
|
|
614
|
+
| 75 KB | 29,137,215 | 388,000 | | | | |
|
|
615
|
+
| 90 KB | 35,868,124 | 399,000 | | | | |
|
|
616
|
+
|
|
617
|
+
geth's `--rpc.gascap` defaults to 50M, hosted providers commonly cap lower, and L1 block limits sit in
|
|
618
|
+
the mid-30-millions — so a project at the _documented_ ceiling could not be read by a normal node, and
|
|
619
|
+
from ~90 KB (~36M gas) up no contract can read it inside a transaction at all. Note what the 100 KB
|
|
620
|
+
refusal is: a **margin**, not the wall. 100 KB reads at ~40M, and geth's own 50M default is not
|
|
621
|
+
exhausted until ~120 KB; the refusal sits early because hosted providers cap well below geth.
|
|
622
|
+
|
|
623
|
+
The gate now sits on that axis, per token (each `tokenURI` assembles only its own content, so a
|
|
624
|
+
300-piece collection of 5 KB works is fine while one 256 KB work is not):
|
|
625
|
+
|
|
626
|
+
- **from ~40 KB — warn.** Reads need a deliberately high-gas RPC.
|
|
627
|
+
- **past ~100 KB — refuse**, with `--allow-unreadable-onchain` as the override. The refusal names the
|
|
628
|
+
three ways forward and says plainly what the override accepts: rendering needs a high-gas endpoint and
|
|
629
|
+
most marketplaces and indexers will show nothing. It fires on the **dry run** too, so a creator meets it
|
|
630
|
+
before spending gas rather than after the first staging transaction.
|
|
631
|
+
|
|
632
|
+
Live on every path that puts bytes on-chain: `deploy --onchain-image`, `deploy-series --onchain-image`,
|
|
633
|
+
both `--copies` edition twins, and `set-field --file`. New in the SDK: `classifyOnchainReadSize`,
|
|
634
|
+
`tokenUriGasEstimate` (fitted to the measurements, so it tracks the superlinearity instead of
|
|
635
|
+
multiplying by a flat rate), `TOKEN_URI_GAS_PER_KB_LOW` / `TOKEN_URI_GAS_PER_KB_HIGH` (the measured
|
|
636
|
+
10–100 KB envelope — there is deliberately no single flat `TOKEN_URI_GAS_PER_KB`, because there is no
|
|
637
|
+
single rate), `ONCHAIN_READ_WARN_BYTES`, `ONCHAIN_READ_REFUSE_BYTES`, `ONCHAIN_READ_GETH_CAP_BYTES`
|
|
638
|
+
(where geth's 50M default actually runs out, so the refusal can be described as the margin it is)
|
|
639
|
+
(`exceedsOnchainSoftLimit` stays, now documented as the _write_-cost predicate it always was). The
|
|
640
|
+
whole-collection 256 KB warning stays as well, relabelled as write cost only, since it never said
|
|
641
|
+
anything about readability.
|
|
642
|
+
|
|
643
|
+
Two things deliberately not done. **No on-chain guard and no gas check** — a read happens off-chain, a
|
|
644
|
+
read too large for one node is an RPC-capability problem, and bricking a contract is worse than needing a
|
|
645
|
+
capable endpoint. And the **code lane still only warns**, at the same 40 KB threshold and now quoting the
|
|
646
|
+
gas figure: `abx inspect`'s document size is an estimate that undercounts unknown dependencies, and a
|
|
647
|
+
registry-hosted library like p5 is legitimately ~200 KB, so the honest move there is to name the cost and
|
|
648
|
+
point at the generator's piecewise getters (`document`, `tokenDataJson`, `dependencyTag`, `abxJs`,
|
|
649
|
+
`gunzipScript`, `registryScriptChunk`) rather than block a shipped lane on a heuristic. Those getters are
|
|
650
|
+
now in the SDK's generator ABI, which claimed to carry "the piecewise reads" while omitting three of them.
|
|
651
|
+
|
|
652
|
+
The docs and the `abx-self-host` skill were swept in the same pass: `protocol/onchain-storage` carries the
|
|
653
|
+
measured table and the corrected envelope, `protocol/code-projects` gains a piecewise-read section, and
|
|
654
|
+
the skill's Quick start and `reference/decisions.md` lead with "40 KB is the number, not 256 KB" plus the
|
|
655
|
+
rule that the override is a creator's deliberate choice and never a way to clear a warning.
|
|
656
|
+
|
|
657
|
+
- 1b6b741: Round-3 audit remediation: link the two ERC-1155 image anchors, and stop guarding by name
|
|
658
|
+
|
|
659
|
+
`EditionImage` and `OneOfOneEdition` became library-linked in the previous round, but neither the
|
|
660
|
+
SDK nor the forge scripts were updated. `predictEditionFactory()` and
|
|
661
|
+
`predictOneOfOneEditionFactory()` hashed **unlinked** bytecode — and viem does not reject a
|
|
662
|
+
`__$…$__` placeholder, it UTF-8-encodes it, so both returned a confident hash of ASCII garbage for
|
|
663
|
+
a trust anchor, and `ops.ts` would have broadcast that bytecode as initcode. Both now link
|
|
664
|
+
`AbxEditionLib` (itself linked against `AbxParamsLib`) and ensure the libraries exist first.
|
|
665
|
+
|
|
666
|
+
Two addresses move as a result: `EditionImageFactory` and `OneOfOneEditionFactory`. Both are
|
|
667
|
+
pre-launch and pending the redeploy gate.
|
|
668
|
+
|
|
669
|
+
This was the third instance of one bug class, and the existing guard missed it because it checked
|
|
670
|
+
two factories **by name**. The replacement is exhaustive by construction: it reverse-looks-up which
|
|
671
|
+
initcode each `predict*` actually hashed, drives every `prepareDeploy*` and every `deploy*`
|
|
672
|
+
orchestrator against a stub chain, and asserts no `__$` placeholder survives anywhere — so the next
|
|
673
|
+
library-linked contract is covered without anyone remembering to add it.
|
|
674
|
+
|
|
675
|
+
Also: every SDK deploy now goes through CREATE2 at a canonical salt (the 1/1 and Series anchors were
|
|
676
|
+
still plain `CREATE`, landing at nonce-dependent addresses that could never match the manifest), and
|
|
677
|
+
the CLI no longer tells creators an ERC-1155 edition emits no ERC-4906 — both lanes emit it now.
|
|
678
|
+
|
|
679
|
+
- 1b6b741: Warn when an explicit `--salt` has an all-zero guard prefix
|
|
680
|
+
|
|
681
|
+
The factory reads a salt's leading 20 bytes as an access guard: the caller's own address means only
|
|
682
|
+
that signer may deploy with it, all-zero means anyone may. Omitting `--salt` gives you the pinned
|
|
683
|
+
form. But only `abx predict` ever explained this — the deploy commands took an explicit `--salt`
|
|
684
|
+
verbatim and said nothing.
|
|
685
|
+
|
|
686
|
+
So a creator who pinned a vanity salt with a zero prefix and announced the predicted address was
|
|
687
|
+
handing it to whoever deployed there first, and the front-runner ends up as `owner()`, because
|
|
688
|
+
ownership is set by `initialize` rather than by the prediction. The 2026-08 security review
|
|
689
|
+
reproduced that.
|
|
690
|
+
|
|
691
|
+
It warns rather than refuses: a zero prefix is the right choice for a deliberately shared,
|
|
692
|
+
caller-independent deployment. The check lives in the flag parser, which is the one place every
|
|
693
|
+
command passes through.
|
|
694
|
+
|
|
695
|
+
- 1b6b741: Say the honest thing about the mint seed: pseudorandom, not lottery-grade
|
|
696
|
+
|
|
697
|
+
The canonical `AbxSeedSource` stopped hashing the recipient (`to` is accepted and ignored) because a
|
|
698
|
+
buyer names the recipient in `purchaseTo`, which turned a caller-supplied field into a search space —
|
|
699
|
+
grind candidates in a view loop, buy once at the winner. Dropping it removes cheap _targeting_, and
|
|
700
|
+
nothing more: the seed is still derived entirely from on-chain values, so it is **deterministic after
|
|
701
|
+
the fact** (replayable from the block — the property that makes generative output verifiable) and
|
|
702
|
+
**not secret before it** (a contract minting in the same transaction can compute the seed it would
|
|
703
|
+
receive and revert unless it likes the result).
|
|
704
|
+
|
|
705
|
+
The docs and the toolkit now say that where a creator decides, instead of implying more than the
|
|
706
|
+
contract delivers:
|
|
707
|
+
|
|
708
|
+
- `deploy-code --help` spells out what `--no-seed` opts out of, and the deploy plan prints the same
|
|
709
|
+
note beside the `seed source` line it already showed — on the 721 and edition lanes both.
|
|
710
|
+
- The `abx-self-host` skill gained a one-paragraph router entry in **Code projects** and a
|
|
711
|
+
**Seeds — pseudorandom, not lottery-grade** section in `reference/code-projects.md`: fine where the
|
|
712
|
+
seed diversifies output and the distribution is the product, not fine for a raffle, a prize draw,
|
|
713
|
+
or any drop where one rare outcome is worth materially more than mint price. The escape hatch is
|
|
714
|
+
named rather than hinted at — the creator's own `IAbxSeedSource` over commit-reveal or a VRF oracle,
|
|
715
|
+
which is a per-project swap, not a fork of ABX (and is now reachable as `--seed-source` /
|
|
716
|
+
`abx set-seed-source` — see the companion entry).
|
|
717
|
+
- Both also document the seed's new governance rule: a `seed` param schema can only be declared
|
|
718
|
+
**before the collection's first seed exists**, so whether a seed's value may be re-set — and by
|
|
719
|
+
whom — is a pre-sale commitment a buyer reads with `paramSchema("seed")` (or `abx state`), not
|
|
720
|
+
something added after a sellout. That schema is deliberately **not** described as a "re-roll":
|
|
721
|
+
the governed write is `configureTokenParam(tokenId, "seed", value)` and the caller supplies the
|
|
722
|
+
value, so it is a chosen seed, not a fresh random draw.
|
|
723
|
+
|
|
724
|
+
The public docs (`site/content/docs`), the protocol specs, and `contracts/README.md` were swept in the
|
|
725
|
+
same pass; no claim that the seed is "unpredictable" survives anywhere.
|
|
726
|
+
|
|
727
|
+
- 1b6b741: Fix `abx skill install` and `abx scaffold-renderer` on a published npm install
|
|
728
|
+
|
|
729
|
+
Reported from the field on v0.1.0-alpha.21: `abx skill install` — the canonical way to install the
|
|
730
|
+
agent skill, and the first thing the quickstart tells you to run — failed on every fresh global npm
|
|
731
|
+
install with `✗ bundled skill not found`. The reporter's diagnosis was exactly right.
|
|
732
|
+
|
|
733
|
+
`resolveBundledSkill()` computed the package root as
|
|
734
|
+
`resolve(fileURLToPath(import.meta.url), '..', '..')`. That is correct for a module compiled to
|
|
735
|
+
`dist/main.js` and off by one for `dist/commands/scaffold.js`, which is a level deeper — so it landed
|
|
736
|
+
on `dist/` and looked for `<pkg>/dist/skill`. The assumption was written when the function lived one
|
|
737
|
+
level up and quietly broke when it moved into `commands/`.
|
|
738
|
+
|
|
739
|
+
Two more instances of the same root cause came out with it:
|
|
740
|
+
|
|
741
|
+
- **`abx scaffold-renderer` was broken the same way** and had not been reported — it looked for
|
|
742
|
+
`<pkg>/dist/assets/renderer-scaffold`.
|
|
743
|
+
- **The "am I running from source?" guard** matched `/src/<one-segment>$`, so it read false for
|
|
744
|
+
anything under `src/commands/`. On its own that was harmless; together with the root fix it would
|
|
745
|
+
have made a dev run prefer `<pkg>/skill` — gitignored prepack output — over the canonical skill,
|
|
746
|
+
which is how the canonical skill once got overwritten by a copy eight versions behind.
|
|
747
|
+
|
|
748
|
+
Nothing caught any of it because a dev run never has the published layout: running from source, both
|
|
749
|
+
resolvers fall back to the repo copies and work regardless of the arithmetic. So the fix is structural
|
|
750
|
+
rather than a third `..`: one `packageRootFrom()` that walks up to the CLI's own name-checked
|
|
751
|
+
`package.json`, depth-independent by construction, plus `packaging.test.ts` — which builds a published
|
|
752
|
+
layout in a temp dir and walks it from several depths, and fails the build if counted-`..` root math
|
|
753
|
+
comes back in any spelling.
|
|
754
|
+
|
|
755
|
+
Workaround on alpha.21, if you're on it:
|
|
756
|
+
`cp -r $(npm root -g)/@artblocks/abx-cli/skill ~/.claude/skills/abx-self-host`
|
|
757
|
+
|
|
758
|
+
- 133ecbc: Add `abx submit-app` — optional post-deploy listing in the ABX App Store.
|
|
759
|
+
|
|
760
|
+
Not folded into deploy. After a collection exists, the agent offers it, confirms catalog copy with the creator (name / summary / description — not the collection's NFT metadata), then mints through the gated minter and writes params in gas-capped batches. Already-claimed collections skip the mint (same as `/update`). Shipped Base Sepolia registry + minter; override with `--registry` / `--minter`.
|
|
761
|
+
|
|
762
|
+
- 1b6b741: The param transfer hook is a veto, not best-effort — say so everywhere, and ship the lock that answers it
|
|
763
|
+
|
|
764
|
+
The transfer hook used to swallow its own revert, and every surface promised that the parameter
|
|
765
|
+
lifecycle "must never block a transfer". That promise could not be kept, so it is withdrawn rather than
|
|
766
|
+
restated: Solady runs the ERC-721/1155 **receiver acceptance check after the hook**, so on the `safe*`
|
|
767
|
+
variants — which marketplace fills wrap — a hook that is cheap when a wallet estimates gas and expensive
|
|
768
|
+
when the transfer lands starves the work that comes after it, with or without a gas cap. Swallowing
|
|
769
|
+
bought a guarantee that was false in exactly the cases people use, while making an honest hook's failure
|
|
770
|
+
invisible.
|
|
771
|
+
|
|
772
|
+
So the contract now calls the hook plainly (no swallow, no gas budget, no assembly), **its revert fails
|
|
773
|
+
the transfer**, and — because a mint and a burn are transfers from/to `0x0` — a reverting hook also stops
|
|
774
|
+
minting for that project, including through the shared minter. That is deliberate: a hook is always the
|
|
775
|
+
creator's own contract, which is why it may block issuance while the transfer _validator_, usually a
|
|
776
|
+
third party's, never sees mint or burn.
|
|
777
|
+
|
|
778
|
+
The mitigation is a lock, and it is now reachable end to end:
|
|
779
|
+
|
|
780
|
+
- **SDK** — `prepareLockParamHooks({contract, chainId})`, the sibling of `prepareLockScript` /
|
|
781
|
+
`prepareLockDependencies`. Plus two reads: `readParamHooks` (the trio) and `readParamHooksLocked`,
|
|
782
|
+
which establishes the freeze without a getter by simulating `setParamHooks` with the current trio and
|
|
783
|
+
watching for `ParamHooksLocked` — and returns `undefined`, never a cheerful `false`, when a node won't
|
|
784
|
+
answer.
|
|
785
|
+
- **CLI** — `abx lock-param-hooks <address>`. Owner-only, one-way, every signing lane, `--dry-run`
|
|
786
|
+
guarded. It prints the exact three addresses it will freeze, states what is given up (arming a
|
|
787
|
+
transfer veto, arming a configure veto, re-pointing or clearing the augment hook) and that there is no
|
|
788
|
+
way back, and distinguishes the two cases: freezing an **empty** set is how a project _proves_ it can
|
|
789
|
+
never arm a transfer veto, while freezing a set that already contains a hook pins which contracts can
|
|
790
|
+
run — it does not disarm them.
|
|
791
|
+
- **`abx state`** now prints the three hooks and whether they are frozen, next to the transfer validator,
|
|
792
|
+
because that pair is the read a buyer performs. `abx verify` warns on an armed-but-unfrozen transfer
|
|
793
|
+
hook. `abx set-param-hooks` states the veto at the moment a transfer hook is armed.
|
|
794
|
+
- **Reconstruction** — `ParamHooksFrozen` folds into `ProjectState.paramHooks.locked`. The event is
|
|
795
|
+
declared only by `AbxParamsLib`, so `spineEventAbi` now unions that ABI (its other events dedup away):
|
|
796
|
+
without it the one-way lock on a hook that can veto a transfer was invisible to every consumer that
|
|
797
|
+
folds the spine.
|
|
798
|
+
|
|
799
|
+
Swept for the withdrawn promise: `protocol/params` (which gained a section on why it died),
|
|
800
|
+
`protocol/owner-powers` (the hook is now on **both** lists — an unlocked hook set as a live power, a
|
|
801
|
+
frozen one as a guarantee), `specs/protocol/event-spine`, `specs/protocol/user-stories`, the spine event
|
|
802
|
+
doc strings, and the `abx-self-host` skill (`SKILL.md`'s lock bullet plus the operating and code-project
|
|
803
|
+
references). No surface still says "best-effort", "reverts are swallowed", or "never blocks a transfer",
|
|
804
|
+
and a CLI test now fails if one comes back.
|
|
805
|
+
|
|
806
|
+
- 1b6b741: Transfer validator: preflight what the chain actually refuses, and stop calling `setTransferValidator` owner-only
|
|
807
|
+
|
|
808
|
+
Two corrections, both in the direction that was costing someone something.
|
|
809
|
+
|
|
810
|
+
**The preflight was a bare `getCode`, and the chain's check is not.** `validateTransfer` returns nothing,
|
|
811
|
+
so there is no ABI decode to fail — which means any address whose fallback succeeds for an unknown
|
|
812
|
+
selector passes a has-code check and then waves **every** transfer through, while ERC-165,
|
|
813
|
+
`getTransferValidator()` and the extension beacon all report enforcement as ON. A Safe does exactly this
|
|
814
|
+
(`FallbackManager.fallback()` returns empty when no handler is set), as do an uninitialised proxy and a
|
|
815
|
+
7702-delegated EOA, and a creator pasting their own Safe is the likely real case. The contract's guard was
|
|
816
|
+
rewritten to probe with a selector no validator implements and require it to **fail**; the toolkit now
|
|
817
|
+
asks the same question, with the same selector, through the new SDK `probeTransferValidator` (verdicts:
|
|
818
|
+
`ok` · `no-code` · `permissive-fallback` · `unreachable`). Both call sites use it — `abx
|
|
819
|
+
set-transfer-validator` and `deploy --721c` — so a creator gets a refusal that names what is actually
|
|
820
|
+
wrong instead of passing the CLI and reverting on chain against an error whose documented meaning ("no
|
|
821
|
+
code on this chain") was false for their address.
|
|
822
|
+
|
|
823
|
+
**`setTransferValidator` is owner-only only while there is an owner.** Once `owner() == address(0)`
|
|
824
|
+
**anyone** may suspend enforcement by passing `address(0)`, and nobody may ever arm a validator again —
|
|
825
|
+
the dead-man release for a validator that reverts every transfer on a collection with nobody left to
|
|
826
|
+
re-point it, which would otherwise strand every collector's token permanently. Four surfaces still said
|
|
827
|
+
owner-only, and the SDK's was wrong in the direction that matters: an integrator reading it would refuse
|
|
828
|
+
to build the suspension a stranded holder needs. Corrected in `creator-token.ts` (module header plus
|
|
829
|
+
`prepareSetTransferValidator`), `protocol/royalty-enforcement`, `protocol/interfaces`, and
|
|
830
|
+
`specs/protocol/interfaces`, each stating the asymmetry: an ownerless collection can only ever be moved
|
|
831
|
+
_toward_ transferability, so this hands a stranger no power over a live project.
|
|
832
|
+
|
|
833
|
+
- Updated dependencies [1b6b741]
|
|
834
|
+
- Updated dependencies [1b6b741]
|
|
835
|
+
- Updated dependencies [1b6b741]
|
|
836
|
+
- Updated dependencies [1b6b741]
|
|
837
|
+
- Updated dependencies [1b6b741]
|
|
838
|
+
- Updated dependencies [1b6b741]
|
|
839
|
+
- Updated dependencies [f64a31f]
|
|
840
|
+
- Updated dependencies [1b6b741]
|
|
841
|
+
- Updated dependencies [1b6b741]
|
|
842
|
+
- Updated dependencies [1b6b741]
|
|
843
|
+
- Updated dependencies [1b6b741]
|
|
844
|
+
- Updated dependencies [1b6b741]
|
|
845
|
+
- Updated dependencies [1b6b741]
|
|
846
|
+
- Updated dependencies [1b6b741]
|
|
847
|
+
- Updated dependencies [1b6b741]
|
|
848
|
+
- Updated dependencies [1b6b741]
|
|
849
|
+
- Updated dependencies [1b6b741]
|
|
850
|
+
- @artblocks/abx-sdk@0.1.0-alpha.14
|
|
851
|
+
- @artblocks/abx-token-api@0.1.0-alpha.17
|
|
852
|
+
- @artblocks/abx-indexer@0.1.0-alpha.15
|
|
853
|
+
- @artblocks/abx-storage@0.1.0-alpha.14
|
|
854
|
+
|
|
855
|
+
## 0.1.0-alpha.21
|
|
856
|
+
|
|
857
|
+
### Patch Changes
|
|
858
|
+
|
|
859
|
+
- 528c6c6: Close most of the ERC-1155 edition CLI parity gaps (B28), give every command one unknown-flag posture
|
|
860
|
+
(B30), and add the per-holder edition balance read (B31).
|
|
861
|
+
|
|
862
|
+
Each B28 item was checked against the tree before any code was written: **none needed a Solidity
|
|
863
|
+
change.** The contracts and SDK ops were already per-id and standard-neutral, so all of this is CLI
|
|
864
|
+
plumbing and **no redeploy, address churn, or Etherscan re-verification is involved.**
|
|
865
|
+
|
|
866
|
+
**Edition custody is now symmetric with the 721 side** — each lane verified live on testnet, not just
|
|
867
|
+
by dry run:
|
|
868
|
+
|
|
869
|
+
- **`deploy-series --copies` gains pattern 2: off-chain image, on-chain JSON, nothing to run.** The 721
|
|
870
|
+
Series had the whole branch; `EditionImage` never got it, so a multi-artwork **raster** edition had no
|
|
871
|
+
no-server option at all. A uniform file extension uploads the folder as ONE directory and bakes a
|
|
872
|
+
single `{id}`-substituting collection field, so the collection costs one field instead of N; mixed
|
|
873
|
+
extensions fall back to per-id `url` fields. Verified at
|
|
874
|
+
`0x4e7e2d1aA951d99046323a620a2a5891b1195452`, where `uri(0|1|2)` resolve to `…/0.png`, `…/1.png`,
|
|
875
|
+
`…/2.png` off one template and all three fetch at their source byte sizes. This gap had real design
|
|
876
|
+
consequences: a cold agent asked for "3 artworks × 25 copies each", hit the SVG-only refusal, and
|
|
877
|
+
split the collection into **three separate single-artwork contracts** to get the permanence it wanted.
|
|
878
|
+
- **`--onchain-image` works on both edition lanes, on the hot AND wallet lanes.** It was refused
|
|
879
|
+
outright on `deploy-series --copies` and hot-lane-only on `deploy --copies`; one sign session now
|
|
880
|
+
covers the chunk writes plus the deploy, and the staging txs are counted in the `approvals` line the
|
|
881
|
+
session's own total is derived from. Verified at `0x0A71382207a980c23E26a54F2b815072da41761A`:
|
|
882
|
+
`uri(id)` returns a `data:image/svg+xml;base64` image with zero `http(s)` anywhere in the document.
|
|
883
|
+
`EditionImage` also now treats `--onchain-image` as implying on-chain resolution — every sibling lane
|
|
884
|
+
already did, and without it the flag would have demanded a resolver URL for a drop whose bytes are
|
|
885
|
+
already on-chain.
|
|
886
|
+
- **`deploy-code --copies` accepts on-chain `--dep`/`--dep-registry`.** `EditionCode` already inherited
|
|
887
|
+
the `Dependencies` extension and called `_initDependencies()`; the legs come from the SDK's shared
|
|
888
|
+
`dependencySetupCalls`, so the two lanes cannot encode `setDependency` differently. So "an edition of
|
|
889
|
+
my p5 sketch, with p5 coming from the chain" is a real lane now — verified on Sepolia at
|
|
890
|
+
`0xC403CB9B956Ca814F1934d8d2A44fc1D2023EFB3`, where `abx verify` reports **chain-complete — every
|
|
891
|
+
dependency resolves to proven on-chain bytes; no server, gateway, or CDN in the graph**, with a 233 KB
|
|
892
|
+
inline `animation_url`.
|
|
893
|
+
- **The cold lane stays refused for `--onchain-image`**, deliberately and identically on all four paths:
|
|
894
|
+
staging is a sequence where each chunk tx's receipt feeds the next, so it cannot be signed offline.
|
|
895
|
+
This replaces an edition-specific "tracked as a follow-up" note with the real, shared reason.
|
|
896
|
+
|
|
897
|
+
Still open on the edition lanes, and recorded in B28: `deploy-code --copies` has no `--code-dir` or
|
|
898
|
+
field renderers, and `deploy-code --resume` has no edition path (that one is genuinely new per-id diff
|
|
899
|
+
logic, not a port).
|
|
900
|
+
|
|
901
|
+
**Every command now has one unknown-flag posture.** Roughly 35 commands — `state`, `add`, `index`,
|
|
902
|
+
`migrate`, `configure-param`, `set-field`, `set-royalty`, `set-minter`, `pause`/`unpause`, `verify`, … —
|
|
903
|
+
silently ignored an unrecognized flag, so `abx set-royalty --bsp 500` did something quietly different
|
|
904
|
+
from what was asked. Allowlists now live in one file and are applied once in the dispatcher; it warns
|
|
905
|
+
rather than refuses (a false refusal would break a working script, and `deploy*` still refuses via its
|
|
906
|
+
own exhaustive sets).
|
|
907
|
+
|
|
908
|
+
The obvious implementation — deriving each allowlist from the command's own `usage` string — was tried
|
|
909
|
+
and **rejected on evidence**: shared helpers read flags that appear in no usage string anywhere
|
|
910
|
+
(`storageOverrides()` reads twelve, pulled in by six commands; `--remote-token` by five), so deriving
|
|
911
|
+
would have warned on valid flags. That is worse than the silence it replaces, because a notice people
|
|
912
|
+
learn to distrust stops working for the real cases too. The sets are explicit instead, composed from
|
|
913
|
+
named groups, and a test reads each command's `--help` to assert every flag it **documents** is accepted
|
|
914
|
+
— the direction that would produce false warnings.
|
|
915
|
+
|
|
916
|
+
**`abx tokens <addr> --holder 0x..`** adds a `held` column — `balanceOf(holder, id)` per id, a live head
|
|
917
|
+
read. It answers the one question supply/cap cannot, and the one a funded agent had to leave blank after
|
|
918
|
+
transferring a copy (it correctly declined to fabricate the recipient's balance rather than guess).
|
|
919
|
+
Refused on a 721, where the owner column already answers it. Also: `deploy-series`' `[2] On-chain
|
|
920
|
+
renderer` step now prints the renderer address instead of an empty header, and `abx verify` no longer
|
|
921
|
+
ends in two ⚠ on a healthy fully-on-chain **image** drop (the code-project checks are scoped to the code
|
|
922
|
+
twins; a static drop has no program and no `animation_url` by design).
|
|
923
|
+
|
|
924
|
+
- 528c6c6: ERC-1155 editions: the trust-anchor and on-chain-URI checks told the truth about 721s only, and four
|
|
925
|
+
membrane gaps around them (2026-08-05 wave-4 agent sweep — 12 cold agents, one funded end-to-end run
|
|
926
|
+
on Base Sepolia).
|
|
927
|
+
|
|
928
|
+
The 1155 contracts themselves were fine: a funded run took an edition from deploy through
|
|
929
|
+
`tokenuri` → mint more copies → transfer one copy → lower the per-id cap, verifying every step
|
|
930
|
+
against the chain. What shipped wrong was everything that _describes_ an edition.
|
|
931
|
+
|
|
932
|
+
- **`abx verify` reported EVERY canonical edition as `canonical: NO`.** `detectCanonicalFactory`
|
|
933
|
+
probed only the three 721 anchors and, on no match, fell back to the 721 1/1 factory — whose
|
|
934
|
+
`isAbxClone` answers **false**, not "unknown". So the one signal platforms allowlist against was
|
|
935
|
+
confidently inverted for the entire edition line (the function's own docstring promised
|
|
936
|
+
"canonicity just shows unverified, never wrong"). All six anchors are now probed. **Editions
|
|
937
|
+
registered before this fix keep the wrong factory in the local projection** — `abx forget <addr>`
|
|
938
|
+
then `abx add <addr>` re-detects it.
|
|
939
|
+
- **`abx verify`'s on-chain-URI probe called the ERC-721 `tokenURI` selector on editions**, which
|
|
940
|
+
exposes `uri(id)`. It reverted for every edition, so verify could never confirm that a
|
|
941
|
+
fully-on-chain edition resolves — and it printed the raw multi-line viem dump (Contract Call /
|
|
942
|
+
args / Docs / Version) into a creator-facing readout. The probe now switches on `contractType`
|
|
943
|
+
(matching `reconstruct.ts`, which already did), reports which accessor it read so an edition is
|
|
944
|
+
never described in 721 terms, and readout errors are trimmed to their first line.
|
|
945
|
+
- **An address with no contract was reported as a specific type: `OneOfOneImage`.**
|
|
946
|
+
`detectTokenKind`'s three probes are each `try/catch → false`, so nothing-deployed and
|
|
947
|
+
no-extensions-composed were indistinguishable and the ladder fell through to its `1of1` default.
|
|
948
|
+
`abx set-max-supply` and `abx minter buy` then told edition owners _"…is a OneOfOneImage (721) —
|
|
949
|
+
drop your edition flags"_ for a mistyped address or, far more often, the wrong `ABX_CHAIN` (the
|
|
950
|
+
default is base-sepolia, so any Sepolia contract hit this immediately) — advice that removes the
|
|
951
|
+
_correct_ flags. The existence check now lives inside `detectTokenKind`, so all 13 call sites
|
|
952
|
+
inherit it; it fails **open** on an unreadable `getCode`, so an RPC blip never becomes a
|
|
953
|
+
"no contract" claim.
|
|
954
|
+
- **`abx transfer --token-id <n>` silently transferred id 0.** `transfer` spells the id `--token`
|
|
955
|
+
while every sibling command (`mint`, `set-max-supply`, `minter …`) spells it `--token-id`, and an
|
|
956
|
+
unrecognized flag was simply ignored — so an agent that learned the name from `mint` moved the
|
|
957
|
+
**wrong artwork** with no warning. `--token-id` is now an accepted alias; disagreeing values are
|
|
958
|
+
refused rather than silently preferring one.
|
|
959
|
+
- **The edition deploy previews dropped lines their 721 twins print.** All three edition lanes
|
|
960
|
+
omitted `approvals N wallet approval(s)` from the dry-run readout — which the skill promises "every
|
|
961
|
+
preview" prints and tells the agent to state up front, so on an edition the agent had nothing to
|
|
962
|
+
tell the creator about how many wallet prompts were coming (`deploy-code --copies` had it only in
|
|
963
|
+
the `--confirm` sentence, which is off by default). `deploy --copies` and `deploy-series --copies`
|
|
964
|
+
also omitted the "resolves ON-CHAIN via the renderer — no resolver, no server" line, which is
|
|
965
|
+
precisely the guarantee an edition creator is asking about, and `deploy-series --copies` never
|
|
966
|
+
showed `paused`. All six lanes now print the approval count; the 1155 paths say `uri()`.
|
|
967
|
+
- **Owner-op writes now surface a stray flag instead of swallowing it.** `mint`, `transfer`,
|
|
968
|
+
`set-max-supply`, and `minter configure|show|buy` warn on an unrecognized flag (warn, not refuse —
|
|
969
|
+
per `unknownFlags`' contract, a false warning must never break a script). These are the commands
|
|
970
|
+
where the 1155 semantics live in _optional_ flags that default rather than fail: a typo'd
|
|
971
|
+
`--amount 50` minted 1 copy and a typo'd `--quantity 5` bought 1 and paid 1×, both in total
|
|
972
|
+
silence. The broader gap — ~35 commands with no unknown-flag notice at all — is filed as B30.
|
|
973
|
+
|
|
974
|
+
A second verification sweep (six more cold agents, one funded) confirmed each fix above from a fresh
|
|
975
|
+
start and turned up four more, all fixed here:
|
|
976
|
+
|
|
977
|
+
- **A supply cap could be raised in `--dry-run` and only revert on send.** Both twins —
|
|
978
|
+
`set-max-invocations` (721) and `set-max-supply` (1155) — printed the new cap as fact for a value
|
|
979
|
+
the chain forbids, then reverted for real. A clean dry run is read as permission to send. Both now
|
|
980
|
+
read the current cap first and refuse up front; the 1155 side also refuses a cap **below** live
|
|
981
|
+
supply (the second way it reverted). Both guards fail **open** on an unreadable getter, since the
|
|
982
|
+
chain enforces the invariant anyway. (The 721 guard needed the Series ABI — reading `maxInvocations`
|
|
983
|
+
through the 1/1 ABI that `read()` uses throws, which would have made the guard silently never fire.)
|
|
984
|
+
- **`abx refresh` told edition owners ERC-4906 had already pinged marketplaces.** An ERC-1155 edition
|
|
985
|
+
emits no ERC-4906 at all — that is precisely why `ping-uri` exists — so the one sentence a creator
|
|
986
|
+
reads after editing metadata said "already handled" when nothing had been, and `ping-uri --help`
|
|
987
|
+
said the opposite. Two commands contradicting each other on the new lane. `refresh` is now
|
|
988
|
+
kind-aware and names `ping-uri` for editions.
|
|
989
|
+
- **`abx verify` ended in two ⚠ on a perfectly healthy fully-on-chain image drop** — "generator
|
|
990
|
+
reports NO code" and "no animation_url" — because the code-project lane runs whenever a tokenURI
|
|
991
|
+
renderer is set, which is true for every on-chain project, image or code. A static image has no
|
|
992
|
+
program and no `animation_url` by design. Three separate agents named this as the worst thing about
|
|
993
|
+
`verify`, and one said plainly it would make a creator distrust future real warnings. The code-lane
|
|
994
|
+
checks are now scoped to the code twins; a static drop gets one informational line instead. (This
|
|
995
|
+
was pre-existing on the 721 path too, not an editions regression.)
|
|
996
|
+
- **`EditionImage`'s SVG-only refusal was a thinner copy of its 721 twin's** — the Series version names
|
|
997
|
+
three routes (`--onchain-image`, `--backend ipfs|arweave`, or host off-chain); the edition version
|
|
998
|
+
said only "drop `--onchain-uri`". A cold agent asked for "3 artworks × 25 copies each" read that,
|
|
999
|
+
concluded there was no no-server option for one contract, and **fanned the collection out into three
|
|
1000
|
+
separate single-artwork contracts**. The refusal now states the real options for the lane and says
|
|
1001
|
+
explicitly that the off-chain-image-URL-in-on-chain-JSON route exists only per-artwork via
|
|
1002
|
+
`abx deploy --copies` — one contract per artwork, not one collection. The underlying capability gap
|
|
1003
|
+
is recorded in B28.
|
|
1004
|
+
|
|
1005
|
+
One knock-on from the first fix: the `false` branch of the canonical readout said _"not a clone of the
|
|
1006
|
+
**configured** factory"_ (singular), which now understates the check and misdirects the reader — six
|
|
1007
|
+
anchors are probed, so a `false` means no trust anchor the CLI knows deployed this contract, and the
|
|
1008
|
+
usual real cause is a **superseded** factory or a hand-deployed contract, not a misconfiguration. It
|
|
1009
|
+
now says so, and points at `abx doctor` for the current anchors.
|
|
1010
|
+
|
|
1011
|
+
Also: `deploy --help` no longer calls the edition sale stack (`--minter`/`--primary-payee`/
|
|
1012
|
+
`--unpaused`) "required" — it is optional, and an agent handed a creator hardcoded addresses as
|
|
1013
|
+
mandatory because of it; `set-field --help` now lists all eleven `--representation` values (it showed
|
|
1014
|
+
seven, omitting `url-template`, `renderer`, `sha256`, `inline-gzip`); and `abx doctor`'s label column
|
|
1015
|
+
widened so `edition factory` no longer runs into its address.
|
|
1016
|
+
|
|
1017
|
+
The skill-drift ✗ now names **which** copy is stale and prescribes the command that actually clears
|
|
1018
|
+
it — `abx skill install` writes the project-local copy only, so a stale **global** copy produced a ✗
|
|
1019
|
+
whose own remedy could not fix it. Six agents hit it in one sweep; several re-ran the install
|
|
1020
|
+
repeatedly and one left its sandbox to read `~/.claude/skills` to work out what the tool meant.
|
|
1021
|
+
|
|
1022
|
+
- 528c6c6: Wave-6 validation sweep: stop three help strings from denying features that work, make `storage show`
|
|
1023
|
+
honor the flags it advertises, and name the byte custody on both Series lanes.
|
|
1024
|
+
|
|
1025
|
+
Nine cold agents re-drove the edition surface after the B28/B30/B31 work. **Every new lane is reachable
|
|
1026
|
+
and no re-run regressed** — the situation that previously fanned "3 artworks × 25 copies" out into three
|
|
1027
|
+
separate contracts now reports _"Contracts deployed: 1, not 3"_; `--onchain-image` + edition + browser
|
|
1028
|
+
wallet went 4/5 at Haiku after being refused outright; the on-chain-dep edition lane went 3/5 → 4/5; the
|
|
1029
|
+
wrong-chain diagnosis went 2/5 → 3/5 on the strength of the new monotonic-cap guard. **B30 produced zero
|
|
1030
|
+
false warnings across all nine**, including a run that control-tested the mechanism with a deliberate
|
|
1031
|
+
bogus flag, and a funded run that exercised ~10 commands with real writes.
|
|
1032
|
+
|
|
1033
|
+
What the sweep found was mostly **documentation still describing behavior the same branch had replaced** —
|
|
1034
|
+
the failure mode a test suite cannot catch, because tests assert what the code does and none of them read
|
|
1035
|
+
the prose that tells an agent what to try:
|
|
1036
|
+
|
|
1037
|
+
- **`deploy --help`** said `--onchain-image` is "hot lane only on an edition (not yet wired for `--sign`)".
|
|
1038
|
+
It works on the wallet lane. Now states the real rule: hot and `--sign` both work; `--unsigned` is
|
|
1039
|
+
refused everywhere, 721 and edition alike, because each chunk tx's receipt feeds the next.
|
|
1040
|
+
- **`deploy-code --help`** listed `--dep`/`--dep-registry` among flags "refused in code" for `--copies`.
|
|
1041
|
+
They work. An agent verified all four flags directly and called this out as _"the dangerous direction:
|
|
1042
|
+
it would talk a creator out of a feature that actually works."_ Now says they work, and names the
|
|
1043
|
+
Sepolia-only registry constraint.
|
|
1044
|
+
- **The `EditionImage` SVG-only refusal** — a message rewritten earlier in the same branch — still said
|
|
1045
|
+
`--onchain-image` "is not wired on the edition-series lane yet", after that branch wired it. It now
|
|
1046
|
+
lists all four real routes for raster art in one contract.
|
|
1047
|
+
- **`abx refresh`'s ERC-4906 claim had no edition caveat** in the skill or `site/using-abx/guides/operate`,
|
|
1048
|
+
even though the CLI itself was made kind-aware earlier in the branch. The skill's _"`abx refresh` is the
|
|
1049
|
+
only nudge"_ was flatly false for an edition, where `ping-uri` is required.
|
|
1050
|
+
|
|
1051
|
+
Two real product fixes, both surfaced by the new warning doing its job:
|
|
1052
|
+
|
|
1053
|
+
- **`abx storage show` now honors `--backend`/`--gateway`/the rest of the storage override set.** It read
|
|
1054
|
+
env-only while its own output said _"choose per command: `--backend` … `--gateway`"_ — advertising flags
|
|
1055
|
+
it then discarded and always reporting `backend: fs`. This was invisible until every command grew an
|
|
1056
|
+
unknown-flag notice, which correctly flagged it (the flags really were ignored). A resolution preview
|
|
1057
|
+
that can't preview a resolution is the one thing it exists to do. Provenance now reports `--backend` as
|
|
1058
|
+
a source, alongside env and default.
|
|
1059
|
+
- **Both `deploy-series` lanes now name the byte custody** (`storage: ipfs (--backend) — byte custody for
|
|
1060
|
+
the image(s)`), as the 1/1 lane always has. A creator asking for "images on IPFS" had no way to confirm
|
|
1061
|
+
from the preview that their collection would pin there; the flag was accepted in silence.
|
|
1062
|
+
|
|
1063
|
+
Also documented, not changed: the ids × copies arithmetic. `deploy-code --copies 100` takes its id space
|
|
1064
|
+
from `--max`, which **defaults to 16** — so "an edition of 100" without `--max 1` means up to 16 different
|
|
1065
|
+
variations × 100 copies each. The dry-run states the real shape (`up to N id(s), M copies each`) and always
|
|
1066
|
+
did; nothing taught the translation, so the skill now does, for all three commands.
|
|
1067
|
+
|
|
1068
|
+
Three legibility nits were routed to B32 rather than fixed: `abx state` naming a contract "Series" where a
|
|
1069
|
+
refusal calls it "SeriesCode"; the Base-Sepolia default appearing only in a subordinate clause of the
|
|
1070
|
+
"no contract" error (three straight waves have lost time to it); and `--onchain-image` on a folder of large
|
|
1071
|
+
rasters honestly reporting 35 wallet approvals with no nudge toward the cheaper lane.
|
|
1072
|
+
|
|
1073
|
+
Two agent claims were checked and **rejected** so they don't get "fixed" into regressions: the skill does
|
|
1074
|
+
_not_ say `--onchain-image` is refused on an edition series (it documents the `--unsigned`-only refusal
|
|
1075
|
+
correctly), and `abx predict` does _not_ silently pick the 721 factory without `--copies` — it labels the
|
|
1076
|
+
lane, yields a different address, and echoes `--copies` in its reproduce line.
|
|
1077
|
+
|
|
1078
|
+
- Updated dependencies [528c6c6]
|
|
1079
|
+
- @artblocks/abx-sdk@0.1.0-alpha.13
|
|
1080
|
+
- @artblocks/abx-indexer@0.1.0-alpha.14
|
|
1081
|
+
- @artblocks/abx-storage@0.1.0-alpha.13
|
|
1082
|
+
- @artblocks/abx-token-api@0.1.0-alpha.16
|
|
1083
|
+
|
|
3
1084
|
## 0.1.0-alpha.20
|
|
4
1085
|
|
|
5
1086
|
### Minor Changes
|