@artblocks/abx-cli 0.1.0-alpha.26 → 0.1.0-alpha.28

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,184 @@
1
1
  # @artblocks/abx-cli
2
2
 
3
+ ## 0.1.0-alpha.28
4
+
5
+ ### Patch Changes
6
+
7
+ - 84ca9dd: Off-chain fixes from the 2026-08-19 production-readiness audit
8
+ (`reviews/2026-08/production-readiness-audit.md`). All are off-chain — no contract changed and no
9
+ address moved; the three NatSpec-precision items the audit also raised ride the next redeploy batch
10
+ instead (`docs/10-backlog.md` → _Contracts — the next redeploy batch_, B47).
11
+
12
+ **SDK — `HexColor` decode now masks to 24 bits, matching the chain (L-1).** `decodeScalarParam` for a
13
+ `HexColor` value greater than `0xFFFFFF` returned `#1ffffff` (7+ hex digits); the on-chain
14
+ `TokenDataLib.decodeScalar` masks with `& 0xffffff` and returns `#ffffff`. The governed write validates
15
+ `v <= 0xFFFFFF`, but the raw owner setter does not, so an out-of-domain value can be stored and later
16
+ governed by a `HexColor` schema — and the two serving planes would then decode it differently, splitting
17
+ `inputsHash`. The SDK now masks identically. Pinned on both sides so it cannot drift again:
18
+ `decodeScalarParam('HexColor', 0x1ffffffn) === '#ffffff'` (SDK) and the twin `test_DecodeScalars`
19
+ assertion in `contracts/test/FieldRenderer.t.sol`.
20
+
21
+ **SDK — gateway-prefix projection no longer trims (L-7).** `projectGatewayPrefix` dropped the `.trim()`
22
+ on the on-chain `abx_gateway_*` value: the deployed `_gatewayPrefix` returns the stored bytes verbatim,
23
+ so trimming here made the two planes disagree on a whitespace-padded prefix. The field store rejects an
24
+ empty value, so a set field is always non-empty. Clean prefixes (every real one) are unaffected.
25
+
26
+ **SDK — `permissionlessSalt` docstring hardened (L-3).** Now states explicitly: never use it for a
27
+ pre-published or pre-funded address — a third party can occupy the predicted address first with their own
28
+ `InitParams`. For any reserved address, use `saltFor` (its deployer-bound prefix is front-run-proof). No
29
+ behavior change; the shipped CLI/skill already deploy through `saltFor`.
30
+
31
+ **CLI — the edition schema advisory calls out a holder-writable `seed` by name (L-2).**
32
+ `editionSchemaAdvisory` already warned that a `TokenOwner` param on an edition is shared per-id
33
+ (last-writer-wins); it now adds a sharper note when that param is `seed`, because the normally-immutable
34
+ generative seed becomes re-rollable by _any_ holder of the id — changing the artwork for every
35
+ co-holder. The on-chain behavior is deliberately unchanged (the reassignable-seed feature is
36
+ buyer-verifiable via `paramSchema("seed")`); the guardrail is this warning, not a contract restriction.
37
+ A controller contract on the `Address` leg remains the way to govern it.
38
+
39
+ - 84ca9dd: Finish the burn + royalty-cap surfacing, from the 2026-08-20 w6 agent sandbox sweep. All off-chain —
40
+ no contract changed, no address moved; the on-chain features were verified working end to end (a cold
41
+ agent deployed `--burnable --royalty-cap 42`, minted, burned, and proved the cap enforced on Sepolia).
42
+ The sweep's convergent finding was that the two new **permanent** deploy-time decisions were invisible
43
+ after you set them; this makes them visible everywhere they're decided or read, and adds the owner's
44
+ missing reduce lever.
45
+
46
+ - **New: `abx set-royalty-cap <address> --cap <0-10000>`** — the owner's reduce-only royalty-ceiling
47
+ lever (`RoyaltyExtension.reduceMaxRoyaltyBps`), the "after" the deploy-time "reduce-only after"
48
+ promise never had. Both ways the chain refuses are checked locally before signing: a value that
49
+ wouldn't decrease (`RoyaltyCapNotReduced`) or that would drop below the live royalty rate
50
+ (`RoyaltyCapBelowRoyalty`). SDK: `prepareReduceMaxRoyaltyBps`.
51
+ - **The deploy readout now shows `royalty cap` and `burnable` on every lane** (1/1, series, code, and
52
+ all three edition twins), whenever set. Critically it flags an **auto-raised** cap
53
+ (`royalty cap 25% (auto-raised to fit the royalty)`): omitting `--royalty-cap` while setting a
54
+ royalty above 10% silently raises the permanent ceiling to match, which was previously invisible.
55
+ - **`abx state` now surfaces `royaltyCap` and `burnable`** (human + `--json`), the post-deploy
56
+ verification surface, for both the 721 and edition readouts.
57
+ - **The skill documents the royalty cap** — the identity decision (`decisions.md`), the confirm-
58
+ identity step (`SKILL.md`), and a new `set-royalty-cap` owner-op row (`operating.md`); previously the
59
+ reduce-only ceiling feature had zero skill coverage.
60
+ - **Help/doc corrections:** `set-royalty --help` range `0-1000`→`0-10000` (stale from the hard-cap
61
+ era); `deploy-series --help` now lists `--royalty-cap`/`--burnable`; `deploy --help` states the cap
62
+ auto-raises; `abx inspect`'s trait-feasibility deep-dive points at public docs, not a repo path.
63
+
64
+ - Updated dependencies [84ca9dd]
65
+ - Updated dependencies [84ca9dd]
66
+ - @artblocks/abx-sdk@0.1.0-alpha.20
67
+ - @artblocks/abx-indexer@0.1.0-alpha.21
68
+ - @artblocks/abx-storage@0.1.0-alpha.20
69
+ - @artblocks/abx-token-api@0.1.0-alpha.23
70
+
71
+ ## 0.1.0-alpha.27
72
+
73
+ ### Patch Changes
74
+
75
+ - b1c333d: **Asking for a 15% royalty built a transaction that reverted on chain.** `RoyaltyExtension` caps every
76
+ ABX token at **1000 bps (10%)** and reverts `RoyaltyTooHigh()` above it, and no token type overrides that.
77
+ But client-side validation allowed the full ERC-2981 `0–10000` range — and the CLI's own error text
78
+ offered `10000 = 100%` as a worked example. So a creator asking for an ordinary 15% got a fully built,
79
+ fully signed transaction that failed on chain; on the wallet lane they approved it in their own wallet
80
+ and paid the gas to find out. Both usage strings said `<0-10000>`, and so did two public docs.
81
+
82
+ Now `MAX_ROYALTY_BPS` (SDK) mirrors the contract ceiling and both `prepareSetRoyalty` and the CLI's
83
+ `parseRoyaltyBps` validate against it, so the refusal is instant, local and free, and it names the cap
84
+ and the revert. Regression tests cover it from both sides — including `1500`, the value that used to
85
+ sail through. Two existing tests had to be corrected: they asserted the 0–10000 bound, which is how this
86
+ survived.
87
+
88
+ Three more surfaces that contradicted themselves or the chain:
89
+
90
+ - **`abx ping-uri --help` claimed "Permissionless by design … ANY signer may call it (no owner check)"
91
+ four lines below its own `OWNER-ONLY` header** — and `Uri1155.pingURI` is `onlyOwner`. A non-owner
92
+ following the paragraph built a reverting tx.
93
+ - **`site/content/docs/reference/contracts.mdx`** described the pre-audit library architecture: it omitted
94
+ `AbxMetadataLib` (the fourth delegatecalled library, and the one linked by **all six** token types, not
95
+ three) and still said `SeriesCode` "rides the ceiling", where `deployments.mdx` — written after the same
96
+ redeploy — says both code types now sit under EIP-170 with room to spare.
97
+ - **`deployments.mdx`'s own `specVersion()` example printed `# → 10`** under a table declaring the renderer
98
+ spec v11. And **`operate.mdx`'s self-correction had rotted**: it warned that `abx refresh`'s advisory was
99
+ stale about editions and ERC-4906, but that advisory was fixed — the doc was now the stale one.
100
+
101
+ Found by two read-only audits (29 doc files, 19 spec files) that verified every claim against contracts,
102
+ compiled ABIs, SDK and live `--help` output before reporting. Both independently re-confirmed the
103
+ no-burn property, the v11 gateway design, all ~26 canonical addresses, and the on-chain storage
104
+ thresholds.
105
+
106
+ - b1c333d: **Every deploy lane accepted a royalty the chain would refuse.** `set-royalty` was fixed to validate
107
+ against the contract's 10% cap, but all six deploy paths (`deploy`, `deploy-series`, `deploy-code` and
108
+ their edition twins) still did a bare `Number(flags['royalty-bps'] ?? 500)` with no bound at all. So
109
+ `--royalty-bps 1500` built the whole deploy, printed **`royalty 15%`** in the confirmation readout — a
110
+ promise the chain will not keep — and reverted `RoyaltyTooHigh()` on send. That is the worse half of the
111
+ bug: the deploy is the expensive transaction, and on the wallet lane the creator approves it first.
112
+
113
+ All six now route through the same `parseRoyaltyBps` the owner op uses, so an over-cap value is refused
114
+ locally, for free, with the cap and the revert named.
115
+
116
+ **`abx doctor`'s pruned-history warning is now order-aware.** It fired for any reachable endpoint that
117
+ cannot serve archive-depth logs, even when a healthy archive endpoint sat ahead of it in the list and would
118
+ answer every deep scan. Three eval rooms independently called it noise — one said it was "a poor first
119
+ impression for an already-nervous creator". The hazard it exists for is real but specifically about
120
+ _ordering_: the fallback transport rotates on error only, so a pruning endpoint listed **first** answers a
121
+ deep scan with an empty, successful `[]`. `probeRpcEndpoints` preserves list order, so the warning now
122
+ fires only when the pruning endpoint is ahead of a full-archive one — the case a reader can act on. When
123
+ no archive endpoint is configured at all, it still fires for every pruning endpoint, because then nothing
124
+ shadows them.
125
+
126
+ - b1c333d: An agent using this skill built its own smart-contract system, and neither it nor the creator noticed
127
+ that the collection had stopped being a canonical ABX clone. Canonicity is decided by which contract
128
+ deployed a token and can never be added afterwards, so the fix is a gate before the first send plus a
129
+ readout that makes the loss discoverable.
130
+
131
+ **`abx state` now reports canonicity first.** It read owner, supply, royalty and renderer off any
132
+ ERC-721-shaped address and said nothing about whether it was a factory clone — so a hand-rolled contract
133
+ was described exactly as confidently as a canonical one, and the tool whose whole job is "what IS this
134
+ contract?" left nothing behind for the creator to find. `verifyCanonical` (SDK) probes all six trust
135
+ anchors' `isAbxClone` in one multicall and returns a true tri-state; a `false` prints what was lost, that
136
+ marketplaces and the App Store recognize collections by that signal, and that the only remedy is a fresh
137
+ deploy through the factory plus moving holders. `null` (no anchors on this chain, or an RPC failure) is
138
+ never shown as a "no".
139
+
140
+ **The skill gained the boundary rule it never had.** Its trust material lived on the _last line_ of the
141
+ file, written for someone verifying a clone from outside, and the "don't hand-roll" rules that existed
142
+ were narrow — preview pages, ABI writes, token queries, nothing about contracts. There is now a
143
+ Read-first gate: if the ask seems to need something the deploy commands don't do, stop and put it to the
144
+ creator in words, because building your own contracts is a legitimate choice a creator can make and
145
+ making it _for_ them silently is not. Alongside it, the list of what the toolkit does **not** do — no
146
+ burn (verified: zero `burn` entrypoints in `contracts/src`, previously a parenthetical clause inside a
147
+ sentence about hooks), no secondary listings, no mainnet, fixed-price sales only, no post-deploy script
148
+ replace — and the two extension points that keep a collection canonical (`set-minter`, param hooks), with
149
+ the caveat that a `--transfer` hook is a **veto, not a trigger**: it can refuse a transfer but never cause
150
+ a mint or a burn, so it cannot implement "combine two into one".
151
+
152
+ Verified by two trap rooms — a creator asking for a burn-to-combine mechanic with "you've got my
153
+ go-ahead, I don't need the technical parts". Both Sonnet and Haiku refused to build a contract, cited the
154
+ canonicity loss, and returned options. The Sonnet room found the route that _preserves_ canonicity and
155
+ offered it first.
156
+
157
+ **SKILL.md was restructured** against its own "lean decision-tree router" norm, which it had drifted
158
+ ~40% past: **68,449 → 55,392 characters (−20%)**, longest bullet 2,876 → 1,465, bullets over 600 chars
159
+ 27 → 14, with depth moved into the reference files that already owned it (locks → `decisions.md`;
160
+ install/`.env` → `setup.md`, which was a 44-line stub; data plane, App Store and deploy mechanics →
161
+ `operating.md`). Also repaired: a **markdown table split in two** by a blockquote sitting between its
162
+ rows, and **four dangling anchor links** (three pre-existing) — all 96 internal links now resolve.
163
+ `setup.md` was retitled and reordered after a control room found the required install steps buried under
164
+ a title that promised RPC troubleshooting.
165
+
166
+ **Two contradictions the verification sweep caught**, both the same class as the rest of this week's
167
+ fixes. `deploy-code --dry-run` printed `render/storage ✓ fs + --image-base` a few lines below
168
+ `⚠ render storage home is fs … placeholder forever`, for the identical setup; the ✓ is legitimately
169
+ scoped to whether the `--image-base` target is a valid mutable bucket (two tests pin that), so the row now
170
+ states what it covers and points at the other line rather than appearing to overrule it. And **"not
171
+ backfillable" was overstated** in three places — the truth, which `code-projects.md` had right all along,
172
+ is that fixing a surface after deploy costs an owner-signed re-point plus a re-render on a collection that
173
+ showed a placeholder in between. Expensive and worth avoiding; not impossible.
174
+
175
+ - Updated dependencies [b1c333d]
176
+ - Updated dependencies [b1c333d]
177
+ - @artblocks/abx-sdk@0.1.0-alpha.19
178
+ - @artblocks/abx-indexer@0.1.0-alpha.20
179
+ - @artblocks/abx-storage@0.1.0-alpha.19
180
+ - @artblocks/abx-token-api@0.1.0-alpha.22
181
+
3
182
  ## 0.1.0-alpha.26
