@artblocks/abx-cli 0.1.0-alpha.27 → 0.1.0-alpha.29

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 CHANGED
@@ -1,5 +1,217 @@
1
1
  # @artblocks/abx-cli
2
2
 
3
+ ## 0.1.0-alpha.29
4
+
5
+ ### Minor Changes
6
+
7
+ - c9f7aeb: Fold the burn, and make "canonically ABX v2" sayable — from abx-services' 2026-08-20 reply
8
+ (`reviews/2026-08/services-reply-docs-23.md`). All off-chain: no contract changed, no address moved.
9
+
10
+ **SDK — `foldSpine` now folds an ERC-721 burn, and `TokenState.minted` is replaced by `lifecycle`
11
+ (BREAKING).** A burn wrote the zero address into `TokenState.owner` — where nothing downstream could
12
+ tell it from a holder — and `minted` latched `true` at mint and was never recomputed, so a burned
13
+ token reconstructed as live. `lifecycle: 'unminted' | 'live' | 'burned' | 'no-live-copies'` replaces it,
14
+ and `owner` is `null` for a burned id rather than a sentinel. An enum rather than the `minted` +
15
+ `burned` pair the report asked for: a boolean whose `false` has two meanings leaves the burn case one
16
+ forgotten field away from rendering as "not yet minted", which is exactly what our own CLI did.
17
+
18
+ **`'burned'` is terminal and ERC-721-only; an edition at zero live copies is `'no-live-copies'`.** The
19
+ first cut of this enum shared `'burned'` across both standards, on the grounds that "one vocabulary on
20
+ both lanes" was the honest shape. It wasn't: the correct _response_ to destruction differs by standard
21
+ (a 721 id is gone forever and must `410`; an edition id can mint again and must not), so a shared word
22
+ put every consumer one forgotten `contractType` branch away from serving `410 Gone` for a token the
23
+ contract still resolves — and our own migration note told the first consumer to map the shared word
24
+ straight onto their `410` sites. abx-services caught it in review before it published. Splitting the
25
+ word moves the rule out of prose and into the type: **`lifecycle === 'burned'` is safe to treat as
26
+ permanent on either standard, with no carve-out**, which let the reference resolver's `isEditionState`
27
+ guard go away entirely. Pinned from both ends — no edition history can fold to `'burned'`, and the
28
+ route asserts its own standard-blindness.
29
+
30
+ **The two lanes now agree id-for-id.** The same review found `TokenRow.lifecycle` (head reads) reporting
31
+ `'unknown'` for a fully-burned edition id while the fold reported `'burned'` for that same id — under a
32
+ docstring claiming the two lanes used the same words. That is the sibling-drift class this repo treats
33
+ as a bug (see `TokenState.maxSupply`'s note, the last time one field name carried two meanings). Both
34
+ lanes now answer `'no-live-copies'` there: the fold _could_ distinguish never-minted from fully-burned
35
+ and deliberately does not, because the distinction has no consumer. `'unknown'` survives for the one
36
+ case where a head read genuinely cannot say — a 1/1 has no mint frontier, so a reverting `ownerOf`
37
+ there is evidence of nothing.
38
+
39
+ What the latched boolean was costing, all in our own tree: the effects harness re-rendered destroyed
40
+ tokens on every sweep, forever; `onchain-uri`'s probe reads the LOWEST live id, so burning token 0 made
41
+ a healthy collection's on-chain-URI lane report as **broken**; the resolver served metadata — and the
42
+ pre-mint _warming placeholder_, i.e. "still loading" forever — for ids the contract disowns; `mintedCount`
43
+ never went down; and `abx state` printed "not yet minted" for a token that had been destroyed.
44
+
45
+ **SDK — `BurnConfigured` and `MaxRoyaltyBpsUpdated` fold into `ProjectState.burnable` /
46
+ `.maxRoyaltyBps`.** Both events shipped in the ABI and in `SPINE_EVENT_DOC` and reached no field of
47
+ state; the upgrade memo then told consumers the doc entry _was_ the fold, which it has never been (it
48
+ supplies `register`/`what` on the event record, full stop). Both are tri-state: `null` means the spine
49
+ never stated it — an implementation with no `burn` entrypoint and an unpublished ceiling — which is not
50
+ `false`, and not 10%. The ceiling is deliberately not nested inside `royalty`, since clearing a royalty
51
+ nulls that field while the ceiling stays binding on chain.
52
+
53
+ **SDK — a fold-coverage guard, because this was the second instance in four days.** `DefaultMaxSupplySet`
54
+ did the same thing on 2026-08-17. `SPINE_EVENT_NO_FOLD` now lists the events that deliberately reach no
55
+ state _and why_ (a ping whose value is a head read, a factory's own log, a minter sibling, an allowance),
56
+ and `spine-fold-coverage.test.ts` asserts every decodable event is either folded or excused. A new event
57
+ is unfolded and unexcused until someone decides which.
58
+
59
+ **SDK — a burned 1/1 no longer vanishes from `listTokens`.** The id range fell back to `totalSupply`
60
+ (live: mints − burns) when a token type exposes no `nextTokenId`, so burning a 1/1's only token made the
61
+ listing enumerate ZERO ids — `abx tokens` printed nothing at all, indistinguishable from a collection
62
+ with no tokens. A 1/1's id space is `{0}` forever regardless of what is live. Found by burning a real
63
+ token on Sepolia rather than by any fixture, which is the only way this one surfaces.
64
+
65
+ **SDK — head reads can now say `burned`.** `nextTokenId` is a mint frontier that only rises, so an id
66
+ below it whose `ownerOf` reverts was minted and destroyed. `TokenRow.lifecycle` (`'live'` / `'burned'` /
67
+ `'unminted'` / `'unknown'`) and `TokenListing.burnedCount` (`nextTokenId − totalSupply`) drop out of that,
68
+ with no event log. `'unknown'` stays a real member where the chain declines to say: a 1/1 has no frontier,
69
+ and an edition's `supply == 0` cannot distinguish never-minted from fully-burned.
70
+
71
+ **SDK — `burned` joins `ServiceErrorCode`, paired with `410 Gone`.** A remote token API had nothing
72
+ honest to say about a destroyed id and was sending `410` with `code: 'not_registered'` — a statement
73
+ about the _contract_. The rule behind the pairing is the general one: **a resolver answers what the
74
+ contract's own URI getter answers.** A burned 721's `tokenURI` reverts `NonexistentToken`, so serving a
75
+ document would contradict the contract; an ERC-1155's `uri(id)` has no existence gate and a zero-supply
76
+ id can mint again, so **an edition never 410s** — it serves, with `supply: 0`.
77
+
78
+ **SDK — `readCollectionPolicy`, `canonicalFactories`, `verifyProvenance`: protocol knowledge moves out
79
+ of the CLI.** The CLI was declaring its own ABI fragments for `burnable`/`maxRoyaltyBps` and reading
80
+ them itself in two places, so no other integrator could reach either fact. `canonicalFactories(chainId)`
81
+ replaces two copies of the six-anchor enumeration inside `anchors.ts` — the list whose completeness _is_
82
+ the trust model. `verifyCanonical` gains `opts.factories`, which **replaces** the manifest set rather
83
+ than appending to it, so a multi-tenant operator's pinned allowlist can drive the gate without silently
84
+ widening it.
85
+
86
+ **SDK — anchor generations: "canonically ABX v2", not a bare `false`.** `verifyCanonical` reported
87
+ "deployed by an ABX factory since replaced" and "deployed outside the toolkit entirely" identically —
88
+ its own docstring admitted it — and the first is a perfectly good collection. `ANCHOR_GENERATIONS` keys
89
+ each generation of the six trust anchors by the **on-chain core version** its clones report, and
90
+ `verifyProvenance` returns `{canonical, generation: 'current' | 'prior' | null, coreVersion, factory,
91
+ anchorsAnswered}`. The version is on the clone itself, so the answer is chain-verified rather than a
92
+ manifest claim, and it keeps working after a factory retires. Retired generations never feed
93
+ `verifyCanonical`: provenance is not trust — a superseded generation can predate a security remediation.
94
+ Pre-launch testnet generations are deliberately not backfilled.
95
+
96
+ **Redeploy process — a token-layer runtime change is a new anchor generation.** It bumps
97
+ `AbxVersion.CORE_VERSION` and moves the SDK's `isCurrent*` probes in lockstep. That is now a checklist
98
+ step (`contracts/README.md`, `CLAUDE.md`) _enforced by_ `deployments.test.ts`: recorded generations must
99
+ have unique, increasing core versions and the newest must match the constant in `AbxVersion.sol`, so a
100
+ batch cannot record a generation without bumping or bump without recording. The gap that prompted it: the
101
+ 2026-08-20 batch added `burn()`, `burnable()`, `maxRoyaltyBps()` and `reduceMaxRoyaltyBps()` to the CORE
102
+ base — burn is not an extension, so no extension version moved either — and left the constant at 2, so
103
+ `abxVersion() == 2` cannot tell a burn-capable token from a pre-burn one.
104
+
105
+ **CLI — the royalty ceiling is shown with its rate, and headroom is named.** A ceiling above the live
106
+ rate is royalty the owner can add unilaterally, and a listing page never shows it. `abx state` prints the
107
+ pair and, when there is headroom, says the useful thing: reducing the cap **to** the current rate is what
108
+ turns "5% today" into "5%, provably, forever". (Named by abx-services as their `royalty-headroom` flag —
109
+ a marketplace can compute it, so a creator should hear it from us first.)
110
+
111
+ **Reference stack — the same fixes, so the reference is exemplary rather than the counter-example.**
112
+ `mintedCount` keeps its name and counts LIVE ids, with `burnedCount` added only when non-zero (so a
113
+ project with no burns serves byte-identical JSON, and a consumer's fixtures keep telling the truth);
114
+ the indexer projection carries `lifecycle`; the effects status route and the render sweep skip destroyed
115
+ ids; the dashboard badges a burned token instead of calling it minted; and `abx verify` / the migrate and
116
+ re-point probes never choose a burned token as their subject.
117
+
118
+ **CLI — `--send` is accepted on the deploy paths, and two lane flags is refused.** Every deploy's help
119
+ advertises the signing lanes as `--send hot/env key · --sign wallet page · --unsigned print tx`, in 25
120
+ places, and `--send` was the one of the three the deploy allowlist rejected: typing what the help showed
121
+ produced `unrecognized flag(s): --send` from the guard whose whole purpose is catching flags that would
122
+ be silently ignored — the guard firing on the tool's own documentation. Found by using the CLI as a cold
123
+ reader of its own `--help` while setting up an on-chain test. While there, lane resolution moved from
124
+ truthiness to **presence** (`--sign=` parses to `''`, which is falsy, so a caller who explicitly named
125
+ the wallet lane was routed to the unattended env key — the one direction of that bug that costs
126
+ something), and naming two lanes at once now refuses instead of resolving by an undocumented precedence.
127
+
128
+ **Packaging — every published tarball ships its CHANGELOG.** `files` was `['dist', 'LICENSE']` on the
129
+ SDK, indexer, storage, effects and token-api, so the only package whose release notes reached consumers
130
+ was the CLI. A downstream team upgrading the SDK had no CHANGELOG in the tarball and diffed two `dist/`
131
+ trees to find out what moved — then reported the release notes as missing, which cost a whole exchange
132
+ to adjudicate. One line each.
133
+
134
+ **Docs + spec.** The event spine gains a Burn section and the `MaxRoyaltyBpsUpdated` row, both with the
135
+ fold rules an indexer relies on; three stale deploy-event-order lists are corrected; the remote-services
136
+ error taxonomy gains the `410 burned` row and the edition carve-out; `owner-powers` explains headroom and
137
+ burn permanence; and `indexing-notes` states plainly that documenting an event is not folding it.
138
+
139
+ ### Patch Changes
140
+
141
+ - Updated dependencies [c9f7aeb]
142
+ - @artblocks/abx-sdk@0.1.0-alpha.21
143
+ - @artblocks/abx-storage@0.1.0-alpha.21
144
+ - @artblocks/abx-indexer@0.1.0-alpha.22
145
+ - @artblocks/abx-token-api@0.1.0-alpha.24
146
+
147
+ ## 0.1.0-alpha.28
148
+
149
+ ### Patch Changes
150
+
151
+ - 84ca9dd: Off-chain fixes from the 2026-08-19 production-readiness audit
152
+ (`reviews/2026-08/production-readiness-audit.md`). All are off-chain — no contract changed and no
153
+ address moved; the three NatSpec-precision items the audit also raised ride the next redeploy batch
154
+ instead (`docs/10-backlog.md` → _Contracts — the next redeploy batch_, B47).
155
+
156
+ **SDK — `HexColor` decode now masks to 24 bits, matching the chain (L-1).** `decodeScalarParam` for a
157
+ `HexColor` value greater than `0xFFFFFF` returned `#1ffffff` (7+ hex digits); the on-chain
158
+ `TokenDataLib.decodeScalar` masks with `& 0xffffff` and returns `#ffffff`. The governed write validates
159
+ `v <= 0xFFFFFF`, but the raw owner setter does not, so an out-of-domain value can be stored and later
160
+ governed by a `HexColor` schema — and the two serving planes would then decode it differently, splitting
161
+ `inputsHash`. The SDK now masks identically. Pinned on both sides so it cannot drift again:
162
+ `decodeScalarParam('HexColor', 0x1ffffffn) === '#ffffff'` (SDK) and the twin `test_DecodeScalars`
163
+ assertion in `contracts/test/FieldRenderer.t.sol`.
164
+
165
+ **SDK — gateway-prefix projection no longer trims (L-7).** `projectGatewayPrefix` dropped the `.trim()`
166
+ on the on-chain `abx_gateway_*` value: the deployed `_gatewayPrefix` returns the stored bytes verbatim,
167
+ so trimming here made the two planes disagree on a whitespace-padded prefix. The field store rejects an
168
+ empty value, so a set field is always non-empty. Clean prefixes (every real one) are unaffected.
169
+
170
+ **SDK — `permissionlessSalt` docstring hardened (L-3).** Now states explicitly: never use it for a
171
+ pre-published or pre-funded address — a third party can occupy the predicted address first with their own
172
+ `InitParams`. For any reserved address, use `saltFor` (its deployer-bound prefix is front-run-proof). No
173
+ behavior change; the shipped CLI/skill already deploy through `saltFor`.
174
+
175
+ **CLI — the edition schema advisory calls out a holder-writable `seed` by name (L-2).**
176
+ `editionSchemaAdvisory` already warned that a `TokenOwner` param on an edition is shared per-id
177
+ (last-writer-wins); it now adds a sharper note when that param is `seed`, because the normally-immutable
178
+ generative seed becomes re-rollable by _any_ holder of the id — changing the artwork for every
179
+ co-holder. The on-chain behavior is deliberately unchanged (the reassignable-seed feature is
180
+ buyer-verifiable via `paramSchema("seed")`); the guardrail is this warning, not a contract restriction.
181
+ A controller contract on the `Address` leg remains the way to govern it.
182
+
183
+ - 84ca9dd: Finish the burn + royalty-cap surfacing, from the 2026-08-20 w6 agent sandbox sweep. All off-chain —
184
+ no contract changed, no address moved; the on-chain features were verified working end to end (a cold
185
+ agent deployed `--burnable --royalty-cap 42`, minted, burned, and proved the cap enforced on Sepolia).
186
+ The sweep's convergent finding was that the two new **permanent** deploy-time decisions were invisible
187
+ after you set them; this makes them visible everywhere they're decided or read, and adds the owner's
188
+ missing reduce lever.
189
+
190
+ - **New: `abx set-royalty-cap <address> --cap <0-10000>`** — the owner's reduce-only royalty-ceiling
191
+ lever (`RoyaltyExtension.reduceMaxRoyaltyBps`), the "after" the deploy-time "reduce-only after"
192
+ promise never had. Both ways the chain refuses are checked locally before signing: a value that
193
+ wouldn't decrease (`RoyaltyCapNotReduced`) or that would drop below the live royalty rate
194
+ (`RoyaltyCapBelowRoyalty`). SDK: `prepareReduceMaxRoyaltyBps`.
195
+ - **The deploy readout now shows `royalty cap` and `burnable` on every lane** (1/1, series, code, and
196
+ all three edition twins), whenever set. Critically it flags an **auto-raised** cap
197
+ (`royalty cap 25% (auto-raised to fit the royalty)`): omitting `--royalty-cap` while setting a
198
+ royalty above 10% silently raises the permanent ceiling to match, which was previously invisible.
199
+ - **`abx state` now surfaces `royaltyCap` and `burnable`** (human + `--json`), the post-deploy
200
+ verification surface, for both the 721 and edition readouts.
201
+ - **The skill documents the royalty cap** — the identity decision (`decisions.md`), the confirm-
202
+ identity step (`SKILL.md`), and a new `set-royalty-cap` owner-op row (`operating.md`); previously the
203
+ reduce-only ceiling feature had zero skill coverage.
204
+ - **Help/doc corrections:** `set-royalty --help` range `0-1000`→`0-10000` (stale from the hard-cap
205
+ era); `deploy-series --help` now lists `--royalty-cap`/`--burnable`; `deploy --help` states the cap
206
+ auto-raises; `abx inspect`'s trait-feasibility deep-dive points at public docs, not a repo path.
207
+
208
+ - Updated dependencies [84ca9dd]
209
+ - Updated dependencies [84ca9dd]
210
+ - @artblocks/abx-sdk@0.1.0-alpha.20
211
+ - @artblocks/abx-indexer@0.1.0-alpha.21
212
+ - @artblocks/abx-storage@0.1.0-alpha.20
213
+ - @artblocks/abx-token-api@0.1.0-alpha.23
214
+
3
215
  ## 0.1.0-alpha.27
4
216
 
5
217
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"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"}
@@ -188,6 +188,8 @@ export function deployCommandLine(flags, salt) {
188
188
  bool('onchain-image');
189
189
  str('compress', flags.compress);
190
190
  str('royalty-bps', flags['royalty-bps']);
191
+ str('royalty-cap', flags['royalty-cap']);
192
+ bool('burnable');
191
193
  bool('no-mint');
192
194
  str('721c', flags['721c'] === 'true' ? 'recommended' : flags['721c']); // bare --721c ≡ recommended
193
195
  str('backend', flags.backend);
@@ -237,6 +239,8 @@ export function deploySeriesCommandLine(flags, salt) {
237
239
  str('minter', flags.minter);
238
240
  str('primary-payee', flags['primary-payee']);
239
241
  str('royalty-bps', flags['royalty-bps']);
242
+ str('royalty-cap', flags['royalty-cap']);
243
+ bool('burnable');
240
244
  str('721c', flags['721c'] === 'true' ? 'recommended' : flags['721c']); // bare --721c ≡ recommended
241
245
  if (flags.sign !== undefined)
242
246
  parts.push('--sign');
@@ -279,6 +283,8 @@ export function deployCodeCommandLine(flags, salt) {
279
283
  str('minter', flags.minter);
280
284
  str('primary-payee', flags['primary-payee']);
281
285
  str('royalty-bps', flags['royalty-bps']);
286
+ str('royalty-cap', flags['royalty-cap']);
287
+ bool('burnable');
282
288
  str('721c', flags['721c'] === 'true' ? 'recommended' : flags['721c']); // bare --721c ≡ recommended
283
289
  str('backend', flags.backend);
284
290
  str('gateway', flags.gateway);
@@ -638,6 +644,15 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
638
644
  // 1500, printed "royalty 15%" in the confirmation readout, and then lost the whole deploy to a
639
645
  // RoyaltyTooHigh() revert — the expensive tx, and one the creator had already approved.
640
646
  const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
647
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
648
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
649
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
650
+ ? Math.max(1000, royaltyBps)
651
+ : parseRoyaltyBps(String(flags['royalty-cap']));
652
+ if (royaltyBps > maxRoyaltyBps) {
653
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
654
+ }
655
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
641
656
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
642
657
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
643
658
  // Creator traits (real OpenSea attributes). Off-chain operator metadata by default (cheap,
@@ -952,6 +967,8 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
952
967
  contractURIRenderer: renderer,
953
968
  royaltyReceiver: deployer,
954
969
  royaltyBps,
970
+ maxRoyaltyBps,
971
+ burnable,
955
972
  transferValidator,
956
973
  tokenFields,
957
974
  // authorship + rights (creator / license / …) are collection-scope on-chain fields.
@@ -991,6 +1008,8 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
991
1008
  authorshipReadout(flags).forEach((line) => info(line));
992
1009
  info(describeTraits(traits, traitsOnchain));
993
1010
  info(`royalty ${royaltyBps / 100}% → ${deployer}${flags['royalty-bps'] ? '' : dim(' (default 5%)')}`);
1011
+ info(`royalty cap ${maxRoyaltyBps / 100}%${flags['royalty-cap'] !== undefined ? '' : maxRoyaltyBps > 1000 ? dim(' (auto-raised to fit the royalty)') : dim(' (default 10%)')} ${dim('— reduce-only ceiling; lower later with `abx set-royalty-cap`')}`);
1012
+ info(`burnable ${burnable ? `yes ${dim('— holders may burn their own token')}` : `no ${dim('(no token can be destroyed)')}`}`);
994
1013
  info(contentNote);
995
1014
  info(`tokenURI base ${params.tokenURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
996
1015
  // Same reasoning as above: this line derives token #0's URL from `predicted`, so it's only an
@@ -1281,6 +1300,15 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
1281
1300
  const name = flags.name ?? 'ABX Edition';
1282
1301
  const symbol = flags.symbol ?? 'ABXE';
1283
1302
  const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
1303
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
1304
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
1305
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
1306
+ ? Math.max(1000, royaltyBps)
1307
+ : parseRoyaltyBps(String(flags['royalty-cap']));
1308
+ if (royaltyBps > maxRoyaltyBps) {
1309
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
1310
+ }
1311
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
1284
1312
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
1285
1313
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
1286
1314
  const traits = parseDeployTraits(flags);
@@ -1446,6 +1474,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
1446
1474
  contractURIRenderer: renderer,
1447
1475
  royaltyReceiver: deployer,
1448
1476
  royaltyBps,
1477
+ maxRoyaltyBps,
1478
+ burnable,
1449
1479
  transferValidator,
1450
1480
  editionSize,
1451
1481
  primaryPayee,
@@ -1484,6 +1514,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
1484
1514
  authorshipReadout(flags).forEach((line) => info(line));
1485
1515
  info(describeTraits(traits, traitsOnchain));
1486
1516
  info(`royalty ${royaltyBps / 100}% → ${deployer}${flags['royalty-bps'] ? '' : dim(' (default 5%)')}`);
1517
+ info(`royalty cap ${maxRoyaltyBps / 100}%${flags['royalty-cap'] !== undefined ? '' : maxRoyaltyBps > 1000 ? dim(' (auto-raised to fit the royalty)') : dim(' (default 10%)')} ${dim('— reduce-only ceiling; lower later with `abx set-royalty-cap`')}`);
1518
+ info(`burnable ${burnable ? `yes ${dim('— holders may burn their own token')}` : `no ${dim('(no token can be destroyed)')}`}`);
1487
1519
  info(contentNote);
1488
1520
  info(`tokenURI base ${params.tokenURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
1489
1521
  info(`contractURI base ${params.contractURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
@@ -1642,6 +1674,15 @@ export async function cmdDeploySeriesBody(flags, emit) {
1642
1674
  const name = flags.name ?? 'ABX Series';
1643
1675
  const symbol = flags.symbol ?? 'ABXS';
1644
1676
  const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
1677
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
1678
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
1679
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
1680
+ ? Math.max(1000, royaltyBps)
1681
+ : parseRoyaltyBps(String(flags['royalty-cap']));
1682
+ if (royaltyBps > maxRoyaltyBps) {
1683
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
1684
+ }
1685
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
1645
1686
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
1646
1687
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
1647
1688
  console.log(bold(`\n ABX Self-Host Toolkit — deploy series\n ${dim('a multi-token drop, served from chain alone')}`));
@@ -1948,6 +1989,8 @@ export async function cmdDeploySeriesBody(flags, emit) {
1948
1989
  contractURIRenderer: renderer,
1949
1990
  royaltyReceiver: deployer,
1950
1991
  royaltyBps,
1992
+ maxRoyaltyBps,
1993
+ burnable,
1951
1994
  transferValidator,
1952
1995
  maxInvocations: count,
1953
1996
  primaryPayee,
@@ -1983,7 +2026,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
1983
2026
  // readout for the no-salt case).
1984
2027
  if (explicitSalt)
1985
2028
  info(`deterministic address: ${predicted}`);
1986
- info(`name "${name}" · symbol ${symbol} · size ${count} · royalty ${royaltyBps / 100}%`);
2029
+ info(`name "${name}" · symbol ${symbol} · size ${count} · royalty ${royaltyBps / 100}% · cap ${maxRoyaltyBps / 100}%${burnable ? ' · burnable' : ''}`);
1987
2030
  authorshipReadout(flags).forEach((line) => info(line));
1988
2031
  info(`mint: ${mintCount > 0 ? `${mintCount} token(s) in order → ${deployer} at deploy` : 'deferred (mint later / external minter)'}`);
1989
2032
  if (minter !== zeroAddress)
@@ -2203,6 +2246,15 @@ export async function cmdDeployEditionImageBody(flags, emit) {
2203
2246
  const name = flags.name ?? 'ABX Edition Series';
2204
2247
  const symbol = flags.symbol ?? 'ABXES';
2205
2248
  const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
2249
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
2250
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
2251
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
2252
+ ? Math.max(1000, royaltyBps)
2253
+ : parseRoyaltyBps(String(flags['royalty-cap']));
2254
+ if (royaltyBps > maxRoyaltyBps) {
2255
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
2256
+ }
2257
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
2206
2258
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
2207
2259
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
2208
2260
  console.log(bold(`\n ABX Self-Host Toolkit — deploy edition series\n ${dim(editionSize === 0n
@@ -2489,6 +2541,8 @@ export async function cmdDeployEditionImageBody(flags, emit) {
2489
2541
  contractURIRenderer: renderer,
2490
2542
  royaltyReceiver: deployer,
2491
2543
  royaltyBps,
2544
+ maxRoyaltyBps,
2545
+ burnable,
2492
2546
  transferValidator,
2493
2547
  maxInvocations: count,
2494
2548
  editionSize,
@@ -2529,6 +2583,8 @@ export async function cmdDeployEditionImageBody(flags, emit) {
2529
2583
  info(`ids ${count} (id-space cap)`);
2530
2584
  info(`copies ${editionSize === 0n ? 'open (uncapped) per id' : `${editionSize} per id`}`);
2531
2585
  info(`royalty ${royaltyBps / 100}% → ${deployer}${flags['royalty-bps'] ? '' : dim(' (default 5%)')}`);
2586
+ info(`royalty cap ${maxRoyaltyBps / 100}%${flags['royalty-cap'] !== undefined ? '' : maxRoyaltyBps > 1000 ? dim(' (auto-raised to fit the royalty)') : dim(' (default 10%)')} ${dim('— reduce-only ceiling; lower later with `abx set-royalty-cap`')}`);
2587
+ info(`burnable ${burnable ? `yes ${dim('— holders may burn their own token')}` : `no ${dim('(no token can be destroyed)')}`}`);
2532
2588
  info(`tokenURI base ${params.tokenURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
2533
2589
  info(`mint: ${effectiveMintCount > 0 ? `${effectiveMintCount} id(s) × ${mintAmount} cop${mintAmount === 1n ? 'y' : 'ies'} → ${deployer} at deploy` : 'deferred'}`);
2534
2590
  if (minter !== zeroAddress)
@@ -2720,7 +2776,7 @@ export function projectionFingerprint(s) {
2720
2776
  royalty: s.royalty ? { bps: s.royalty.bps, receiver: s.royalty.receiver.toLowerCase() } : null,
2721
2777
  extensions: s.extensions.map((e) => e.name).sort(),
2722
2778
  collectionFields: s.collectionFields.map((f) => `${f.field}=${f.value}`).sort(),
2723
- tokens: s.tokens.map((t) => ({ id: t.tokenId, minted: t.minted, owner: t.owner?.toLowerCase() ?? null })),
2779
+ tokens: s.tokens.map((t) => ({ id: t.tokenId, lifecycle: t.lifecycle, owner: t.owner?.toLowerCase() ?? null })),
2724
2780
  events: s.events.map((e) => `${e.blockNumber}:${e.logIndex}:${e.name}`),
2725
2781
  };
2726
2782
  return createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
@@ -2788,8 +2844,8 @@ export async function walkthroughRebuild(indexer, address, before) {
2788
2844
  export async function walkthroughReadBack(state, baseUrl, onChainUri) {
2789
2845
  step('Read it back the way a marketplace would');
2790
2846
  const token = state.tokens[0];
2791
- if (!token?.minted) {
2792
- info('no minted token to read yet.');
2847
+ if (token?.lifecycle !== 'live') {
2848
+ info(token?.lifecycle === 'burned' ? 'token #0 was burned — nothing to read back.' : 'no minted token to read yet.');
2793
2849
  return;
2794
2850
  }
2795
2851
  if (onChainUri) {
@@ -2849,15 +2905,15 @@ export const DEPLOY_CODE_FLAGS = new Set([
2849
2905
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag), // creator · display-notes · creator-links · license
2850
2906
  'image-base', 'attributes-renderer', 'image-renderer',
2851
2907
  // supply + mint + economics
2852
- 'max', 'mint-count', 'mint-all', 'no-mint', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', '721c',
2908
+ 'max', 'mint-count', 'mint-all', 'no-mint', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', 'royalty-cap', 'burnable', '721c',
2853
2909
  // params + dependencies
2854
2910
  'schema', 'no-seed', 'seed-source', 'no-delegation', 'dep', 'dep-registry',
2855
2911
  // serving lane
2856
2912
  'public-base-url', 'onchain-uri', 'generator', 'renderer', 'port',
2857
2913
  // storage (directory mode) — mirrors storageOverrides()
2858
2914
  'backend', 'endpoint', 'bucket', 'region', 'prefix', 'public-base', 'gateway', 'mode', 'api-url', 'upload-url', 'provider', 'storage-signer',
2859
- // signing lane + safety + trust anchor
2860
- 'sign', 'unsigned', 'for', 'salt', 'factory', 'bootstrap-factory', 'sign-url-file',
2915
+ // signing lane + safety + trust anchor ('send' = the default lane, stated explicitly; see SHARED_DEPLOY_FLAGS)
2916
+ 'send', 'sign', 'unsigned', 'for', 'salt', 'factory', 'bootstrap-factory', 'sign-url-file',
2861
2917
  // preview / confirm
2862
2918
  'dry-run', 'confirm', 'yes',
2863
2919
  'json', // B19: the deployed ADDRESS as data, narration to stderr
@@ -2871,6 +2927,13 @@ export const SHARED_DEPLOY_FLAGS = [
2871
2927
  // which is where the CLI uploads and probes; `--gateway` seeds these for the backend in use, and
2872
2928
  // these override it when the two should differ. Every deploy path takes them.
2873
2929
  'ipfs-gateway', 'arweave-gateway',
2930
+ // `--send` is the DEFAULT lane, and it is accepted explicitly because every deploy's own help
2931
+ // advertises it by name ("signing: --send hot/env key · --sign wallet page · --unsigned print tx").
2932
+ // It was the one of the three the parser rejected, so a reader who typed what the help showed got
2933
+ // `unrecognized flag(s): --send` from the guard whose whole job is catching flags that would be
2934
+ // ignored — the guard firing on the tool's own documentation. Explicit is also better than bare for
2935
+ // a script or an agent: the lane that signs and sends is worth stating out loud.
2936
+ 'send',
2874
2937
  'sign', 'unsigned', 'for', 'sign-url-file', 'dry-run', 'confirm', 'yes', 'salt', 'factory', 'bootstrap-factory', 'port', 'renderer', 'public-base-url',
2875
2938
  '721c', // opt-in ERC-721C enrollment (recommended | 0x…) — every deploy path takes it
2876
2939
  'json', // B19: the deployed ADDRESS as data, narration to stderr — every deploy path takes it
@@ -2878,13 +2941,13 @@ export const SHARED_DEPLOY_FLAGS = [
2878
2941
  // A 1/1 `abx deploy` accepts these (see `abx help deploy`). Anything else warns (typo'd/unsupported).
2879
2942
  export const DEPLOY_FLAGS = new Set([
2880
2943
  'image', 'name', 'symbol', 'type', 'description', 'description-onchain', 'external-url', 'traits', 'traits-onchain', 'attributes',
2881
- 'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'royalty-bps', 'no-mint',
2944
+ 'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'royalty-bps', 'royalty-cap', 'burnable', 'no-mint',
2882
2945
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
2883
2946
  ...SHARED_DEPLOY_FLAGS,
2884
2947
  ]);
2885
2948
  // `abx deploy-series` accepts these (see `abx help deploy-series`).
2886
2949
  export const DEPLOY_SERIES_FLAGS = new Set([
2887
- 'dir', 'count', 'name', 'symbol', 'description', 'external-url', 'royalty-bps', 'attributes', 'traits-onchain',
2950
+ 'dir', 'count', 'name', 'symbol', 'description', 'external-url', 'royalty-bps', 'royalty-cap', 'burnable', 'attributes', 'traits-onchain',
2888
2951
  'no-mint', 'mint-all', 'mint-count', 'unpaused', 'minter', 'primary-payee',
2889
2952
  'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'chunk-store',
2890
2953
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
@@ -2956,6 +3019,15 @@ export async function cmdDeployCodeBody(flags, emit) {
2956
3019
  throw new Error(`--mint-count ${mintCount} exceeds --max ${max}`);
2957
3020
  const paused = flags.unpaused === undefined;
2958
3021
  const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
3022
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
3023
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
3024
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
3025
+ ? Math.max(1000, royaltyBps)
3026
+ : parseRoyaltyBps(String(flags['royalty-cap']));
3027
+ if (royaltyBps > maxRoyaltyBps) {
3028
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
3029
+ }
3030
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
2959
3031
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
2960
3032
  const baseUrl = (flags['public-base-url'] ?? resolveBaseUrl(port)).replace(/\/$/, '');
2961
3033
  const hasPublicUrl = !!(flags['public-base-url'] || process.env.ABX_PUBLIC_BASE_URL);
@@ -3480,7 +3552,7 @@ export async function cmdDeployCodeBody(flags, emit) {
3480
3552
  info(`deployer: ${dim('not set')} — pass ${bold('--for 0x..')} (or set a signing key) to preview the exact deterministic address; the plan below is otherwise deployer-independent.`);
3481
3553
  if (shownAddr)
3482
3554
  info(`deterministic address: ${shownAddr}`);
3483
- info(`name "${name}" · symbol ${symbol} · max ${max}${maxProvided ? '' : ' ⚠ default — set --max N'} · royalty ${royaltyBps / 100}%${royaltyProvided ? '' : ' ⚠ default — set --royalty-bps N'}`);
3555
+ info(`name "${name}" · symbol ${symbol} · max ${max}${maxProvided ? '' : ' ⚠ default — set --max N'} · royalty ${royaltyBps / 100}%${royaltyProvided ? '' : ' ⚠ default — set --royalty-bps N'} · cap ${maxRoyaltyBps / 100}%${burnable ? ' · burnable' : ''}`);
3484
3556
  if (!flags.description)
3485
3557
  warn(`no --description — the on-chain metadata ships with no description (the one creators most often forget). Add --description "…" or deploy bare.`);
3486
3558
  info(`content: ${contentSummary}`);
@@ -3724,6 +3796,8 @@ export async function cmdDeployCodeBody(flags, emit) {
3724
3796
  contractURIRenderer: foldIntoInit ? metadataRenderer : zeroAddress,
3725
3797
  royaltyReceiver: owner,
3726
3798
  royaltyBps,
3799
+ maxRoyaltyBps,
3800
+ burnable,
3727
3801
  transferValidator,
3728
3802
  maxInvocations: BigInt(max),
3729
3803
  primaryPayee: flags['primary-payee'] ?? zeroAddress,
@@ -4081,7 +4155,7 @@ export async function cmdDeployCodeBody(flags, emit) {
4081
4155
  export const DEPLOY_CODE_EDITION_FLAGS = new Set([
4082
4156
  'script', 'name', 'symbol', 'description', 'external-url', 'description-onchain',
4083
4157
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
4084
- 'max', 'mint-count', 'mint-all', 'no-mint', 'mint-amount', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', '721c',
4158
+ 'max', 'mint-count', 'mint-all', 'no-mint', 'mint-amount', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', 'royalty-cap', 'burnable', '721c',
4085
4159
  'schema', 'no-seed', 'seed-source',
4086
4160
  'public-base-url', 'onchain-uri', 'generator', 'renderer', 'port',
4087
4161
  'sign', 'unsigned', 'for', 'salt', 'factory', 'bootstrap-factory', 'sign-url-file',
@@ -4140,6 +4214,15 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
4140
4214
  }
4141
4215
  const paused = flags.unpaused === undefined;
4142
4216
  const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
4217
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
4218
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
4219
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
4220
+ ? Math.max(1000, royaltyBps)
4221
+ : parseRoyaltyBps(String(flags['royalty-cap']));
4222
+ if (royaltyBps > maxRoyaltyBps) {
4223
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
4224
+ }
4225
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
4143
4226
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
4144
4227
  const baseUrl = (flags['public-base-url'] ?? resolveBaseUrl(port)).replace(/\/$/, '');
4145
4228
  const hasPublicUrl = !!(flags['public-base-url'] || process.env.ABX_PUBLIC_BASE_URL);
@@ -4282,6 +4365,8 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
4282
4365
  contractURIRenderer: zeroAddress,
4283
4366
  royaltyReceiver: owner,
4284
4367
  royaltyBps,
4368
+ maxRoyaltyBps,
4369
+ burnable,
4285
4370
  transferValidator,
4286
4371
  maxInvocations: BigInt(max),
4287
4372
  editionSize,
@@ -4375,7 +4460,7 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
4375
4460
  info(`deployer: ${dim('not set')} — pass ${bold('--for 0x..')} to preview the exact deterministic address.`);
4376
4461
  if (shownAddr)
4377
4462
  info(`deterministic address: ${shownAddr}`);
4378
- info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}%`);
4463
+ info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}% · cap ${maxRoyaltyBps / 100}%${burnable ? ' · burnable' : ''}`);
4379
4464
  // `source` is what this lane read; there has never been a `bytes` binding here, so this line
4380
4465
  // threw a ReferenceError for every `deploy-code --copies … --dry-run` — the dry run crashed at the
4381
4466
  // last step of the plan it was printing. Byte length, not UTF-16 code units, matching the 721 twin.