@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 +212 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +97 -12
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +63 -10
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reads.js +1 -1
- package/dist/commands/reads.js.map +1 -1
- package/dist/commands/service.js +2 -2
- package/dist/commands/service.js.map +1 -1
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +3 -0
- package/dist/conformance.js.map +1 -1
- package/dist/flag-allowlists.d.ts.map +1 -1
- package/dist/flag-allowlists.js +1 -0
- package/dist/flag-allowlists.js.map +1 -1
- package/dist/main.js +17 -7
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +11 -0
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +37 -3
- package/dist/ownerops.js.map +1 -1
- package/dist/remote.d.ts +2 -2
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +13 -3
- package/dist/remote.js.map +1 -1
- package/dist/riskgate.d.ts.map +1 -1
- package/dist/riskgate.js +16 -2
- package/dist/riskgate.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +10 -1
- package/dist/schema.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +20 -7
- package/skill/reference/decisions.md +1 -1
- package/skill/reference/hosting.md +1 -1
- package/skill/reference/operating.md +2 -1
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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"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -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,
|
|
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 (
|
|
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.
|