@artblocks/abx-cli 0.1.0-alpha.20 → 0.1.0-alpha.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +1081 -0
  2. package/assets/renderer-scaffold/README.md +2 -2
  3. package/assets/renderer-scaffold/src/MyRenderer.sol +2 -2
  4. package/assets/renderer-scaffold/src/interfaces/IAbxFieldRenderer.sol +1 -1
  5. package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +2 -2
  6. package/assets/renderer-scaffold/test/MyRenderer.t.sol +1 -1
  7. package/dist/commands/deploy.d.ts +30 -17
  8. package/dist/commands/deploy.d.ts.map +1 -1
  9. package/dist/commands/deploy.js +523 -117
  10. package/dist/commands/deploy.js.map +1 -1
  11. package/dist/commands/project.d.ts +16 -0
  12. package/dist/commands/project.d.ts.map +1 -1
  13. package/dist/commands/project.js +171 -15
  14. package/dist/commands/project.js.map +1 -1
  15. package/dist/commands/reads.d.ts.map +1 -1
  16. package/dist/commands/reads.js +35 -5
  17. package/dist/commands/reads.js.map +1 -1
  18. package/dist/commands/scaffold.d.ts +3 -1
  19. package/dist/commands/scaffold.d.ts.map +1 -1
  20. package/dist/commands/scaffold.js +81 -29
  21. package/dist/commands/scaffold.js.map +1 -1
  22. package/dist/commands/service.js +1 -1
  23. package/dist/commands/service.js.map +1 -1
  24. package/dist/commands/storage.d.ts.map +1 -1
  25. package/dist/commands/storage.js +11 -3
  26. package/dist/commands/storage.js.map +1 -1
  27. package/dist/commands/submit-app.d.ts +58 -0
  28. package/dist/commands/submit-app.d.ts.map +1 -0
  29. package/dist/commands/submit-app.js +512 -0
  30. package/dist/commands/submit-app.js.map +1 -0
  31. package/dist/config.d.ts +1 -15
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +18 -1
  34. package/dist/config.js.map +1 -1
  35. package/dist/flag-allowlists.d.ts +53 -0
  36. package/dist/flag-allowlists.d.ts.map +1 -0
  37. package/dist/flag-allowlists.js +152 -0
  38. package/dist/flag-allowlists.js.map +1 -0
  39. package/dist/flags.d.ts +6 -2
  40. package/dist/flags.d.ts.map +1 -1
  41. package/dist/flags.js +23 -0
  42. package/dist/flags.js.map +1 -1
  43. package/dist/kind.d.ts +8 -3
  44. package/dist/kind.d.ts.map +1 -1
  45. package/dist/kind.js +25 -0
  46. package/dist/kind.js.map +1 -1
  47. package/dist/main.js +166 -53
  48. package/dist/main.js.map +1 -1
  49. package/dist/mintpage.d.ts.map +1 -1
  50. package/dist/mintpage.js +29 -4
  51. package/dist/mintpage.js.map +1 -1
  52. package/dist/output.d.ts +32 -1
  53. package/dist/output.d.ts.map +1 -1
  54. package/dist/output.js +74 -9
  55. package/dist/output.js.map +1 -1
  56. package/dist/ownerops.d.ts +101 -13
  57. package/dist/ownerops.d.ts.map +1 -1
  58. package/dist/ownerops.js +421 -71
  59. package/dist/ownerops.js.map +1 -1
  60. package/dist/preview.d.ts +1 -1
  61. package/dist/preview.js +1 -1
  62. package/dist/remote.d.ts.map +1 -1
  63. package/dist/remote.js +8 -2
  64. package/dist/remote.js.map +1 -1
  65. package/dist/schema.d.ts +18 -0
  66. package/dist/schema.d.ts.map +1 -1
  67. package/dist/schema.js +37 -2
  68. package/dist/schema.js.map +1 -1
  69. package/dist/served.js +1 -1
  70. package/dist/update-check.d.ts +25 -0
  71. package/dist/update-check.d.ts.map +1 -1
  72. package/dist/update-check.js +49 -9
  73. package/dist/update-check.js.map +1 -1
  74. package/package.json +6 -6
  75. package/skill/SKILL.md +64 -32
  76. package/skill/reference/code-projects.md +98 -22
  77. package/skill/reference/creator-token.md +16 -3
  78. package/skill/reference/decisions.md +83 -11
  79. package/skill/reference/hosting.md +1 -1
  80. package/skill/reference/operating.md +35 -12
  81. package/skill/reference/setup.md +1 -1
  82. package/skill/reference/troubleshooting.md +16 -6
package/dist/main.js CHANGED
@@ -14,7 +14,7 @@
14
14
  * abx demo deploy a 1/1 to Sepolia, index it, and serve it
15
15
  * abx deploy [--image ..] deploy + index a 1/1 (--image to custody your own bytes; --no-mint to defer)
16
16
  * --copies <n|open> routes to the ERC-1155 edition twin instead — copies of the
17
- * SAME artwork (an open edition, with --copies open, is the flagship edition product)
17
+ * SAME work (an open edition, with --copies open, is the flagship edition product)
18
18
  * abx deploy-series a multi-token image Series from a folder of media; --copies <n|open> makes it
19
19
  * an edition (N ids from the folder × copies each) — see `abx help deploy-series`
20
20
  * abx predict [--salt ..] pre-compute a deploy address (reserve / vanity it before signing); --copies-aware
@@ -32,7 +32,8 @@
32
32
  * never hand-build a resolver path)
33
33
  * abx tokens <address> every token's owner + seed + params, from chain alone (--json for the machine
34
34
  * path); on an edition, supply/maxSupply per id instead of an owner
35
- * abx configure-param <addr> <id> <key> <value> set a governed PostParam (typed encode; any lane)
35
+ * abx configure-param <addr> <id> <key> <value> set a governed PostParam (typed encode; any lane)
36
+ * abx submit-app <collection> list a deployed ABX collection in the App Store (optional, after deploy)
36
37
  * abx set-param-hooks <addr> wire/clear a SeriesCode's configure/augment/transfer param hooks
37
38
  * abx render <addr> [id…] effect-runner repair lane: render missing stills/traits
38
39
  * (enqueues via ABX_EFFECTS_URL when a runner is up, else inline)
@@ -41,7 +42,7 @@
41
42
  * (required unless a 1/1-edition) + --amount (copies of that id, default 1)
42
43
  * abx set-max-supply <addr> edition-only: lower ONE id's ERC-1155 supply cap (the per-id twin of
43
44
  * set-max-invocations) — --token-id <n> --cap <n> (never "open"; caps only decrease)
44
- * abx ping-uri <addr> edition-only, permissionless: re-emit the native URI event for --token-ids
45
+ * abx ping-uri <addr> edition-only, owner-only: re-emit the native URI event for --token-ids
45
46
  * <csv|range> after a re-point, so URI-only indexers/marketplaces re-index
46
47
  * abx refresh <addr> ask marketplaces to (re)index a token's metadata
47
48
  * abx transfer <addr> sell/transfer a token · set-admin: hand over contract ownership