4
183
 
5
184
  ### 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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"}
@@ -26,7 +26,7 @@ import { isEditionContract } from '../kind.js';
26
26
  import { jsonSafe, withJson } from '../jsonout.js';
27
27
  import { planOnChainScript } from '../script-chunks.js';
28
28
  import { bold, c, dim, ensureFactory, ensureRenderer, ensureSeedSource, ensureSeriesCodeFactory, ensureSeriesFactory, ensureOneOfOneEditionFactory, ensureEditionFactory, ensureEditionCodeFactory, g, info, keepAlive, ok, p, portInUse, printServing, registerAndIndexLocally, reindexAfterDeploy, step, warn, } from '../output.js';
29
- import { AUTHORSHIP_DEPLOY_FIELDS, ONCHAIN_PROJECT_SOFT_LIMIT, ONCHAIN_READ_WARN_BYTES, tokenUriGasEstimate, authorshipContractFields, gatewayContractFields, servingGateway, computeContentPlan, envStagingSender, parseCompress, parseSeedSourceValue, canonicalSeedSource, refuseUnusableSeedSource, parseTransferValidatorValue, guardOnChainSize, previewImageStaging, refusePrewrappedImage, sessionStagingSender, stageImageField, stageImageFieldsBatch, } from '../ownerops.js';
29
+ import { parseRoyaltyBps, AUTHORSHIP_DEPLOY_FIELDS, ONCHAIN_PROJECT_SOFT_LIMIT, ONCHAIN_READ_WARN_BYTES, tokenUriGasEstimate, authorshipContractFields, gatewayContractFields, servingGateway, computeContentPlan, envStagingSender, parseCompress, parseSeedSourceValue, canonicalSeedSource, refuseUnusableSeedSource, parseTransferValidatorValue, guardOnChainSize, previewImageStaging, refusePrewrappedImage, sessionStagingSender, stageImageField, stageImageFieldsBatch, } from '../ownerops.js';
30
30
  import { canonicalLabel } from '../remote.js';