@@ -74,13 +75,15 @@ import { cmdContractUri, cmdInspect, cmdPreview, cmdRender, cmdTokenUri, cmdToke
74
75
  import { cmdStorage } from './commands/storage.js';
75
76
  import { cmdDeployEffects, cmdDeployResolver, cmdEffects, cmdMigrate, cmdRemote, cmdServe, } from './commands/service.js';
76
77
  import { cmdDoctor, cmdMintPage, cmdScaffoldRenderer, cmdSkill } from './commands/scaffold.js';
78
+ import { cmdSubmitApp } from './commands/submit-app.js';
77
79
  import { CHAIN } from './config.js';
78
80
  import { CliError } from './errors.js';
79
- import { parseFlags } from './flags.js';
81
+ import { allowlistFor } from './flag-allowlists.js';
82
+ import { parseFlags, positionalArgs, warnStrayFlags } from './flags.js';
80
83
  import { bold, c, dim, ensureRenderer, g } from './output.js';
81
- import { cmdAttach, cmdConfigureParam, cmdLockDependencies, cmdLockField, cmdLockUri, cmdMint, cmdMinterBuy, cmdMinterConfigure, cmdMinterShow, cmdPause, cmdPingUri, cmdRefresh, cmdRemoveLastDependency, cmdRetireParam, cmdSetAdmin, cmdSetContractUri, cmdSetDependency, cmdSetDependencyRegistry, cmdSetField, cmdSetMaxInvocations, cmdSetMaxSupply, cmdSetMinter, cmdSetParamHooks, cmdSetPrimaryPayee, cmdSetRenderer, cmdSetRoyalty, cmdSetSchema, cmdSetTokenUri, cmdSetTransferValidator, cmdTransfer, cmdUnpause, } from './ownerops.js';
84
+ import { cmdAttach, cmdConfigureParam, cmdLockDependencies, cmdLockParamHooks, cmdLockScript, cmdLockField, cmdLockUri, cmdMint, cmdMinterBuy, cmdMinterConfigure, cmdMinterShow, cmdPause, cmdPingUri, cmdRefresh, cmdRemoveLastDependency, cmdRetireParam, cmdSetAdmin, cmdSetContractUri, cmdSetDependency, cmdSetDependencyRegistry, cmdSetField, cmdSetMaxInvocations, cmdSetMaxSupply, cmdSetMinter, cmdSetParamHooks, cmdSetPrimaryPayee, cmdSetRenderer, cmdSetRoyalty, cmdSetSchema, cmdSetTokenUri, cmdSetTransferValidator, cmdSetSeedSource, cmdTransfer, cmdUnpause, } from './ownerops.js';
82
85
  import { DEFAULT_PREVIEW_PORT } from './preview.js';
83
- import { checkForCliUpdate, compareVersions, installedSkillVersions, readCliVersion } from './update-check.js';
86
+ import { checkForCliUpdate, compareVersions, installedSkillCopies, readCliVersion, skillRefreshCommands } from './update-check.js';
84
87
  /**
85
88
  * Validate `ABX_CHAIN` at module load, BEFORE anything derives from it.
86
89
  *
@@ -118,11 +121,14 @@ async function maybeNotifyUpdate(flags) {
118
121
  const current = readCliVersion();
119
122
  // Skill drift (cheap, offline): the skill and CLI are co-versioned, but a separately-installed
120
123
  // skill copy doesn't move when the CLI upgrades. Nudge a reinstall when the CLI is ahead.
121
- for (const v of installedSkillVersions()) {
122
- if (compareVersions(current, v) > 0) {
123
- console.error(` ${c.orange}⚠${c.reset} installed abx skill is v${v} but the CLI is v${current} — refresh it: ${g('abx skill install')}`);
124
- break;
125
- }
124
+ // Name WHICH copy is stale and print the command that actually refreshes THAT one: a bare
125
+ // `abx skill install` never touches the global copy, so pointing at it for a stale global copy
126
+ // left agents re-running a fix that could not work (see `installedSkillCopies`).
127
+ const staleCopies = installedSkillCopies().filter((copy) => compareVersions(current, copy.version) > 0);
128
+ if (staleCopies.length) {
129
+ const worst = staleCopies[0];
130
+ const where = staleCopies.length > 1 ? `${staleCopies.length} installed copies are` : `the ${worst.scope} skill copy (${worst.path}) is`;
131
+ console.error(` ${c.orange}⚠${c.reset} ${where} v${worst.version} but the CLI is v${current} — refresh: ${skillRefreshCommands(staleCopies).map((cmd) => g(cmd)).join(' && ')}`);
126
132
  }
127
133
  const latest = await checkForCliUpdate(current);
128
134
  if (latest) {
@@ -177,6 +183,19 @@ async function main() {
177
183
  if (cmd && cmd !== 'help' && (flags.help !== undefined || rest.includes('-h'))) {
178
184
  return printCommandHelp(cmd);
179
185
  }
186
+ // One unknown-flag notice for every listed command (B30). Centralized here rather than added to ~45
187
+ // command bodies: one call site can't drift out of sync with itself, and a command absent from
188
+ // COMMAND_FLAGS keeps its previous behavior instead of gaining a half-right allowlist.
189
+ //
190
+ // WARN, not refuse. `unknownFlags`' own contract says so, and the owner's scriptability line backs it:
191
+ // an allowlist here could be incomplete, and a false refusal on a valid flag would break someone's
192
+ // working script — strictly worse than the silence this replaces. `deploy*` still refuses via its own
193
+ // exhaustive sets. The warning names the flag and points at the command's help, so the fix is local.
194
+ if (cmd) {
195
+ const allowed = allowlistFor(cmd, positionalArgs(rest)[0]);
196
+ if (allowed)
197
+ warnStrayFlags(flags, allowed, cmd);
198
+ }
180
199
  switch (cmd) {
181
200
  case 'demo': return cmdDeploy(flags, true);
182
201
  case 'deploy': return cmdDeploy(flags, false);
@@ -197,6 +216,7 @@ async function main() {
197
216
  case 'serve': return cmdServe(flags);
198
217
  // owner operations — write + sign (hot/wallet/cold lane), then re-index
199
218
  case 'mint': return cmdMint(rest[0], flags);
219
+ case 'submit-app': return cmdSubmitApp(rest[0], flags);
200
220
  case 'set-minter': return cmdSetMinter(rest[0], flags);
201
221
  case 'set-max-invocations': return cmdSetMaxInvocations(rest[0], flags);
202
222
  case 'set-max-supply': return cmdSetMaxSupply(rest[0], flags);
@@ -209,6 +229,8 @@ async function main() {
209
229
  case 'remove-last-dependency': return cmdRemoveLastDependency(rest[0], flags);
210
230
  case 'set-dependency-registry': return cmdSetDependencyRegistry(rest[0], rest.slice(1), flags);
211
231
  case 'lock-dependencies': return cmdLockDependencies(rest[0], flags);
232
+ case 'lock-script': return cmdLockScript(rest[0], flags);
233
+ case 'lock-param-hooks': return cmdLockParamHooks(rest[0], flags);
212
234
  case 'set-primary-payee': return cmdSetPrimaryPayee(rest[0], flags);
213
235
  case 'minter': return cmdMinter(rest);
214
236
  case 'pause': return cmdPause(rest[0], flags);
@@ -222,6 +244,7 @@ async function main() {
222
244
  case 'mint-page': return cmdMintPage(rest[0], flags);
223
245
  case 'set-royalty': return cmdSetRoyalty(rest[0], flags);
224
246
  case 'set-transfer-validator': return cmdSetTransferValidator(rest[0], rest.slice(1), flags);
247
+ case 'set-seed-source': return cmdSetSeedSource(rest[0], rest.slice(1), flags);
225
248
  case 'set-field': return cmdSetField(rest[0], flags);
226
249
  case 'attach': return cmdAttach(rest, flags);
227
250
  case 'lock-field': return cmdLockField(rest[0], flags);
@@ -291,17 +314,24 @@ const COMMAND_HELP = {
291
314
  ${dim('cross-agent installer also works (not version-locked):')} ${g('npx skills add ArtBlocks/abx --skill abx-self-host')}`,
292
315
  deploy: `
293
316
  ${bold('abx deploy')} ${dim('— deploy + index a 1/1 (no server). Sends a tx in the chosen lane.')}
294
- --image <path> custody your own image (png · jpg · gif · svg · webp); else generative demo art
317
+ --image <path> custody your own image (png · jpg · gif · svg · webp); else generative demo content
295
318
  --name <s> --symbol <s> ERC-721 identity
296
319
  --description "<s>" the creator's words, served in token metadata --external-url <url>
297
320
  --description-onchain store the description ON-CHAIN (durable, reconstructable from chain alone)
298
- --artist "<s>" · --license "<s>" authorship + rights, ON-CHAIN collection fields served in contractURI (also --display-notes, --artist-links)
321
+ --creator "<s>" · --license "<s>" authorship + rights, ON-CHAIN collection fields served in contractURI (also --display-notes, --creator-links)
299
322
  --traits "K=V; K2=V2" real OpenSea traits (the marketplace trait array) --attributes <file.json> (array or {name: value} map)
300
323
  --traits-onchain store traits ON-CHAIN (inline JSON, lockable); else off-chain operator metadata (editable via ${g('abx add --traits')})
301
324
  ${g('--onchain-uri')} resolve tokenURI/contractURI FULLY ON-CHAIN (renderer assembles JSON from fields;
302
325
  image inlined as SVG, description on-chain) — the token self-resolves, no server needed
303
326
  ${g('--onchain-image')} stage ${g('--image')} bytes on-chain (chunk store) and bake a reader field INTO the deploy —
304
- large content with NO post-deploy tx (implies --onchain-uri)
327
+ content with NO post-deploy tx (implies --onchain-uri)
328
+ ${dim('READ COST decides the ceiling, not storage: tokenURI reassembles the whole document per call at')}
329
+ ${dim('~360-405k gas/KB of on-chain content, climbing with size (same for inline and reader). Under')}
330
+ ${dim('~40KB reads routinely (~15M gas); 40-100KB needs a high-gas RPC (warned); past ~100KB (~40M) it is')}
331
+ ${dim('refused — a margin below the ~120KB where geth\'s own 50M cap runs out, since hosts cap lower (')}
332
+ ${dim('override with --allow-unreadable-onchain).')}
333
+ ${g('--allow-unreadable-onchain')} ship on-chain content past ~100KB anyway. You accept that ordinary RPC tooling —
334
+ ${dim('marketplaces, indexers, other contracts — sees a revert instead of your token, and rendering needs a high-gas endpoint.')}
305
335
  ${g('--compress')} none|fastlz|gzip for --onchain-image (default none): ${g('fastlz')} = on-chain decode (stays renderable);
306
336
  ${g('gzip')} = smaller but off-chain decode only
307
337
  --royalty-bps <0-10000> default 500 (5%)
@@ -309,6 +339,8 @@ const COMMAND_HELP = {
309
339
  (${g('recommended')} = OpenSea's validator for creator-fee enforcement — only owner-initiated
310
340
  transfers + authorized sales move the token). Absent = plain ERC-721, forever (the default).
311
341
  Manage later with ${g('abx set-transfer-validator')} (re-point or suspend; never un-enroll).
342
+ ${dim('A validator must be a deployed contract that REFUSES calls it does not implement — a Safe or a bare')}
343
+ ${dim('proxy is refused before any gas, since it would enforce nothing while every read said enforcement was ON.')}
312
344
  ${g('--no-mint')} deploy without minting (warm the resolver, then ${g('abx mint')})
313
345
  --salt 0x..<64hex> deploy to a reserved / vanity address (see ${g('abx predict')})
314
346
  --public-base-url <url> bake a public resolver URL on-chain (off-chain custody REQUIRES a public URL; localhost is refused)
@@ -321,26 +353,30 @@ const COMMAND_HELP = {
321
353
  ${dim('(needs a deployer to compute anything address-dependent — pass --for 0x.. if no signing key is set)')}
322
354
  ${dim('address only prints WITH --salt (it is the only thing that pins it) — without --salt this shows the salt + how to pin it, or use `abx predict`.')}
323
355
  ${g('--confirm')} interactive y/N gate before the real send (opt-in; --yes or non-TTY proceeds)
324
- ${bold(g('--copies <n|open>'))} ${dim('routes to the ERC-1155 edition twin (OneOfOneEdition) — copies of the SAME artwork, not a unique token.')}
356
+ ${bold(g('--copies <n|open>'))} ${dim('routes to the ERC-1155 edition twin (OneOfOneEdition) — copies of the SAME work, not a unique token.')}
325
357
  ${g('open')} = an uncapped OPEN EDITION (the flagship edition product) · a number = that many copies max.
326
358
  ${g('--copies 1')} is legal (a single-copy edition) but almost never what you want — drop --copies for the plain 721 lane instead.
327
359
  Adds: ${g('--mint-amount <n>')} (copies of #0 to premint at deploy; default 1, 0 ≡ --no-mint) ·
328
- ${g('--minter 0x..')} · ${g('--primary-payee 0x..')} · ${g('--unpaused')} (the sale stack — required on an edition, absent on a plain 1/1).
360
+ ${g('--minter 0x..')} · ${g('--primary-payee 0x..')} · ${g('--unpaused')} ${dim('(the sale stack — OPTIONAL, and available only on an edition. Omit them and the')}
361
+ ${dim('edition deploys paused with no sale, which is the common case: price + allocation are set AFTER deploy with')} ${g('abx minter configure')}${dim(' either way.)')}
329
362
  ${g('--721c')} enrolls ERC-1155C instead of 721C — same flag, same UX, same validator grammar.
330
- ${dim('v1 scope: --onchain-image is hot-lane only on an edition (not yet wired for --sign).')}`,
363
+ ${dim('--onchain-image works on the hot AND wallet (--sign) lanes; the cold lane (--unsigned) is refused everywhere,')}
364
+ ${dim('721 and edition alike — each chunk tx feeds the next, so staging cannot be signed offline in one run.')}`,
331
365
  'deploy-series': `
332
366
  ${bold('abx deploy-series')} --dir <folder> ${dim('— deploy a MULTI-TOKEN collection (one contract, N tokens) from a folder of media. Sends a tx.')}
333
367
  --dir <folder> media directory; files sort → token ids 0,1,2,… (metadata = token id)
334
368
  --count <n> deploy fewer than the folder holds (default: all files) --name <s> --symbol <s> --royalty-bps
335
369
  mint timing: ${g('--mint-all')} (mint every token at deploy) · ${g('--mint-count')} N (first N) · ${g('--no-mint')} (defer)
336
- ${bold('image custody')} — how the artwork is stored + resolved:
337
- ${g('--onchain-image')} [--compress fastlz] bytes ON-CHAIN (chunk store) — tiny art only (~200 gas/byte)
370
+ ${bold('image custody')} — how the work is stored + resolved:
371
+ ${g('--onchain-image')} [--compress fastlz] bytes ON-CHAIN (chunk store) — small content (~200 gas/byte to write, and
372
+ ${dim('~360-405k gas/KB per tokenURI to READ, climbing with size: under ~40KB/token reads routinely, past ~100KB refused —')}
373
+ ${dim('override with')} ${g('--allow-unreadable-onchain')}${dim('. The gate is per token; a big collection of small files reads fine.)')}
338
374
  ${g('--onchain-uri --backend arweave|ipfs|cloud')} image OFF-CHAIN, JSON on-chain via the renderer — ${bold('NO server')};
339
375
  same-extension folders upload as ONE directory/manifest → a single collection url-template (O(1)).
340
376
  ${dim('cloud/S3 also needs --public-base <bucket-or-cdn-url>')}
341
377
  --public-base-url <url> hosted resolver (off-chain custody; you run a node) — for mutable metadata
342
378
  --description "<s>" · --external-url <url> collection identity, served in the metadata (set them or the description is boilerplate)
343
- --artist "<s>" · --license "<s>" authorship + rights, ON-CHAIN collection fields served in contractURI (also --display-notes, --artist-links)
379
+ --creator "<s>" · --license "<s>" authorship + rights, ON-CHAIN collection fields served in contractURI (also --display-notes, --creator-links)
344
380
  ${g('--attributes <file.json>')} ${bold('per-token marketplace traits')} (written ON-CHAIN): a JSON ARRAY indexed by token id, OR an OBJECT
345
381
  keyed by filename / token id — each value an attributes array or a ${dim('{name: value}')} map. Small/medium series;
346
382
  a huge collection should set traits post-deploy via ${g('abx set-field')} under a gas budget.
@@ -375,7 +411,7 @@ const COMMAND_HELP = {
375
411
  seed, so what you iterate on is what deploys. Routes: ${g('/')} studio (seed + params + live traits) · ${g('/grid')} N seeds at once,
376
412
  all live · ${g('/view')} the bare document. The program is re-read from disk per render, so ${bold('edit and refresh')} — no watcher.
377
413
  Unlike a still-image sweep this shows ${bold('animation')}, which is most of what a screenshot throws away.
378
- ${dim('Still do both after the art settles:')} ${g('abx inspect')} ${dim('(is it wired right?) and a testnet deploy (the faithful end-to-end).')}
414
+ ${dim('Still do both after the work settles:')} ${g('abx inspect')} ${dim('(is it wired right?) and a testnet deploy (the faithful end-to-end).')}
379
415
  ${g('abx preview')} --script art.js --schema palette:HexColor:TokenOwner
380
416
  ${g('abx preview')} --script art.js --shoot ./frames --count 12`,
381
417
  inspect: `
@@ -387,7 +423,7 @@ const COMMAND_HELP = {
387
423
  Ends with a recommended lane (fully-on-chain / resolver / directory). Run it BEFORE ${g('abx deploy-code')} to derive the
388
424
  lane instead of guessing. Deep dive: docs/research/onchain-traits-feasibility.md`,
389
425
  'scaffold-renderer': `
390
- ${bold('abx scaffold-renderer')} [<dir>] ${dim('— write a buildable Foundry project for the in-chain Solidity art lane (default dir: ./my-renderer). No tx.')}
426
+ ${bold('abx scaffold-renderer')} [<dir>] ${dim('— write a buildable Foundry project for the in-chain Solidity renderer lane (default dir: ./my-renderer). No tx.')}
391
427
  Writes: ${g('src/MyRenderer.sol')} (an ${bold('image')} renderer — a seed → SVG, tinted by a ${g('palette')} HexColor PostParam), ${g('src/MyTraits.sol')}
392
428
  (an ${bold('attributes')} renderer reading the SAME seed math, so image + traits agree), the two ABX interfaces you implement/read
393
429
  (${g('src/interfaces/')} — read ${bold('IAbxFieldRenderer.sol')} for the five invariants, above all ${bold('never revert')}), a ${g('forge test')} proving
@@ -424,11 +460,11 @@ const COMMAND_HELP = {
424
460
  ${g('key:Type:Address(0x…)')} (a CONTRACT may hold it: the controller pattern for open participation).
425
461
  Append ${g(':lock=<when>')} (ISO date, unix seconds, or ${g('now')}) to freeze the value after that time.
426
462
  ${dim('repeatable OR comma-separable (like --dep): ')}${g('--schema a --schema b')}${dim(' ≡ ')}${g('--schema a,b')}
427
- Type ∈ Bool·Select·Uint256Range·Int256Range·DecimalRange·HexColor·Timestamp·String·Bytes; Auth ∈ Artist·TokenOwner·Address (+ Or-combos).
463
+ Type ∈ Bool·Select·Uint256Range·Int256Range·DecimalRange·HexColor·Timestamp·String·Bytes; Auth ∈ Creator·TokenOwner·Address (+ Or-combos).
428
464
  ${bold('a Select declares its options')}: ${g('mood:Select[Calm|Wild|Chaotic]:TokenOwner')}; ${bold('a Range takes bounds')}: ${g('density:Uint256Range[0..100]:TokenOwner')} (Int/Decimal/Timestamp too; bounds optional)
429
465
  --description "<s>" · --external-url <url> collection identity, written as ON-CHAIN collection fields in the deploy tx
430
- --artist "<s>" · --license "<s>" authorship + rights, ON-CHAIN collection fields served in contractURI (also --display-notes, --artist-links)
431
- ${g('--image-renderer 0x..')} THE in-chain-art lane: bake the on-chain ${g('image')} to a Solidity IAbxFieldRenderer → the SVG is COMPUTED
466
+ --creator "<s>" · --license "<s>" authorship + rights, ON-CHAIN collection fields served in contractURI (also --display-notes, --creator-links)
467
+ ${g('--image-renderer 0x..')} THE in-chain-renderer lane: bake the on-chain ${g('image')} to a Solidity IAbxFieldRenderer → the SVG is COMPUTED
432
468
  on-chain from the token's seed + params (fork ${g('SeedSvgRenderer.sol')}). No ${g('--script')} needed; no bucket, no runner, no
433
469
  resolver — the marketplace still lives in ${g('tokenURI')} itself. Mutually exclusive with ${g('--image-base')} (both set ${g('image')}).
434
470
  ${g('--image-base <url>')} bake the on-chain ${g('image')} as a url-template (${g('{base}/{id}.png')}) — the marketplace thumbnail lives OFF-CHAIN at a
@@ -443,7 +479,15 @@ const COMMAND_HELP = {
443
479
  none known → warned + skipped — never blocks). Registry deps are existence-checked before deploy (best-effort;
444
480
  a CDN-served record is the normal production path, on-chain bytes are the durability floor).
445
481
  --max N supply cap (default 16) mint timing: ${g('--mint-all')} · ${g('--mint-count')} N · (default: defer, then ${g('abx mint')})
446
- --no-seed opt out of the canonical mint-time seed (seeds settle once assigned; curated pre-set seeds win)
482
+ --no-seed opt out of a mint-time seed entirely (seeds settle once assigned; curated pre-set seeds win)
483
+ ${g('--seed-source 0x..')} WHERE the mint seed comes from. Default (omit it) = the canonical ${bold('AbxSeedSource')} we deploy;
484
+ ${g('canonical')} says that out loud. Pass an address for ${bold('your own IAbxSeedSource')} — commit-reveal, a VRF
485
+ oracle, a curated queue. ${bold('Probed before any gas')}: it must answer ${g('seed(uint256,address)')} with 32 bytes,
486
+ else the deploy is refused (a bad source is silent until the first buyer, then every mint reverts).
487
+ ${dim('the canonical seed is PSEUDORANDOM — replayable after the mint, and computable DURING it (a buyer can decline an')}
488
+ ${dim('outcome; on an edition, where the BUYER names the id and the id feeds the seed, they can also shop the unminted')}
489
+ ${dim('ids): right for diversifying output, NOT strong enough for a raffle or prize draw. Need that? that is what')}
490
+ ${dim('--seed-source is for. Change it later with ')}${g('abx set-seed-source')}${dim(' (future mints only).')}
447
491
  ${g('--721c')} [recommended|0x..] opt-in ERC-721C: enroll at deploy, PERMANENTLY, with that transfer validator (absent = plain
448
492
  ERC-721 forever). ${g('recommended')} = OpenSea's creator-fee-enforcement validator. Manage: ${g('abx set-transfer-validator')}
449
493
  --backend ipfs|arweave directory-mode custody for the build upload ${g('--unpaused')} · --minter 0x.. · --primary-payee 0x..
@@ -457,15 +501,17 @@ const COMMAND_HELP = {
457
501
  continuous: ${g('abx deploy-effects --resolver-url <resolver>')} · one-shot: ${g('abx render <addr> --remote <resolver>')} · check it landed: ${g('abx verify <addr>')}
458
502
  ${bold(g('--copies <n|open>'))} ${dim('routes to EditionCode — a generative drop minted as copies (N ids × --copies copies each, open = uncapped).')}
459
503
  --mint-count/--mint-all keep their meaning; new ${g('--mint-amount <n>')} sets copies of EACH premint id (default 1).
460
- --721c enrolls ERC-1155C instead of 721C. ${dim('v1 scope, each refused in code (not silently ignored):')}
461
- ${dim('--code-dir (use --script) · --dep/--dep-registry (ship a self-contained script) · --image-renderer/--attributes-renderer/--image-base ·')}
462
- ${dim('--no-delegation (EditionCode has no TokenOwner-leg delegation to opt out of) · --resume (not wired for editions yet).')}`,
504
+ --721c enrolls ERC-1155C instead of 721C. ${g('--dep')}/${g('--dep-registry')} ${dim('WORK here (on-chain deps resolve on Sepolia,')}
505
+ ${dim('where the registry lives on Base Sepolia the pointer leg is skipped with a warning and the drop is NOT chain-complete).')}
506
+ ${dim('v1 scope, each refused in code (not silently ignored): --code-dir (use --script) ·')}
507
+ ${dim('--image-renderer/--attributes-renderer/--image-base · --no-delegation (EditionCode has no TokenOwner-leg delegation to')}
508
+ ${dim('opt out of) · --resume (not wired for editions yet).')}`,
463
509
  render: `
464
510
  ${bold('abx render')} <address> [tokenId…] ${dim('— render missing stills + traits for a code project (the effect runner repair lane). Read-only on-chain (no tx).')}
465
511
  Renders each ${bold('minted')} token whose still is missing at the CURRENT inputsHash (a param change re-addresses it → a plain render fills it in).
466
512
  No tokenId → sweeps all minted tokens; pass ids (${g('0 1 2')}) to target specific tokens.
467
513
  ${g('--force')} RE-RENDER even when the still already exists — the fix for a bad / blank / timed-out capture
468
- (the art is otherwise deterministic, so a plain render idempotent-skips an existing still). Overwrites it (+ republishes on --remote).
514
+ (the render is otherwise deterministic, so a plain render idempotent-skips an existing still). Overwrites it (+ republishes on --remote).
469
515
  ${g('--remote [name|url]')} register each render with a REMOTE resolver: the bytes go to ${bold('ABX_STORAGE_BACKEND')} (YOU hold them), and
470
516
  POST /v1/effect-artifacts hands the resolver the URL — it redirects there and never proxies. Traits are the
471
517
  exception: they stitch into the token JSON, so their content (≤64KB) goes to the resolver itself.
@@ -510,12 +556,12 @@ const COMMAND_HELP = {
510
556
  Reads the current schema and changes ONLY the lock, so type/auth/bounds/options are carried forward untouched.
511
557
  What it does NOT do: remove the key (a governed key stays governed forever) or erase a value already stored — that
512
558
  value keeps serving in token data. A value written under a TokenOwner/Address leg came from a collector, and the
513
- artist deliberately cannot delete it.
559
+ creator deliberately cannot delete it.
514
560
  ${g('--dry-run')} print the exact tx and send nothing`,
515
561
  'configure-param': `
516
562
  ${bold('abx configure-param')} <address> <tokenId|-> <key> <value> ${dim('— set a PostParam (typed, canonical encode). Sends a tx.')}
517
563
  Reads the on-chain schema for <key> and canonically encodes <value> (${g('#rrggbb')} for HexColor, fixed-decimal ranges,
518
- Select by label, address, bool). Auth is per the schema leg (Artist=owner · TokenOwner — delegate.xyz honored · Address).
564
+ Select by label, address, bool). Auth is per the schema leg (Creator=owner · TokenOwner — delegate.xyz honored · Address).
519
565
  A change ${bold('re-addresses renders')} → the still becomes a placeholder until you re-render (${g('abx render <addr> <id>')} or the runner).
520
566
  ${g('<tokenId> "-"')} CONTRACT scope (owner-only raw setter; schema-less keys only) — the write path of well-known params
521
567
  like ${g('display.gateway')}. ≤31 printable-ASCII chars ride as a literal bytes32; longer takes the data path.
@@ -529,12 +575,35 @@ const COMMAND_HELP = {
529
575
  resolver running the chain watcher (the ${g('abx serve')} default) sees the change on its next poll (~12s)
530
576
  and auto-re-renders on its own. Keep --remote for a watcher-disabled resolver or when seconds matter.
531
577
  signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview (encodes + shows the tx, sends nothing)`,
578
+ 'submit-app': `
579
+ ${bold('abx submit-app')} <collection> ${dim('— list a deployed ABX collection in the App Store. Optional; not part of deploy. Sends txs.')}
580
+ After ${g('abx deploy')} / ${g('deploy-series')} / ${g('deploy-code')} succeeds, offer this. It is an opt-in: factory events can
581
+ find every ABX clone, but only minting into the App Store registry makes it an app entry.
582
+ Does ${bold('not')} reuse the collection's ${g('--name')} / ${g('--description')} — those are NFT metadata. Confirm store copy
583
+ with the creator (what someone can ${bold('do')}), then run this. The connected wallet must own the collection
584
+ (or, on a retry, hold the entry token). ${g('built-on-abx')} is forced; the gated minter checks ${g('isAbxClone')} + owner.
585
+ If the collection is already claimed, skips the mint and only writes params (same as the store's ${g('/update')} page).
586
+ Metadata is split across a few signatures — one multicall of every field exceeds the chain's per-tx gas cap.
587
+ ${g('--name')} <s> listing title (required; ≤100 chars) — not the collection name unless the creator says so
588
+ ${g('--summary')} "<s>" one sentence, what someone can do (required; ≤240)
589
+ ${g('--description')} "<s>" full description (required; ≤2000)
590
+ ${g('--mark')} <s> icon monogram, ≤3 chars (default ${g('A/')})
591
+ ${g('--tone')} acid|coral|blue|violet|amber|mint icon color (default ${g('acid')})
592
+ ${g('--category')} Create|Games|"Physical world"|Records|Coordination|Utilities|"Developer tools" (default ${g('Create')})
593
+ ${g('--stage')} Live|Prototype|Concept (default ${g('Prototype')})
594
+ ${g('--url')} https://… launch URL (optional)
595
+ ${g('--url-label')} "<s>" launch button label (optional)
596
+ ${g('--image')} https://… custom icon URL (optional; otherwise the on-chain SVG from mark/tone)
597
+ ${g('--tags')} a,b,c up to 12 tags
598
+ ${g('--registry')} 0x… App Store registry (default: shipped address for this chain, or ${g('ABX_APP_STORE_REGISTRY')})
599
+ ${g('--minter')} 0x… gated minter (default: shipped address for this chain, or ${g('ABX_APP_STORE_MINTER')})
600
+ signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview (encodes + shows the txs, sends nothing)`,
532
601
  'set-param-hooks': `
533
602
  ${bold('abx set-param-hooks')} <address> ${dim('— wire/clear the three SeriesCode param-lifecycle hooks (owner-only). Sends a tx.')}
534
603
  The three optional hook addresses a PostParams project may wire (${bold('SeriesCode only')} — a 1/1 / plain Series has none):
535
604
  ${g('--configure 0x…')} write-time veto/validator — a governed ${g('configure-param')} reverts if this hook reverts
536
605
  ${g('--augment 0x…')} read-time derivation folded into tokenData — the opt-in to LIVE data (view reads chain per view)
537
- ${g('--transfer 0x…')} ownership-change lifecycle, best-effort — a revert here never blocks the transfer
606
+ ${g('--transfer 0x…')} ownership-change lifecycle, a ${bold('VETO')} — a revert here FAILS the transfer, and mints too (mint = transfer from 0x0)
538
607
  The contract has ${bold('no per-hook setter')} — it writes all three at once — so this READS the current trio and re-sends
539
608
  it with your change applied: ${bold('omit a role to keep it')}, pass an address to set it, or ${g('none')} to clear it. ${g('--clear')} clears all three.
540
609
  Run with ${bold('no flags')} to print the current hooks (mutates nothing).
@@ -557,6 +626,21 @@ const COMMAND_HELP = {
557
626
  'lock-dependencies': `
558
627
  ${bold('abx lock-dependencies')} <address> ${dim('— freeze the dependency set (list + registry pointer) FOREVER (irreversible). Sends a tx.')}
559
628
  signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')}`,
629
+ 'lock-script': `
630
+ ${bold('abx lock-script')} <address> ${dim('— freeze the on-chain program (script chunks) FOREVER (irreversible). Sends a tx.')}
631
+ ${dim('This is the lock that freezes the WORK of a code project — after it, setScriptChunk/removeLastScriptChunk revert.')}
632
+ ${dim('lock-field/lock-uri only freeze metadata; the full set for a code drop is lock-script + lock-dependencies + lock-field/lock-uri.')}
633
+ ${dim('That freezes what THIS contract stores — not necessarily what the token renders: params stay writable, and a')}
634
+ ${dim('Registry dependency fetches its bytes live from a contract the registry owner can change. Say "locked metadata", not "immutable output".')}
635
+ signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')}`,
636
+ 'lock-param-hooks': `
637
+ ${bold('abx lock-param-hooks')} <address> ${dim('— freeze the three param-lifecycle hook addresses FOREVER (irreversible). Sends a tx.')}
638
+ ${dim('The lock aimed at a BUYER, not at metadata: the transfer hook is a VETO (its revert fails a transfer, and a mint too),')}
639
+ ${dim('so until this is sent the owner holds a standing power over whether a collector can sell. After it, set-param-hooks reverts.')}
640
+ ${dim('You give up: arming a transfer veto, arming a configure veto, and re-pointing/clearing the augment hook. There is no way back.')}
641
+ ${dim('With no transfer hook set, this is how you PROVE you can never add one. A hook already set stays live — freezing the SET is not disarming it.')}
642
+ ${dim('Prints the exact three addresses it will freeze before it sends. SeriesCode/EditionCode only. Read it back with `abx state`.')}
643
+ signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview (encodes + shows the tx, sends nothing)`,
560
644
  state: `
561
645
  ${bold('abx state')} <address> ${dim('— read-only, on-chain operational snapshot (no tx, no local index).')}
562
646
  Shows owner · supply (minted / max, nextTokenId) · paused · minter · primary payee · royalty · renderer
@@ -582,7 +666,7 @@ const COMMAND_HELP = {
582
666
  ${dim('minting is sequential (by token id); repeat with different --to for an airdrop to N wallets.')}
583
667
  ${bold('editions')} ${dim('(OneOfOneEdition/EditionImage/EditionCode) mint COPIES of one id instead — --count is refused there:')}
584
668
  ${g('--token-id <n>')} which id to mint copies of. Defaults to 0 on a 1/1-edition (its only id); REQUIRED on
585
- EditionImage/EditionCode (ids are caller-named artworks — see \`abx tokens <addr>\` for existing ones)
669
+ EditionImage/EditionCode (ids are caller-named works — see \`abx tokens <addr>\` for existing ones)
586
670
  ${g('--amount <n>')} copies to mint (default 1)
587
671
  ${dim('--token-id/--amount are refused on a 721 target; --count is refused on an edition — pointed either way.')}
588
672
  signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview`,
@@ -591,7 +675,8 @@ const COMMAND_HELP = {
591
675
  --token <id> default 0. Uses OPENSEA_API_KEY if set, else prints the marketplace links.`,
592
676
  transfer: `
593
677
  ${bold('abx transfer')} <address> ${dim('— move a token to a new holder (settle a sale/gift). Sends a tx.')}
594
- --to 0x.. (required) --token <id> (default 0) signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview
678
+ --to 0x.. (required) --token <id> ${dim('(default 0; ')}${g('--token-id')}${dim(' is accepted as an alias — every other id-taking command spells it that way)')}
679
+ signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview
595
680
  ${bold('editions')} move COPIES of an id, not the whole token: ${g('--amount <n>')} (default 1) + ${g('--from 0x..')} ${dim('(REQUIRED — an')}
596
681
  ${dim('edition id can have many concurrent holders, so there is no single on-chain "the owner" to read the way a 721 ownerOf gives one).')}
597
682
  ${dim('--amount is refused on a 721 target (a token transfers as a whole).')}`,
@@ -634,12 +719,27 @@ const COMMAND_HELP = {
634
719
  'set-royalty': `
635
720
  ${bold('abx set-royalty')} <address> --bps <0-10000> [--receiver 0x..] ${dim('— change the royalty. Sends a tx.')}`,
636
721
  'set-transfer-validator': `
637
- ${bold('abx set-transfer-validator')} <address> <0x..|none|recommended> ${dim('— manage an ERC-721C collection\'s transfer validator. Owner-only. Sends a tx.')}
638
- <0x..> re-point enforcement at that validator (must be a DEPLOYED contract checked before any gas)
722
+ ${bold('abx set-transfer-validator')} <address> <0x..|none|recommended> ${dim('— manage an ERC-721C/1155C collection\'s transfer validator. Owner-only (one exception below). Sends a tx.')}
723
+ <0x..> re-point enforcement at that validator. ${dim('Must be a DEPLOYED contract AND must refuse a call it')}
724
+ ${dim('does not implement — probed before any gas, so a Safe / bare proxy / 7702-delegated EOA is refused:')}
725
+ ${dim('validateTransfer returns nothing, so such an address would enforce NOTHING while every read said ON.')}
639
726
  ${g('none')} suspend enforcement (validator → address(0); the collection STAYS enrolled)
640
727
  ${g('recommended')} the chain's recommended validator (OpenSea's, for creator-fee enforcement)
641
728
  ${dim('Only for a collection that enrolled AT DEPLOY (--721c on the deploy commands) — a plain ERC-721 is refused')}
642
729
  ${dim('up front: enrollment is a deploy-time decision and can never be added (or fully removed) later.')}
730
+ ${dim('Owner-only WHILE THERE IS AN OWNER: on a renounced collection (owner() == 0x0) ANYONE may send `none` to')}
731
+ ${dim('suspend, and NOBODY may arm a validator again — the release valve for a stuck, abandoned collection.')}
732
+ signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview (encodes + shows the tx, sends nothing)`,
733
+ 'set-seed-source': `
734
+ ${bold('abx set-seed-source')} <address> <0x..|canonical|none> ${dim('— re-point where a CODE project draws its mint seeds. Owner-only. Sends a tx.')}
735
+ <0x..> your own ${bold('IAbxSeedSource')} — commit-reveal, a VRF oracle, a curated queue. ${bold('Probed before any gas')}:
736
+ it must answer ${g('seed(uint256,address)')} with 32 bytes, or every mint would revert. Refused if it can't.
737
+ ${g('canonical')} the shared ${bold('AbxSeedSource')} ABX deploys — pseudorandom, and the default at deploy
738
+ ${g('none')} stop drawing seeds (future mints get none; a generative program expecting one renders blank)
739
+ ${dim('FUTURE MINTS ONLY — a seed settles the instant it is assigned, so nothing already minted changes. On a part-sold')}
740
+ ${dim('drop that means the collection spans two sources; the change is public (SeedSourceSet), but early buyers are not told.')}
741
+ ${dim('Code projects only (`abx deploy-code`) — an image 1/1 or Series has no seed source, and one cannot be added.')}
742
+ ${dim('Set it at birth instead with')} ${g('abx deploy-code --seed-source 0x..')}${dim('.')}
643
743
  signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')} preview (encodes + shows the tx, sends nothing)`,
644
744
  attach: `
645
745
  ${bold('abx attach')} <address> <key> <uri> [<key> <uri> …] ${dim('— attach named file(s) to a token. Sends ONE tx.')}
@@ -654,22 +754,25 @@ const COMMAND_HELP = {
654
754
  ${g('--dry-run')} preview the tx, send nothing · signing: ${g('--send')} (default, needs a hot key) · ${g('--sign')} (browser wallet, no key) · ${g('--unsigned')}
655
755
  ${dim('Need the URI first? Upload the file with')} ${g('abx storage upload <path> --backend arweave|ipfs')} ${dim('— it prints the locator to pass here.')}
656
756
  ${dim('mimeType is declared from the URL EXTENSION (…/master.tiff → image/tiff) — give the file a real extension.')}
657
- ${dim('Surfaces in a resolver\'s artifacts listing (/data/<key> fetches it); a bare on-chain tokenURI shows reserved fields + abx_params only —')}
658
- ${dim('configured params DO appear on-chain (as')} ${bold('abx_params')}${dim('); ATTACHMENTS are the surface that needs a resolver.')}
757
+ ${dim('Surfaces in a resolver\'s artifacts listing (/data/<key> fetches it); a bare on-chain tokenURI shows reserved fields only —')}
758
+ ${dim('params are chain STATE, read with')} ${bold('abx tokens')}${dim(' or tokenParamKeys/tokenParam (not projected into tokenURI); ATTACHMENTS need a resolver.')}
659
759
  ${dim('The image/animation are just reserved members of the same manifest. `artifacts` itself is COMPUTED — not settable.')}`,
660
760
  'set-field': `
661
761
  ${bold('abx set-field')} <address> --field <name> ${dim('— set an on-chain metadata field (the low-level primitive). Sends a tx.')}
662
762
  ${dim('To attach an off-chain FILE (the common case), prefer')} ${bold('abx attach')} ${dim('— it auto-picks the representation.')}
663
763
  --field <name> a reserved display field (image · description · animation_url · external_url · attributes ·
664
764
  background_color · …), a collection authorship/rights field with ${g('--collection')}
665
- (artist · display_notes · artist_links · license), OR any custom key (which becomes an
765
+ (creator · display_notes · creator_links · license), OR any custom key (which becomes an
666
766
  ${bold('artifacts')} entry). NOT ${g('artifacts')}/${g('abx_params')}/${g('abx_provenance')} (computed).
667
767
  --text "<s>" store literal UTF-8 ON-CHAIN (representation defaults ${g('inline')}) — NOT for an off-chain URL (use ${bold('attach')})
668
- --file <path> store a FILE on-chain in SSTORE2 chunks behind the shared reader (any size; multi-chunk)
768
+ --file <path> store a FILE on-chain in SSTORE2 chunks behind the shared reader (multi-chunk)
769
+ ${dim('~360-405k gas/KB per tokenURI to READ it back (climbing with size): under ~40KB reads routinely, 40-100KB needs a high-gas RPC,')}
770
+ ${dim('past ~100KB a default node refuses the read (override with')} ${g('--allow-unreadable-onchain')}${dim(').')}
669
771
  ${g('--compress')} none|fastlz|gzip for --file (default none): ${g('fastlz')} = on-chain decode, stays renderable, cheaper storage;
670
772
  ${g('gzip')} = smallest but off-chain decode only (sets the ${g('reader-gzip')} representation)
671
773
  --value 0x.. store raw bytes / hash (representation defaults keccak256)
672
- --representation <r> inline · reader · reader-gzip · keccak256 · arweave · ipfs · url
774
+ --representation <r> ${dim('on-chain bytes:')} inline · inline-gzip · reader · reader-gzip · renderer ${dim('(computed at read)')}
775
+ ${dim('off-chain, verified:')} keccak256 · sha256 ${dim('· locators:')} arweave · ipfs · url · url-template ${dim('({id} → tokenId, so one field addresses a whole directory)')}
673
776
  ${g('--dry-run')} preview the tx, send nothing
674
777
  --collection target collection (ERC-7572) scope · else --token <id> (default 0)`,
675
778
  'lock-field': `
@@ -689,7 +792,9 @@ const COMMAND_HELP = {
689
792
  'lock-uri': `
690
793
  ${bold('abx lock-uri')} <address> ${dim('— freeze the URI config (pointer + renderer) FOREVER. Sends a tx.')}
691
794
  --collection target the collection scope · else the token URI
692
- ${dim('with the fields also locked (lock-field), the resolved metadata is provably immutable.')}`,
795
+ ${dim('with the fields also locked (lock-field), the stored metadata can never change again.')}
796
+ ${dim('Locked metadata is NOT a locked OUTPUT: params have no lock, and a locked dependency POINTER can still resolve')}
797
+ ${dim('to bytes someone else controls. Some tokens live-adapt to chain state on purpose — see')} https://abx.docs.artblocks.io/protocol/owner-powers/`,
693
798
  'set-admin': `
694
799
  ${bold('abx set-admin')} <address> --to 0x.. ${dim('— hand over contract ownership. Sends a tx.')}`,
695
800
  'set-minter': `
@@ -720,10 +825,10 @@ const COMMAND_HELP = {
720
825
  ${dim('overridden is already open (its --copies default from deploy); there is nothing to set.')}
721
826
  ${dim('Refused on a 721 target (use set-max-invocations instead).')} signing: ${g('--send')} (default) · ${g('--sign')} · ${g('--unsigned')} · ${g('--dry-run')}`,
722
827
  'ping-uri': `
723
- ${bold('abx ping-uri')} <address> --token-ids <csv|range> ${dim('— edition-only, PERMISSIONLESS: re-emit the native URI event. Sends tx(s).')}
828
+ ${bold('abx ping-uri')} <address> --token-ids <csv|range> ${dim('— edition-only, OWNER-ONLY: re-emit the native URI event. Sends tx(s).')}
724
829
  Re-emits ERC-1155's native ${g('URI(string,uint256)')} event for the given ids — the fix for a marketplace/indexer that
725
830
  only honors the native event, after a contract-wide re-point (${g('abx set-token-uri')} / ${g('abx set-renderer')}) which
726
- emits only its own config event, not a per-id one. ${dim('(721 has no equivalent verb — ERC-4906 MetadataUpdate covers that case there.)')}
831
+ emits the ERC-4906 range form, not a per-id native event. ${dim('(721 has no equivalent verb — it has no native per-id URI event to re-emit.)')}
727
832
  --token-ids <csv|range> e.g. ${g('0,1,2')} or ${g('0-99')} or mixed ${g('0-9,20,25-30')}
728
833
  ${dim('Permissionless by design — it only re-emits already-public, current truth, so ANY signer may call it (no owner')}
729
834
  ${dim('check, no --for pinning). Chunks large id lists into several transactions (200 ids/tx) — each previews under --dry-run.')}
@@ -826,7 +931,9 @@ const COMMAND_HELP = {
826
931
  ${dim('resolver, because the params store enumerates its own keys on-chain and')} ${g('seed')} ${dim('is a reserved param read by name.')}
827
932
  ${dim('Traits are NOT here: a trait comes from running the script against the seed —')} ${g('abx render')}${dim(' does that.')}
828
933
  ${dim('On an edition (OneOfOneEdition/EditionImage/EditionCode) each row shows')} ${g('supply/maxSupply')} ${dim('instead of an owner')}
829
- ${dim('(an id can have many concurrent holders — not chain-enumerable outside the event log) — the header names the kind.')}`,
934
+ ${dim('(an id can have many concurrent holders — not chain-enumerable outside the event log) — the header names the kind.')}
935
+ ${g('--holder 0x..')} ${dim('editions only: add a')} ${g('held')} ${dim('column —')} ${g('balanceOf(holder, id)')} ${dim('per id, so you CAN ask what one')}
936
+ ${dim('address holds (the question supply/cap cannot answer). Refused on a 721, where the owner column already says it.')}`,
830
937
  contracturi: `
831
938
  ${bold('abx contracturi')} <address> ${dim('— read contractURI() (ERC-7572 collection metadata) from the contract, FOLLOW it, and decode (read-only).')}
832
939
  ${dim('The collection-level counterpart of')} ${g('tokenuri')}${dim('. On-chain lane: decodes the data: URI. Off-chain lane: fetches the')}
@@ -897,12 +1004,12 @@ function help() {
897
1004
  storage override (else uses config): --backend --endpoint --bucket --region --gateway --upload-url
898
1005
  signing: ${g('--send')} hot/env key (default) · ${g('--sign')} wallet page · ${g('--unsigned')} print tx
899
1006
  ${g('--dry-run')} preview (commitment · URIs · mint plan · approvals) without sending or storing — address only WITH --salt
900
- ${bold(g('--copies <n|open>'))} copies of this SAME artwork (ERC-1155 edition) instead of a unique token —
1007
+ ${bold(g('--copies <n|open>'))} copies of this SAME work (ERC-1155 edition) instead of a unique token —
901
1008
  ${g('open')} = uncapped OPEN EDITION, the flagship edition product. See ${g('abx help deploy')}.
902
1009
  ${g('abx deploy-series')} --dir <folder> deploy a ${bold('multi-token collection')} from a folder of media (one contract, N tokens)
903
1010
  mint timing: ${g('--mint-all')} | ${g('--mint-count')} N | ${g('--no-mint')} · --count N --name --symbol --royalty-bps
904
- image custody: ${g('--onchain-image')} (bytes on-chain, tiny art) ·
905
- ${g('--onchain-uri --backend arweave|ipfs|cloud')} (image off-chain, JSON on-chain, ${bold('no server')} — best for most art) ·
1011
+ image custody: ${g('--onchain-image')} (bytes on-chain, tiny content) ·
1012
+ ${g('--onchain-uri --backend arweave|ipfs|cloud')} (image off-chain, JSON on-chain, ${bold('no server')} — best for most projects) ·
906
1013
  --public-base-url <url> (hosted resolver)
907
1014
  --minter 0x.. · --primary-payee 0x.. · --unpaused · ${g('--dry-run')} · see ${g('abx help deploy-series')}
908
1015
  ${bold(g('--copies <n|open>'))} N ids from the folder × copies each (EditionImage) instead of N unique tokens
@@ -924,13 +1031,14 @@ function help() {
924
1031
  ${g('abx preview')} (--script <f> | --code-dir <d>) ${bold('while you are still making it')} — run the program on localhost, live: refresh for new seeds,
925
1032
  drive your PostParams, watch it animate. Same document the generator serves. ${g('--shoot <dir>')} for headless frames. No chain.
926
1033
  ${g('abx inspect')} <script.js> ${bold('before you pick a lane')} — static analysis (traits + on-chain reproducibility, deps, doc size → RPC viability) + a lane recommendation
927
- ${g('abx scaffold-renderer')} [<dir>] write a buildable Foundry project for the ${bold('in-chain Solidity art lane')} (seed + PostParam → on-chain SVG + traits; you forge build/test/deploy)
928
- ${g('abx deploy-code')} (--script <file> | --code-dir <dir> | ${g('--image-renderer 0x..')}) deploy a ${bold('generative / code project')} (on-chain script, a build directory, or a Solidity SVG renderer — in-chain art)
1034
+ ${g('abx scaffold-renderer')} [<dir>] write a buildable Foundry project for the ${bold('in-chain Solidity renderer lane')} (seed + PostParam → on-chain SVG + traits; you forge build/test/deploy)
1035
+ ${g('abx deploy-code')} (--script <file> | --code-dir <dir> | ${g('--image-renderer 0x..')}) deploy a ${bold('generative / code project')} (on-chain script, a build directory, or a Solidity SVG renderer — in-chain rendering)
929
1036
  ${bold('--public-base-url <url>')} OR ${bold('--onchain-uri')} · --schema key:Type:Auth · ${g('--dep')} name@version|0x.. (ordered; index 0 = the runtime) ·
930
1037
  --dep-registry 0x.. · --description "<s>" · --external-url <url> · ${g('--image-base <url>')} (off-chain thumbnails at a deterministic /{id} URL) ·
931
1038
  ${g('--attributes-renderer 0x..')} (traits on-chain) · --max N · ${g('--mint-all')}|${g('--mint-count')} N · --backend ipfs|arweave · ${g('--dry-run')}
932
1039
  ${bold(g('--copies <n|open>'))} a generative drop minted as copies (EditionCode) — v1 scope: --script only, no --dep/--code-dir/--image-renderer
933
1040
  ${g('abx configure-param')} <addr> <id> <key> <value> set a governed PostParam (typed encode; a change re-addresses the render)
1041
+ ${g('abx submit-app')} <collection> list that collection in the ABX App Store (optional, after deploy — mint + catalog copy)
934
1042
  ${g('abx set-schema')} <addr> --schema key:Type:Auth[:lock=<when>] attach or replace ONE key's schema, any time after deploy
935
1043
  ${g('abx retire-param')} <addr> <key> stop all further writes to a param, permanently (the closest thing to removing one)
936
1044
  ${g('abx set-dependency')} <addr> <index> <ref> declare/replace a code dependency (name@version via the registry pointer, or 0x.. on-chain)
@@ -943,21 +1051,26 @@ function help() {
943
1051
  ${bold('owner operations')} ${dim('— operate a project after launch')}
944
1052
  ${g('abx mint')} <addr> issue token #0 flags: [--to 0x.. (default: owner)]
945
1053
  ${dim('edition:')} ${g('--token-id <n>')} (required unless a 1/1-edition) + ${g('--amount <n>')} (copies, default 1) — ${g('--count')} refused
1054
+ ${g('abx submit-app')} <collection> list a deployed collection in the ABX App Store (opt-in; not part of deploy)
1055
+ --name --summary --description [--mark] [--tone] [--category] [--stage] [--url] [--tags]
946
1056
  ${g('abx refresh')} <addr> ask marketplaces to re-index flags: [--token 0]
947
- ${g('abx transfer')} <addr> sell/transfer a token flags: --to 0x.. [--token 0]
1057
+ ${g('abx transfer')} <addr> sell/transfer a token flags: --to 0x.. [--token 0 | --token-id 0]
948
1058
  ${dim('edition:')} ${g('--amount <n>')} copies + ${g('--from 0x..')} (required — many holders, no single on-chain owner)
949
1059
  ${g('abx set-max-supply')} <addr> --token-id <n> --cap <n> edition-only: lower ONE id's ERC-1155 cap (never "open" — caps only decrease)
950
- ${g('abx ping-uri')} <addr> --token-ids <csv|range> edition-only, permissionless: re-emit the native URI event after a re-point
1060
+ ${g('abx ping-uri')} <addr> --token-ids <csv|range> edition-only, owner-only: re-emit the native URI event after a re-point
951
1061
  ${g('abx set-token-uri')} <addr> re-point a token's metadata URI --uri <url>
952
1062
  ${g('abx set-contract-uri')} <addr> re-point the collection URI --uri <url>
953
1063
  ${g('abx migrate')} <addr> move off-chain state to a NEW resolver (verifies parity; no cutover) --from <old> --to <new>
954
1064
  ${g('abx set-royalty')} <addr> change royalty --bps <0-10000> [--receiver 0x..]
955
1065
  ${g('abx set-transfer-validator')} <addr> <0x..|none|recommended> manage an ERC-721C collection's validator (enrolled-at-deploy only)
1066
+ ${g('abx set-seed-source')} <addr> <0x..|canonical|none> re-point a CODE project's mint-seed source (probed; future mints only)
956
1067
  ${g('abx attach')} <addr> <key> <ipfs://…|ar://…|https://…> attach a named file → the token's ${bold('artifacts')} manifest (data plane)
957
1068
  ${g('abx set-field')} <addr> set an on-chain metadata field (low-level) --field <name> (--text "…" | --value 0x..) [--collection]
958
1069
  ${g('abx lock-field')} <addr> freeze a field forever --field <name> [--collection]
959
1070
  ${g('abx set-renderer')} <addr> toggle URI resolution on-chain ↔ off-chain [--off] [--renderer 0x..] [--collection]
960
1071
  ${g('abx lock-uri')} <addr> freeze the URI config (pointer + renderer) forever [--collection]
1072
+ ${g('abx lock-script')} <addr> freeze the on-chain program (script chunks) forever — the lock that freezes a code project's ${bold('work')}
1073
+ ${g('abx lock-param-hooks')} <addr> freeze the three hook addresses forever — the ${bold('transfer hook is a veto')}, so this is the guarantee a buyer reads
961
1074
  ${g('abx set-admin')} <addr> hand over contract ownership --to 0x..
962
1075
  ${dim('Series-only:')} ${g('abx mint')} <addr> mints the ${dim('next')} token in order (or ${g('--count')} N)
963
1076
  ${g('abx pause')} / ${g('abx unpause')} <addr> Series mint gate (paused ⇒ owner-only minting)