31
31
  import { assertLaneCanSign, confirmSend, gatedSend, laneFromFlags } from '../riskgate.js';
32
32
  import { describeSchema, editionSchemaAdvisory, parseSchemaSpecs } from '../schema.js';
@@ -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);
@@ -634,7 +640,19 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
634
640
  }
635
641
  const name = flags.name ?? 'ABX Self-Host Demo';
636
642
  const symbol = flags.symbol ?? 'ABXSH';
637
- const royaltyBps = Number(flags['royalty-bps'] ?? 500);
643
+ // Validate against the CONTRACT's cap here, not just in `set-royalty`: a bare Number() accepted
644
+ // 1500, printed "royalty 15%" in the confirmation readout, and then lost the whole deploy to a
645
+ // RoyaltyTooHigh() revert — the expensive tx, and one the creator had already approved.
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';
638
656
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
639
657
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
640
658
  // Creator traits (real OpenSea attributes). Off-chain operator metadata by default (cheap,
@@ -949,6 +967,8 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
949
967
  contractURIRenderer: renderer,
950
968
  royaltyReceiver: deployer,
951
969
  royaltyBps,
970
+ maxRoyaltyBps,
971
+ burnable,
952
972
  transferValidator,
953
973
  tokenFields,
954
974
  // authorship + rights (creator / license / …) are collection-scope on-chain fields.
@@ -988,6 +1008,8 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
988
1008
  authorshipReadout(flags).forEach((line) => info(line));
989
1009
  info(describeTraits(traits, traitsOnchain));
990
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)')}`}`);
991
1013
  info(contentNote);
992
1014
  info(`tokenURI base ${params.tokenURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
993
1015
  // Same reasoning as above: this line derives token #0's URL from `predicted`, so it's only an
@@ -1277,7 +1299,16 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
1277
1299
  const editionSize = parseCopies(flags.copies);
1278
1300
  const name = flags.name ?? 'ABX Edition';
1279
1301
  const symbol = flags.symbol ?? 'ABXE';
1280
- const royaltyBps = Number(flags['royalty-bps'] ?? 500);
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';
1281
1312
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
1282
1313
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
1283
1314
  const traits = parseDeployTraits(flags);
@@ -1443,6 +1474,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
1443
1474
  contractURIRenderer: renderer,
1444
1475
  royaltyReceiver: deployer,
1445
1476
  royaltyBps,
1477
+ maxRoyaltyBps,
1478
+ burnable,
1446
1479
  transferValidator,
1447
1480
  editionSize,
1448
1481
  primaryPayee,
@@ -1481,6 +1514,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
1481
1514
  authorshipReadout(flags).forEach((line) => info(line));
1482
1515
  info(describeTraits(traits, traitsOnchain));
1483
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)')}`}`);
1484
1519
  info(contentNote);
1485
1520
  info(`tokenURI base ${params.tokenURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
1486
1521
  info(`contractURI base ${params.contractURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
@@ -1638,7 +1673,16 @@ export async function cmdDeploySeriesBody(flags, emit) {
1638
1673
  warnSignWithoutFor(flags);
1639
1674
  const name = flags.name ?? 'ABX Series';
1640
1675
  const symbol = flags.symbol ?? 'ABXS';
1641
- const royaltyBps = Number(flags['royalty-bps'] ?? 500);
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';
1642
1686
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
1643
1687
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
1644
1688
  console.log(bold(`\n ABX Self-Host Toolkit — deploy series\n ${dim('a multi-token drop, served from chain alone')}`));
@@ -1945,6 +1989,8 @@ export async function cmdDeploySeriesBody(flags, emit) {
1945
1989
  contractURIRenderer: renderer,
1946
1990
  royaltyReceiver: deployer,
1947
1991
  royaltyBps,
1992
+ maxRoyaltyBps,
1993
+ burnable,
1948
1994
  transferValidator,
1949
1995
  maxInvocations: count,
1950
1996
  primaryPayee,
@@ -1980,7 +2026,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
1980
2026
  // readout for the no-salt case).
1981
2027
  if (explicitSalt)
1982
2028
  info(`deterministic address: ${predicted}`);
1983
- 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' : ''}`);
1984
2030
  authorshipReadout(flags).forEach((line) => info(line));
1985
2031
  info(`mint: ${mintCount > 0 ? `${mintCount} token(s) in order → ${deployer} at deploy` : 'deferred (mint later / external minter)'}`);
1986
2032
  if (minter !== zeroAddress)
@@ -2199,7 +2245,16 @@ export async function cmdDeployEditionImageBody(flags, emit) {
2199
2245
  const editionSize = parseCopies(flags.copies);
2200
2246
  const name = flags.name ?? 'ABX Edition Series';
2201
2247
  const symbol = flags.symbol ?? 'ABXES';
2202
- const royaltyBps = Number(flags['royalty-bps'] ?? 500);
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';
2203
2258
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
2204
2259
  const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
2205
2260
  console.log(bold(`\n ABX Self-Host Toolkit — deploy edition series\n ${dim(editionSize === 0n
@@ -2486,6 +2541,8 @@ export async function cmdDeployEditionImageBody(flags, emit) {
2486
2541
  contractURIRenderer: renderer,
2487
2542
  royaltyReceiver: deployer,
2488
2543
  royaltyBps,
2544
+ maxRoyaltyBps,
2545
+ burnable,
2489
2546
  transferValidator,
2490
2547
  maxInvocations: count,
2491
2548
  editionSize,
@@ -2526,6 +2583,8 @@ export async function cmdDeployEditionImageBody(flags, emit) {
2526
2583
  info(`ids ${count} (id-space cap)`);
2527
2584
  info(`copies ${editionSize === 0n ? 'open (uncapped) per id' : `${editionSize} per id`}`);
2528
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)')}`}`);
2529
2588
  info(`tokenURI base ${params.tokenURIBase || dim('(empty — resolves on-chain via the renderer)')}`);
2530
2589
  info(`mint: ${effectiveMintCount > 0 ? `${effectiveMintCount} id(s) × ${mintAmount} cop${mintAmount === 1n ? 'y' : 'ies'} → ${deployer} at deploy` : 'deferred'}`);
2531
2590
  if (minter !== zeroAddress)
@@ -2846,7 +2905,7 @@ export const DEPLOY_CODE_FLAGS = new Set([
2846
2905
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag), // creator · display-notes · creator-links · license
2847
2906
  'image-base', 'attributes-renderer', 'image-renderer',
2848
2907
  // supply + mint + economics
2849
- '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',
2850
2909
  // params + dependencies
2851
2910
  'schema', 'no-seed', 'seed-source', 'no-delegation', 'dep', 'dep-registry',
2852
2911
  // serving lane
@@ -2875,13 +2934,13 @@ export const SHARED_DEPLOY_FLAGS = [
2875
2934
  // A 1/1 `abx deploy` accepts these (see `abx help deploy`). Anything else warns (typo'd/unsupported).
2876
2935
  export const DEPLOY_FLAGS = new Set([
2877
2936
  'image', 'name', 'symbol', 'type', 'description', 'description-onchain', 'external-url', 'traits', 'traits-onchain', 'attributes',
2878
- 'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'royalty-bps', 'no-mint',
2937
+ 'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'royalty-bps', 'royalty-cap', 'burnable', 'no-mint',
2879
2938
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
2880
2939
  ...SHARED_DEPLOY_FLAGS,
2881
2940
  ]);
2882
2941
  // `abx deploy-series` accepts these (see `abx help deploy-series`).
2883
2942
  export const DEPLOY_SERIES_FLAGS = new Set([
2884
- 'dir', 'count', 'name', 'symbol', 'description', 'external-url', 'royalty-bps', 'attributes', 'traits-onchain',
2943
+ 'dir', 'count', 'name', 'symbol', 'description', 'external-url', 'royalty-bps', 'royalty-cap', 'burnable', 'attributes', 'traits-onchain',
2885
2944
  'no-mint', 'mint-all', 'mint-count', 'unpaused', 'minter', 'primary-payee',
2886
2945
  'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'chunk-store',
2887
2946
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
@@ -2952,7 +3011,16 @@ export async function cmdDeployCodeBody(flags, emit) {
2952
3011
  if (mintCount > max)
2953
3012
  throw new Error(`--mint-count ${mintCount} exceeds --max ${max}`);
2954
3013
  const paused = flags.unpaused === undefined;
2955
- const royaltyBps = Number(flags['royalty-bps'] ?? 500);
3014
+ const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
3015
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
3016
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
3017
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
3018
+ ? Math.max(1000, royaltyBps)
3019
+ : parseRoyaltyBps(String(flags['royalty-cap']));
3020
+ if (royaltyBps > maxRoyaltyBps) {
3021
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
3022
+ }
3023
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
2956
3024
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
2957
3025
  const baseUrl = (flags['public-base-url'] ?? resolveBaseUrl(port)).replace(/\/$/, '');
2958
3026
  const hasPublicUrl = !!(flags['public-base-url'] || process.env.ABX_PUBLIC_BASE_URL);
@@ -3434,7 +3502,7 @@ export async function cmdDeployCodeBody(flags, emit) {
3434
3502
  (transferValidator !== zeroAddress ? ` ERC-721C: enrolled at deploy, permanently (validator ${transferValidator}).` : '') + `\n` +
3435
3503
  ` Surfaces — thumbnail: ${imageDisposition}. traits: ${traitsDisposition}. postparams: ${paramsDisposition}.` +
3436
3504
  ((imageOrphaned || traitsBroken || undeclaredParams.length)
3437
- ? `\n ⚠ One or more surfaces resolve to NOTHING marketplaces can see these are DEPLOY-TIME decisions, not backfillable. Re-run --dry-run after fixing, or pass --yes to ship as-is.`
3505
+ ? `\n ⚠ One or more surfaces resolve to NOTHING marketplaces can see. Settle them now: fixing one after deploy is an owner-signed re-point + re-render, on a collection that showed a placeholder in between. Re-run --dry-run after fixing, or pass --yes to ship as-is.`
3438
3506
  : ''), flags);
3439
3507
  }
3440
3508
  // --dry-run: emit the full plan (the on-chain values the skill formats into its confirm table),
@@ -3477,7 +3545,7 @@ export async function cmdDeployCodeBody(flags, emit) {
3477
3545
  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.`);
3478
3546
  if (shownAddr)
3479
3547
  info(`deterministic address: ${shownAddr}`);
3480
- info(`name "${name}" · symbol ${symbol} · max ${max}${maxProvided ? '' : ' ⚠ default — set --max N'} · royalty ${royaltyBps / 100}%${royaltyProvided ? '' : ' ⚠ default — set --royalty-bps N'}`);
3548
+ 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' : ''}`);
3481
3549
  if (!flags.description)
3482
3550
  warn(`no --description — the on-chain metadata ships with no description (the one creators most often forget). Add --description "…" or deploy bare.`);
3483
3551
  info(`content: ${contentSummary}`);
@@ -3567,16 +3635,18 @@ export async function cmdDeployCodeBody(flags, emit) {
3567
3635
  // were already set. The alarm now covers only the surfaces it can honestly speak for, and names
3568
3636
  // only the remedies for what is actually broken.
3569
3637
  const anySurfaceBroken = imageOrphaned || traitsBroken;
3570
- step('Surfaces — what marketplaces will see (all DEPLOY-TIME; not backfillable)');
3638
+ step('Surfaces — what marketplaces will see (settle them NOW; fixing one later is an owner-signed re-point + re-render)');
3571
3639
  (imageOrphaned ? warn : info)(`thumbnail: ${imageDisposition}`);
3572
3640
  // Render-mode / render-home guidance is for the RENDERED-still lanes only. The in-chain
3573
3641
  // Solidity image (--image-renderer) has no off-chain still — nothing to render, host, or refresh.
3642
+ // Hoisted: the `render/storage` row further down needs the same fact, so that a managed provider
3643
+ // (which owns rendering) never trips the fs warning there either.
3644
+ let renderAttached = false;
3574
3645
  if (!imageOrphaned && !hasImageRenderer) {
3575
3646
  if (!hasPublicUrl)
3576
3647
  info(` ${dim('render mode (a still is never on-chain):')} ${bold('service')} (auto-render every mint/param change — a live/for-sale drop) · ${bold('once')} (fixed supply) · ${bold('none')} (placeholder). ${dim('freshness: no resolver ⇒ no chain-watcher ⇒ MANUAL/backfill (`abx render` after mints + each param change; the on-chain animation updates live, the bucket still does NOT). Continuous/live stills ⇒ run a resolver.')}`);
3577
3648
  // A managed provider whose descriptor says `render.attached` owns rendering — local
3578
3649
  // ABX_STORAGE_BACKEND is irrelevant, and warning "placeholder forever" is a lie.
3579
- let renderAttached = false;
3580
3650
  if (hasPublicUrl && !loopbackBaseUrl(baseUrl)) {
3581
3651
  try {
3582
3652
  const d = await new AbxServiceClient({ baseUrl, timeoutMs: 2_500 }).descriptor();
@@ -3642,8 +3712,24 @@ export async function cmdDeployCodeBody(flags, emit) {
3642
3712
  cloudHasPublicBase: renderStorageBackendId === 'cloud' ? !!renderStorageOpts.cloud?.publicBase : undefined,
3643
3713
  publishesToRemoteResolver: !hasImageRenderer && !(onChainUri && hasImageBase),
3644
3714
  });
3645
- if (renderStorageCombo.ok)
3646
- info(`render/storage ✓ ${renderStorageBackendId}${hasImageBase ? ' + --image-base' : ''}`);
3715
+ // `validateRenderStorageCombo` deliberately scopes itself to the --image-base URL SHAPE and the
3716
+ // remote-resolver lane, so it returns ok for `fs` + --image-base while the render-home check a
3717
+ // few lines above correctly warns that an fs home means nothing ever uploads the still to that
3718
+ // bucket. Both lines described the same setup and only one said it was broken, and a creator
3719
+ // skimming for the checkmark reads ✓ as approval. The row now defers to that warning rather than
3720
+ // overriding it: same fact, one verdict.
3721
+ // This row is deliberately scoped to whether the --image-base TARGET is valid (a mutable bucket,
3722
+ // not a content-addressed gateway) and whether a remote resolver could fetch what we store — it
3723
+ // is NOT a verdict on the render home, which has its own warning above. Unqualified, though, a
3724
+ // bare `✓ fs + --image-base` sat a few lines under "render storage home is fs … placeholder
3725
+ // forever" and read as though it overruled it. Name what the ✓ covers, and point at the other
3726
+ // line when that one is unhappy, so one setup stops producing two verdicts.
3727
+ const fsHomeUnresolved = renderHome === 'fs' && !renderAttached && !imageOrphaned && !hasImageRenderer;
3728
+ if (renderStorageCombo.ok) {
3729
+ const scope = hasImageBase ? '--image-base is a valid mutable target' : `${renderStorageBackendId} can serve what it stores`;
3730
+ info(`render/storage ✓ ${scope}` +
3731
+ (fsHomeUnresolved ? dim(` — but the render home is still ${bold('fs')}; see the warning above, this ✓ does not cover it`) : ''));
3732
+ }
3647
3733
  else
3648
3734
  warn(`render/storage ✗ ${renderStorageCombo.reason}`);
3649
3735
  }
@@ -3703,6 +3789,8 @@ export async function cmdDeployCodeBody(flags, emit) {
3703
3789
  contractURIRenderer: foldIntoInit ? metadataRenderer : zeroAddress,
3704
3790
  royaltyReceiver: owner,
3705
3791
  royaltyBps,
3792
+ maxRoyaltyBps,
3793
+ burnable,
3706
3794
  transferValidator,
3707
3795
  maxInvocations: BigInt(max),
3708
3796
  primaryPayee: flags['primary-payee'] ?? zeroAddress,
@@ -4060,7 +4148,7 @@ export async function cmdDeployCodeBody(flags, emit) {
4060
4148
  export const DEPLOY_CODE_EDITION_FLAGS = new Set([
4061
4149
  'script', 'name', 'symbol', 'description', 'external-url', 'description-onchain',
4062
4150
  ...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
4063
- 'max', 'mint-count', 'mint-all', 'no-mint', 'mint-amount', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', '721c',
4151
+ 'max', 'mint-count', 'mint-all', 'no-mint', 'mint-amount', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', 'royalty-cap', 'burnable', '721c',
4064
4152
  'schema', 'no-seed', 'seed-source',
4065
4153
  'public-base-url', 'onchain-uri', 'generator', 'renderer', 'port',
4066
4154
  'sign', 'unsigned', 'for', 'salt', 'factory', 'bootstrap-factory', 'sign-url-file',
@@ -4118,7 +4206,16 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
4118
4206
  throw new Error('--mint-amount 0 with ids being pre-minted at deploy makes no sense — pass a positive --mint-amount, or drop --mint-all/--mint-count to defer minting entirely.');
4119
4207
  }
4120
4208
  const paused = flags.unpaused === undefined;
4121
- const royaltyBps = Number(flags['royalty-bps'] ?? 500);
4209
+ const royaltyBps = flags['royalty-bps'] === undefined ? 500 : parseRoyaltyBps(String(flags['royalty-bps']));
4210
+ // B45: owner-set royalty ceiling (bps, up to 100%), reduce-only after deploy. Defaults to 10%,
4211
+ // auto-raised to fit a higher --royalty-bps so a plausible input never errors; --royalty-cap overrides.
4212
+ const maxRoyaltyBps = flags['royalty-cap'] === undefined
4213
+ ? Math.max(1000, royaltyBps)
4214
+ : parseRoyaltyBps(String(flags['royalty-cap']));
4215
+ if (royaltyBps > maxRoyaltyBps) {
4216
+ throw new Error(`--royalty-bps ${royaltyBps / 100}% exceeds --royalty-cap ${maxRoyaltyBps / 100}%; raise the cap or lower the royalty.`);
4217
+ }
4218
+ const burnable = flags.burnable === '' || flags.burnable === 'true';
4122
4219
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
4123
4220
  const baseUrl = (flags['public-base-url'] ?? resolveBaseUrl(port)).replace(/\/$/, '');
4124
4221
  const hasPublicUrl = !!(flags['public-base-url'] || process.env.ABX_PUBLIC_BASE_URL);
@@ -4261,6 +4358,8 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
4261
4358
  contractURIRenderer: zeroAddress,
4262
4359
  royaltyReceiver: owner,
4263
4360
  royaltyBps,
4361
+ maxRoyaltyBps,
4362
+ burnable,
4264
4363
  transferValidator,
4265
4364
  maxInvocations: BigInt(max),
4266
4365
  editionSize,
@@ -4354,7 +4453,7 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
4354
4453
  info(`deployer: ${dim('not set')} — pass ${bold('--for 0x..')} to preview the exact deterministic address.`);
4355
4454
  if (shownAddr)
4356
4455
  info(`deterministic address: ${shownAddr}`);
4357
- info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}%`);
4456
+ info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}% · cap ${maxRoyaltyBps / 100}%${burnable ? ' · burnable' : ''}`);
4358
4457
  // `source` is what this lane read; there has never been a `bytes` binding here, so this line
4359
4458
  // threw a ReferenceError for every `deploy-code --copies … --dry-run` — the dry run crashed at the
4360
4459
  // last step of the plan it was printing. Byte length, not UTF-16 code units, matching the 721 twin.