@artblocks/abx-cli 0.1.0-alpha.20 → 0.1.0-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1081 -0
- package/assets/renderer-scaffold/README.md +2 -2
- package/assets/renderer-scaffold/src/MyRenderer.sol +2 -2
- package/assets/renderer-scaffold/src/interfaces/IAbxFieldRenderer.sol +1 -1
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +2 -2
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +1 -1
- package/dist/commands/deploy.d.ts +30 -17
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +523 -117
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/project.d.ts +16 -0
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +171 -15
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reads.d.ts.map +1 -1
- package/dist/commands/reads.js +35 -5
- package/dist/commands/reads.js.map +1 -1
- package/dist/commands/scaffold.d.ts +3 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +81 -29
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/service.js +1 -1
- package/dist/commands/service.js.map +1 -1
- package/dist/commands/storage.d.ts.map +1 -1
- package/dist/commands/storage.js +11 -3
- package/dist/commands/storage.js.map +1 -1
- package/dist/commands/submit-app.d.ts +58 -0
- package/dist/commands/submit-app.d.ts.map +1 -0
- package/dist/commands/submit-app.js +512 -0
- package/dist/commands/submit-app.js.map +1 -0
- package/dist/config.d.ts +1 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/flag-allowlists.d.ts +53 -0
- package/dist/flag-allowlists.d.ts.map +1 -0
- package/dist/flag-allowlists.js +152 -0
- package/dist/flag-allowlists.js.map +1 -0
- package/dist/flags.d.ts +6 -2
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +23 -0
- package/dist/flags.js.map +1 -1
- package/dist/kind.d.ts +8 -3
- package/dist/kind.d.ts.map +1 -1
- package/dist/kind.js +25 -0
- package/dist/kind.js.map +1 -1
- package/dist/main.js +166 -53
- package/dist/main.js.map +1 -1
- package/dist/mintpage.d.ts.map +1 -1
- package/dist/mintpage.js +29 -4
- package/dist/mintpage.js.map +1 -1
- package/dist/output.d.ts +32 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +74 -9
- package/dist/output.js.map +1 -1
- package/dist/ownerops.d.ts +101 -13
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +421 -71
- package/dist/ownerops.js.map +1 -1
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +1 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +8 -2
- package/dist/remote.js.map +1 -1
- package/dist/schema.d.ts +18 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +37 -2
- package/dist/schema.js.map +1 -1
- package/dist/served.js +1 -1
- package/dist/update-check.d.ts +25 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +49 -9
- package/dist/update-check.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +64 -32
- package/skill/reference/code-projects.md +98 -22
- package/skill/reference/creator-token.md +16 -3
- package/skill/reference/decisions.md +83 -11
- package/skill/reference/hosting.md +1 -1
- package/skill/reference/operating.md +35 -12
- package/skill/reference/setup.md +1 -1
- package/skill/reference/troubleshooting.md +16 -6
|
@@ -115,6 +115,14 @@ export declare const STATE_ABI: readonly [{
|
|
|
115
115
|
readonly outputs: readonly [{
|
|
116
116
|
readonly type: "address";
|
|
117
117
|
}];
|
|
118
|
+
}, {
|
|
119
|
+
readonly type: "function";
|
|
120
|
+
readonly name: "seedSource";
|
|
121
|
+
readonly stateMutability: "view";
|
|
122
|
+
readonly inputs: readonly [];
|
|
123
|
+
readonly outputs: readonly [{
|
|
124
|
+
readonly type: "address";
|
|
125
|
+
}];
|
|
118
126
|
}];
|
|
119
127
|
/** The edition twin of {@link STATE_ABI} — `owner`/`maxInvocations`/`paused`/`minter`/
|
|
120
128
|
* `primaryPayee`/`tokenURIRenderer`/`royaltyInfo` are shared, unchanged, function names (see
|
|
@@ -222,6 +230,14 @@ export declare const EDITION_STATE_ABI: readonly [{
|
|
|
222
230
|
readonly outputs: readonly [{
|
|
223
231
|
readonly type: "address";
|
|
224
232
|
}];
|
|
233
|
+
}, {
|
|
234
|
+
readonly type: "function";
|
|
235
|
+
readonly name: "seedSource";
|
|
236
|
+
readonly stateMutability: "view";
|
|
237
|
+
readonly inputs: readonly [];
|
|
238
|
+
readonly outputs: readonly [{
|
|
239
|
+
readonly type: "address";
|
|
240
|
+
}];
|
|
225
241
|
}];
|
|
226
242
|
export declare function cmdState(address: Address | undefined, flags: Flags): Promise<void>;
|
|
227
243
|
export declare function cmdStateBody(address: Address, flags: Flags, emit: (p: Record<string, unknown>) => void): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,OAAO,EA4Bb,MAAM,oBAAoB,CAAC;AAgB5B,OAAO,EAAC,KAAK,KAAK,EAA0B,MAAM,aAAa,CAAC;AAMhE,OAAO,EACL,KAAK,YAAY,EAalB,MAAM,cAAc,CAAC;AAQtB,wBAAsB,UAAU,CAAC,KAAK,EAAE,KAAK,iBA+E5C;AAMD,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBA2FtE;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBAoCxE;AAMD,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBAazE;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GACzC,OAAO,CAAC,IAAI,CAAC,CAmQf;AAMD,wBAAsB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,iBA+F3E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CnH;AAqBD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBZ,CAAC;AAEX;;;;;oGAKoG;AACpG,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAepB,CAAC;AAEX,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBASxE;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GACzC,OAAO,CAAC,IAAI,CAAC,CAqKf;AAgHD;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBAqDzE;AAED;0EAC0E;AAC1E,wBAAsB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,iBAgErG;AAKD,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBA2BzE"}
|
package/dist/commands/project.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
8
8
|
import { resolve as resolvePath } from 'node:path';
|
|
9
|
-
import { AUTH_OPTIONS, CREATOR_TOKEN_INTERFACE_ID, PARAM_TYPES, hasOnChainUriLane, isCodeProject, isCurrentFactory, isCurrentOneOfOneEditionFactory, makePublicClient, makeWalletClient, normalizeAttributes, onChainUriReport, parseTraitPairs, predictClone, readParamSchema, readParamSchemaKeys, readSetParamKeys, resolveChain, resolveSeriesCodeFactory, saltFor, saltGuard, sleep, tryReadContract, } from '@artblocks/abx-sdk';
|
|
9
|
+
import { AUTH_OPTIONS, CREATOR_TOKEN_INTERFACE_ID, PARAM_TYPES, hasOnChainUriLane, isCodeProject, isCurrentFactory, isCurrentOneOfOneEditionFactory, makePublicClient, makeWalletClient, normalizeAttributes, onChainUriReport, parseTraitPairs, predictClone, readParamHooks, readParamHooksLocked, readParamSchema, readParamSchemaKeys, readSetParamKeys, resolveChain, resolveSeriesCodeFactory, saltFor, saltGuard, sleep, tryReadContract, } from '@artblocks/abx-sdk';
|
|
10
10
|
import { resolveBackend } from '@artblocks/abx-storage';
|
|
11
11
|
import { currentRenderArtifact, verifyProject } from '@artblocks/abx-token-api';
|
|
12
12
|
import { zeroAddress } from 'viem';
|
|
@@ -15,6 +15,7 @@ import { isDryRun, parseSaltFlag } from '../flags.js';
|
|
|
15
15
|
import { jsonSafe, withJson } from '../jsonout.js';
|
|
16
16
|
import { allowLargeScan, bold, c, detectCanonicalFactory, dim, g, info, ok, registerAndIndexLocally, resolveScanFloor, warn } from '../output.js';
|
|
17
17
|
import { parseCopies } from './deploy.js';
|
|
18
|
+
import { canonicalSeedSource } from '../ownerops.js';
|
|
18
19
|
import { detectTokenKind, describeKind } from '../kind.js';
|
|
19
20
|
import { canonicalLabel, describeRemoteError, indexErrorAction, remoteFlag, reportRemoteIndexing, requireRemoteToken, rollUp, serviceClient, statusLabel, statusLine, statusRow, tokenSourceLabel, } from '../remote.js';
|
|
20
21
|
import { describeSchema } from '../schema.js';
|
|
@@ -66,9 +67,10 @@ export async function cmdPredict(flags) {
|
|
|
66
67
|
}
|
|
67
68
|
// The current-factory guard is the 1/1 impl-version check (721 or its edition twin); the Series/
|
|
68
69
|
// Series-edition/code/code-edition lanes verify their own factory at deploy time instead —
|
|
69
|
-
// EditionCodeFactory mirrors SeriesCodeFactory's stance here (no version probe: a
|
|
70
|
-
// factory
|
|
71
|
-
// catch that the real deploy doesn't
|
|
70
|
+
// EditionCodeFactory mirrors SeriesCodeFactory's stance here (no version probe: a configured code
|
|
71
|
+
// factory with code is always accepted, and one found at its CREATE2 address is bytecode-bound to
|
|
72
|
+
// this build anyway, so there's nothing this check would catch that the real deploy doesn't
|
|
73
|
+
// already). predictDeterministicAddress works on any factory ABI.
|
|
72
74
|
if (lane.cmd === 'deploy') {
|
|
73
75
|
const current = isEdition ? await isCurrentOneOfOneEditionFactory(publicClient, factory) : await isCurrentFactory(publicClient, factory);
|
|
74
76
|
if (!current) {
|
|
@@ -338,6 +340,27 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
338
340
|
info(state.paramHooks?.augmentHook
|
|
339
341
|
? `live data: augment hook ${state.paramHooks.augmentHook} — live view reads chain per view; stills snapshot settled state`
|
|
340
342
|
: 'live data: none (settled params only — the live view makes zero extra chain reads)');
|
|
343
|
+
// Immutability of the WORK itself — the on-chain program (script chunks) and its library set.
|
|
344
|
+
// This is the gap `lock-field`/`lock-uri` don't cover: those freeze metadata, but the owner can
|
|
345
|
+
// still call `setScriptChunk`/`removeLastScriptChunk` until `lock-script` is sent. Report it so a
|
|
346
|
+
// "locked" drop isn't quietly still-mutable in the one place that carries the work.
|
|
347
|
+
verifyReport.script = state.script ? { locked: !!state.script.locked } : null;
|
|
348
|
+
verifyReport.dependencies = state.dependencies
|
|
349
|
+
? { count: state.dependencies.list.length, locked: !!state.dependencies.locked }
|
|
350
|
+
: null;
|
|
351
|
+
emit(verifyReport);
|
|
352
|
+
if (state.script) {
|
|
353
|
+
if (state.script.locked)
|
|
354
|
+
ok('script: locked — the program bytes are frozen permanently (setScriptChunk reverts)');
|
|
355
|
+
else
|
|
356
|
+
console.log(` ${c.orange}⚠${c.reset} script: UNLOCKED — the owner can still change the program. Freeze the work with ${bold(`abx lock-script ${address}`)} ${dim("(lock-field/lock-uri don't cover the script)")}`);
|
|
357
|
+
}
|
|
358
|
+
if (state.dependencies && state.dependencies.list.length > 0) {
|
|
359
|
+
if (state.dependencies.locked)
|
|
360
|
+
ok('dependencies: locked — the library set is frozen');
|
|
361
|
+
else
|
|
362
|
+
info(`dependencies: unlocked — freeze the list + registry pointer with \`abx lock-dependencies ${address}\` (that pins WHICH library each ref means; a Registry ref's bytes still live in the registry)`);
|
|
363
|
+
}
|
|
341
364
|
// The other two param hooks (if wired) — surfaced here since this is where hooks show. Quiet when
|
|
342
365
|
// unset (the common case). Manage all three with `abx set-param-hooks`.
|
|
343
366
|
if (state.paramHooks?.configureHook || state.paramHooks?.transferHook) {
|
|
@@ -346,7 +369,26 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
346
369
|
parts.push(`configure ${state.paramHooks.configureHook}`);
|
|
347
370
|
if (state.paramHooks.transferHook)
|
|
348
371
|
parts.push(`transfer ${state.paramHooks.transferHook}`);
|
|
349
|
-
info(`param hooks: ${parts.join(' · ')} ${dim('(configure = write-time veto · transfer =
|
|
372
|
+
info(`param hooks: ${parts.join(' · ')} ${dim('(configure = write-time veto · transfer = a VETO over transfers AND mints; manage with `abx set-param-hooks`)')}`);
|
|
373
|
+
}
|
|
374
|
+
// The hook lock is reported for every project with the surface, set hooks or not: a frozen empty
|
|
375
|
+
// trio is a real guarantee ("this project can never add a transfer veto"), and an unfrozen one is
|
|
376
|
+
// a live power even when no hook is set today. Folded from `ParamHooksFrozen`.
|
|
377
|
+
if (state.paramHooks) {
|
|
378
|
+
verifyReport.paramHooks = {
|
|
379
|
+
configureHook: state.paramHooks.configureHook,
|
|
380
|
+
augmentHook: state.paramHooks.augmentHook,
|
|
381
|
+
transferHook: state.paramHooks.transferHook,
|
|
382
|
+
locked: state.paramHooks.locked,
|
|
383
|
+
};
|
|
384
|
+
if (state.paramHooks.locked)
|
|
385
|
+
ok('param hooks: frozen — no hook address can change again (no transfer veto can ever be armed)');
|
|
386
|
+
else if (state.paramHooks.transferHook) {
|
|
387
|
+
console.log(` ${c.orange}⚠${c.reset} param hooks: UNLOCKED, and a transfer hook is armed — it can block transfers and mints, and the owner can re-point it. Freeze the set with ${bold(`abx lock-param-hooks ${address}`)}`);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
info(`param hooks: unlocked — no hook is set, but the owner can still add one (a transfer hook can block transfers/mints). Freeze with \`abx lock-param-hooks ${address}\``);
|
|
391
|
+
}
|
|
350
392
|
}
|
|
351
393
|
// This checks THIS machine's store. A render PUBLISHED to a hosted resolver (locator bridge) lives
|
|
352
394
|
// on that resolver, NOT here — so a "not found" below can be a false negative for a hosted drop.
|
|
@@ -391,6 +433,14 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
391
433
|
// what the animation_url actually is. All eth_calls; no server, no local store involved.
|
|
392
434
|
if (hasOnChainUriLane(state)) {
|
|
393
435
|
const client = makePublicClient({ chainKey: CHAIN });
|
|
436
|
+
// Is this actually a PROGRAM, or a static image that merely resolves on-chain? `hasOnChainUriLane`
|
|
437
|
+
// is true whenever a tokenURI renderer is set — which it is for EVERY fully-on-chain project,
|
|
438
|
+
// image or code. So an image drop ran the code-project checks and ended in two ⚠ ("generator
|
|
439
|
+
// reports NO code", "no animation_url") that are the DESIGN for a static image, not a defect.
|
|
440
|
+
// Two cold agents in one sweep flagged those warnings as the worst thing about `verify`: the
|
|
441
|
+
// command the skill points at to answer "did my on-chain drop work?" was ending in warnings on a
|
|
442
|
+
// drop that is completely fine, which teaches people to discount its warnings generally.
|
|
443
|
+
const isCodeProject = state.contractType === 'code' || state.contractType === 'edition-code';
|
|
394
444
|
try {
|
|
395
445
|
const report = await onChainUriReport(client, state, flags.generator);
|
|
396
446
|
const { status, probe } = report;
|
|
@@ -409,8 +459,15 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
409
459
|
info(`on-chain URI lane: generator ${report.generator}${state.tokenURIRenderer ? ` · tokenURI renderer ${state.tokenURIRenderer}` : ' · tokenURIRenderer NOT set (animation field only — tokenURI still resolves off-chain)'}`);
|
|
410
460
|
if (status.branchName === 'template') {
|
|
411
461
|
ok(`generator branch: template — the document assembles from the on-chain script chunks`);
|
|
412
|
-
if (status.chainComplete)
|
|
413
|
-
ok('chain-complete — every dependency resolves to
|
|
462
|
+
if (status.chainComplete) {
|
|
463
|
+
ok('chain-complete — every dependency resolves to on-chain bytes; no server, gateway, or CDN in the graph');
|
|
464
|
+
// chainComplete is a SERVING claim, not an immutability one. A `Registry` dependency is
|
|
465
|
+
// fetched live from the registry contract on every read, so its bytes can change after
|
|
466
|
+
// `lock-dependencies` froze the pointer — and this flag reported `true` on both sides of
|
|
467
|
+
// exactly that swap during the audit. Say what it means rather than letting "complete" read
|
|
468
|
+
// as "finished".
|
|
469
|
+
info(dim(' chain-complete describes WHERE the bytes come from, not that they are frozen: a Registry dependency is re-fetched from the registry on every read, so its bytes can change even with lock-dependencies set. Locks freeze this contract; they cannot freeze another one.'));
|
|
470
|
+
}
|
|
414
471
|
else
|
|
415
472
|
info('not chain-complete — CDN-served or unresolved dependencies (CDN entries serve fine; on-chain bytes are the durability floor)');
|
|
416
473
|
for (const ref of status.unresolvedRefs) {
|
|
@@ -424,17 +481,21 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
424
481
|
else
|
|
425
482
|
info('URL within the 8KB budget');
|
|
426
483
|
}
|
|
427
|
-
else {
|
|
484
|
+
else if (isCodeProject) {
|
|
428
485
|
console.log(` ${c.orange}⚠${c.reset} generator reports NO code (neither script chunks nor a code field) — animation_url degrades to an <!-- abx:no-code --> marker`);
|
|
429
486
|
}
|
|
487
|
+
else {
|
|
488
|
+
// A static image drop has no program by design — stating that is fine, warning about it is not.
|
|
489
|
+
info('no program on this project — a static image drop, so the metadata carries no animation_url (expected, not a gap)');
|
|
490
|
+
}
|
|
430
491
|
if (!probe) {
|
|
431
492
|
info('tokenURI probe skipped — no token minted yet (mint token #0, then re-run)');
|
|
432
493
|
}
|
|
433
494
|
else if (!probe.onChainJson) {
|
|
434
|
-
console.log(` ${c.red}✗${c.reset}
|
|
495
|
+
console.log(` ${c.red}✗${c.reset} ${probe.accessor}(${probe.tokenId}) is NOT an on-chain data:application/json;base64 URI — got "${probe.uriPrefix}…"`);
|
|
435
496
|
}
|
|
436
497
|
else {
|
|
437
|
-
ok(
|
|
498
|
+
ok(`${probe.accessor}(${probe.tokenId}) resolves ON-CHAIN: data:application/json;base64 — decoded from the contract, no server`);
|
|
438
499
|
const a = probe.animation;
|
|
439
500
|
if (a.form === 'data-html') {
|
|
440
501
|
if (a.marker)
|
|
@@ -448,9 +509,10 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
448
509
|
else if (a.form === 'data-other') {
|
|
449
510
|
console.log(` ${c.orange}⚠${c.reset} animation_url is a data: URI but not text/html — got "${a.prefix}…"`);
|
|
450
511
|
}
|
|
451
|
-
else {
|
|
512
|
+
else if (isCodeProject) {
|
|
452
513
|
console.log(` ${c.orange}⚠${c.reset} no animation_url in the on-chain JSON — the generator field may be missing or unrenderable`);
|
|
453
514
|
}
|
|
515
|
+
// else: a static image has no animation_url and is not supposed to — say nothing.
|
|
454
516
|
}
|
|
455
517
|
// The READ-side envelope. Params enumerate on-chain, so the write side is unbounded — but
|
|
456
518
|
// tokenURI and tokenData assemble EVERY enumerated param per call, and that is what grows.
|
|
@@ -466,7 +528,10 @@ export async function cmdVerifyBody(address, flags, emit) {
|
|
|
466
528
|
}
|
|
467
529
|
}
|
|
468
530
|
catch (e) {
|
|
469
|
-
|
|
531
|
+
// First line only. A viem read error carries a multi-line dump (Contract Call / args / Docs /
|
|
532
|
+
// Version) that is meaningless to a creator and buried the actual sentence when this fired.
|
|
533
|
+
const first = String(e.message ?? e).split('\n')[0].trim();
|
|
534
|
+
console.log(` ${c.orange}⚠${c.reset} on-chain URI check unavailable: ${first}`);
|
|
470
535
|
}
|
|
471
536
|
}
|
|
472
537
|
if (renderGap)
|
|
@@ -641,6 +706,22 @@ export async function reportRemoteByteIntegrity(address, remote, base) {
|
|
|
641
706
|
// ── state: a one-glance operational snapshot of a deployed contract, read straight from chain ──
|
|
642
707
|
// Read-only (no tx, no local index). Series-only getters revert on a 1/1, so each read is defensive
|
|
643
708
|
// → the same command works for both. The agent-friendly "what's the state before/after an op" call.
|
|
709
|
+
/**
|
|
710
|
+
* The `seed source` readout — three states, and the distinction between them is the whole reason the
|
|
711
|
+
* line exists. `0x0` is a code project that deliberately draws no mint seed; the canonical address is
|
|
712
|
+
* the shared pseudorandom `AbxSeedSource` (whose properties are documented and NOT lottery-grade);
|
|
713
|
+
* anything else is the creator's own contract, about which ABX knows and claims nothing. A buyer or an
|
|
714
|
+
* agent reading this needs to be able to tell "the standard one" from "someone's custom randomness"
|
|
715
|
+
* at a glance — flattening them to a bare address hides exactly the fact worth surfacing.
|
|
716
|
+
*/
|
|
717
|
+
function describeSeedSource(source) {
|
|
718
|
+
if (source === zeroAddress)
|
|
719
|
+
return dim('none (0x0 — no mint-time seed drawn at mint)');
|
|
720
|
+
if (source.toLowerCase() === canonicalSeedSource(resolveChain(CHAIN).id).toLowerCase()) {
|
|
721
|
+
return `${source} ${dim('(canonical AbxSeedSource — pseudorandom, not lottery-grade)')}`;
|
|
722
|
+
}
|
|
723
|
+
return `${source} ${dim('— CUSTOM IAbxSeedSource (not the canonical one; its randomness properties are the project’s to state)')}`;
|
|
724
|
+
}
|
|
644
725
|
export const STATE_ABI = [
|
|
645
726
|
{ type: 'function', name: 'owner', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
646
727
|
{ type: 'function', name: 'totalSupply', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
|
|
@@ -654,6 +735,10 @@ export const STATE_ABI = [
|
|
|
654
735
|
// ERC-721C (creator token) — ERC-165 advertises ICreatorToken ONLY when enrolled at deploy.
|
|
655
736
|
{ type: 'function', name: 'supportsInterface', stateMutability: 'view', inputs: [{ type: 'bytes4' }], outputs: [{ type: 'bool' }] },
|
|
656
737
|
{ type: 'function', name: 'getTransferValidator', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
738
|
+
// Seed Source extension — composed by CODE projects only, so absent (undefined) on an image token.
|
|
739
|
+
// Shown because it is the one setting an owner can re-point mid-sale that changes what a LATER
|
|
740
|
+
// buyer receives; `SeedSourceSet` puts it on the spine, and this makes it readable in one command.
|
|
741
|
+
{ type: 'function', name: 'seedSource', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
657
742
|
];
|
|
658
743
|
/** The edition twin of {@link STATE_ABI} — `owner`/`maxInvocations`/`paused`/`minter`/
|
|
659
744
|
* `primaryPayee`/`tokenURIRenderer`/`royaltyInfo` are shared, unchanged, function names (see
|
|
@@ -674,6 +759,8 @@ export const EDITION_STATE_ABI = [
|
|
|
674
759
|
// ERC-1155C (creator token) — identical ERC-165 id + getter as 721C; advertised only when enrolled.
|
|
675
760
|
{ type: 'function', name: 'supportsInterface', stateMutability: 'view', inputs: [{ type: 'bytes4' }], outputs: [{ type: 'bool' }] },
|
|
676
761
|
{ type: 'function', name: 'getTransferValidator', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
762
|
+
// Seed Source extension — EditionCode composes it; the image editions don't. See STATE_ABI's note.
|
|
763
|
+
{ type: 'function', name: 'seedSource', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
677
764
|
];
|
|
678
765
|
export async function cmdState(address, flags) {
|
|
679
766
|
if (!address || address.startsWith('--')) {
|
|
@@ -694,7 +781,7 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
694
781
|
return cmdStateEditionBody(address, publicClient, kind, emit);
|
|
695
782
|
// getter absent (extension not composed) or contract has no code → undefined, never a throw.
|
|
696
783
|
const read = (functionName, args = []) => tryReadContract(publicClient, { address, abi: STATE_ABI, functionName, args });
|
|
697
|
-
const [owner, totalSupply, nextTokenId, maxInvocations, paused, minter, primaryPayee, renderer, royalty, creatorToken, transferValidator] = await Promise.all([
|
|
784
|
+
const [owner, totalSupply, nextTokenId, maxInvocations, paused, minter, primaryPayee, renderer, royalty, creatorToken, transferValidator, seedSource] = await Promise.all([
|
|
698
785
|
read('owner'),
|
|
699
786
|
read('totalSupply'),
|
|
700
787
|
read('nextTokenId'),
|
|
@@ -706,6 +793,7 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
706
793
|
read('royaltyInfo', [0n, 10000n]),
|
|
707
794
|
read('supportsInterface', [CREATOR_TOKEN_INTERFACE_ID]),
|
|
708
795
|
read('getTransferValidator'),
|
|
796
|
+
read('seedSource'),
|
|
709
797
|
]);
|
|
710
798
|
if (owner === undefined && totalSupply === undefined) {
|
|
711
799
|
throw new Error(`no ABX contract state at ${address} on ${CHAIN} (no code, or not an ABX token).`);
|
|
@@ -732,6 +820,10 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
732
820
|
royalty: royalty ? { receiver: royalty[0], bps: Number(royalty[1]) } : null,
|
|
733
821
|
creatorToken: !!creatorToken,
|
|
734
822
|
transferValidator: creatorToken ? zeroToNull(transferValidator) : null,
|
|
823
|
+
// `undefined` (no extension — an image token) and `0x0` (a code project that opted out) are
|
|
824
|
+
// different facts; the JSON keeps them apart the same way every other getter here does.
|
|
825
|
+
seedSource: zeroToNull(seedSource),
|
|
826
|
+
hasSeedSource: seedSource !== undefined,
|
|
735
827
|
params: { governed: [], contractScopeSet: [] },
|
|
736
828
|
});
|
|
737
829
|
emit(payload);
|
|
@@ -758,6 +850,8 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
758
850
|
if (creatorToken) {
|
|
759
851
|
info(`721C validator ${transferValidator && transferValidator !== zeroAddress ? transferValidator : `${c.orange}suspended${c.reset} ${dim('(zero — enforcement off; re-enable with `abx set-transfer-validator`)')}`}`);
|
|
760
852
|
}
|
|
853
|
+
if (seedSource !== undefined)
|
|
854
|
+
info(`seed source ${describeSeedSource(seedSource)}`);
|
|
761
855
|
// The PostParam surface, read straight from chain: `paramSchemaKeys()` is the DECLARED (governed)
|
|
762
856
|
// set — append-only, and it includes keys declared but never yet written, which nothing off-chain
|
|
763
857
|
// could otherwise discover — and `contractParamKeys()` is the collection-scope set values. Both
|
|
@@ -786,7 +880,7 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
786
880
|
})));
|
|
787
881
|
emit(payload);
|
|
788
882
|
if (governed.length) {
|
|
789
|
-
console.log(`\n ${bold('PostParams')} ${dim(`${governed.length} governed · collectors/
|
|
883
|
+
console.log(`\n ${bold('PostParams')} ${dim(`${governed.length} governed · collectors/creator set these; the generator injects them`)}`);
|
|
790
884
|
for (const { key, s } of governed) {
|
|
791
885
|
const retired = s.lockAfter !== 0 && s.lockAfter < now;
|
|
792
886
|
const locks = s.lockAfter !== 0 && !retired ? dim(` locks ${new Date(s.lockAfter * 1000).toISOString().slice(0, 10)}`) : '';
|
|
@@ -802,6 +896,36 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
802
896
|
emit(payload);
|
|
803
897
|
info(`${dim('contract-scope params set:')} ${[...set.contract].sort().join(', ')} ${dim('(apply to every token)')}`);
|
|
804
898
|
}
|
|
899
|
+
// The hooks, and whether they can still change. This is a BUYER's read, which is why it lives in
|
|
900
|
+
// `state` next to the validator rather than only in `verify`: the transfer hook is a veto over
|
|
901
|
+
// transfers and mints, so "which hooks, and are they frozen" is exactly the pair someone needs
|
|
902
|
+
// before buying. Printed whenever the surface exists — including with all three unset, because
|
|
903
|
+
// "none, and frozen" is the strongest thing this line can say and it must be visible.
|
|
904
|
+
const hooks = await readParamHooks(publicClient, address);
|
|
905
|
+
if (hooks) {
|
|
906
|
+
const locked = await readParamHooksLocked(publicClient, address);
|
|
907
|
+
const shown = [['configure', hooks.configureHook], ['augment', hooks.augmentHook], ['transfer', hooks.transferHook]]
|
|
908
|
+
.filter(([, a]) => a !== zeroAddress)
|
|
909
|
+
.map(([role, a]) => `${role} ${a}`);
|
|
910
|
+
payload.params.hooks = jsonSafe({
|
|
911
|
+
configureHook: hooks.configureHook === zeroAddress ? null : hooks.configureHook,
|
|
912
|
+
augmentHook: hooks.augmentHook === zeroAddress ? null : hooks.augmentHook,
|
|
913
|
+
transferHook: hooks.transferHook === zeroAddress ? null : hooks.transferHook,
|
|
914
|
+
// `null` = could not be established (see readParamHooksLocked). Never collapse it to false:
|
|
915
|
+
// "unknown" and "the owner can still arm a transfer veto" are different answers.
|
|
916
|
+
locked: locked ?? null,
|
|
917
|
+
});
|
|
918
|
+
emit(payload);
|
|
919
|
+
const lockNote = locked === true
|
|
920
|
+
? `${c.green}frozen${c.reset} ${dim('— no hook address can ever change again')}`
|
|
921
|
+
: locked === false
|
|
922
|
+
? `${c.orange}not frozen${c.reset} ${dim('— the owner can re-point these (`abx lock-param-hooks` freezes them forever)')}`
|
|
923
|
+
: dim('freeze state unknown (the node refused the check)');
|
|
924
|
+
info(`param hooks ${shown.length ? shown.join(' · ') : dim('none set')} ${lockNote}`);
|
|
925
|
+
if (hooks.transferHook !== zeroAddress) {
|
|
926
|
+
info(dim(` the transfer hook is a VETO: if it reverts, the transfer fails — and mints too (mint = transfer from 0x0).`));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
805
929
|
}
|
|
806
930
|
catch {
|
|
807
931
|
/* best-effort — a non-code project has no params surface to read */
|
|
@@ -817,7 +941,7 @@ export async function cmdStateBody(address, flags, emit) {
|
|
|
817
941
|
*/
|
|
818
942
|
async function cmdStateEditionBody(address, publicClient, kind, emit) {
|
|
819
943
|
const read = (functionName, args = []) => tryReadContract(publicClient, { address, abi: EDITION_STATE_ABI, functionName, args });
|
|
820
|
-
const [owner, maxInvocations, paused, minter, primaryPayee, renderer, royalty, creatorToken, transferValidator] = await Promise.all([
|
|
944
|
+
const [owner, maxInvocations, paused, minter, primaryPayee, renderer, royalty, creatorToken, transferValidator, seedSource] = await Promise.all([
|
|
821
945
|
read('owner'),
|
|
822
946
|
read('maxInvocations'), // absent on a 1/1-edition — its id space is fixed to {0}
|
|
823
947
|
read('paused'),
|
|
@@ -827,6 +951,7 @@ async function cmdStateEditionBody(address, publicClient, kind, emit) {
|
|
|
827
951
|
read('royaltyInfo', [0n, 10000n]),
|
|
828
952
|
read('supportsInterface', [CREATOR_TOKEN_INTERFACE_ID]),
|
|
829
953
|
read('getTransferValidator'),
|
|
954
|
+
read('seedSource'),
|
|
830
955
|
]);
|
|
831
956
|
if (owner === undefined)
|
|
832
957
|
throw new Error(`no ABX contract state at ${address} on ${CHAIN} (no code, or not an ABX token).`);
|
|
@@ -853,6 +978,8 @@ async function cmdStateEditionBody(address, publicClient, kind, emit) {
|
|
|
853
978
|
royalty: royalty ? { receiver: royalty[0], bps: Number(royalty[1]) } : null,
|
|
854
979
|
creatorToken: !!creatorToken,
|
|
855
980
|
transferValidator: creatorToken ? zeroToNull(transferValidator) : null,
|
|
981
|
+
seedSource: zeroToNull(seedSource),
|
|
982
|
+
hasSeedSource: seedSource !== undefined,
|
|
856
983
|
});
|
|
857
984
|
emit(payload);
|
|
858
985
|
console.log(bold(`\n ${describeKind(kind)} state`) + dim(` ${address} · ${CHAIN}`));
|
|
@@ -875,6 +1002,35 @@ async function cmdStateEditionBody(address, publicClient, kind, emit) {
|
|
|
875
1002
|
if (creatorToken) {
|
|
876
1003
|
info(`1155C validator ${transferValidator && transferValidator !== zeroAddress ? transferValidator : `${c.orange}suspended${c.reset} ${dim('(zero — enforcement off; re-enable with `abx set-transfer-validator`)')}`}`);
|
|
877
1004
|
}
|
|
1005
|
+
if (seedSource !== undefined)
|
|
1006
|
+
info(`seed source ${describeSeedSource(seedSource)}`);
|
|
1007
|
+
// EditionCode composes ConfigurableParams exactly like SeriesCode, so an edition has hooks too —
|
|
1008
|
+
// and the transfer hook's veto reaches a mint, which on this lane is a buyer picking an id. Report
|
|
1009
|
+
// the same pair as the 721 body (which hooks, and whether they can still change); silent for the
|
|
1010
|
+
// image/1-of-1 edition twins, which have no params surface at all.
|
|
1011
|
+
const hooks = await readParamHooks(publicClient, address);
|
|
1012
|
+
if (hooks) {
|
|
1013
|
+
const locked = await readParamHooksLocked(publicClient, address);
|
|
1014
|
+
const shown = [['configure', hooks.configureHook], ['augment', hooks.augmentHook], ['transfer', hooks.transferHook]]
|
|
1015
|
+
.filter(([, a]) => a !== zeroAddress)
|
|
1016
|
+
.map(([role, a]) => `${role} ${a}`);
|
|
1017
|
+
payload.paramHooks = jsonSafe({
|
|
1018
|
+
configureHook: hooks.configureHook === zeroAddress ? null : hooks.configureHook,
|
|
1019
|
+
augmentHook: hooks.augmentHook === zeroAddress ? null : hooks.augmentHook,
|
|
1020
|
+
transferHook: hooks.transferHook === zeroAddress ? null : hooks.transferHook,
|
|
1021
|
+
locked: locked ?? null, // null = unknown; never collapse it to false
|
|
1022
|
+
});
|
|
1023
|
+
emit(payload);
|
|
1024
|
+
const lockNote = locked === true
|
|
1025
|
+
? `${c.green}frozen${c.reset} ${dim('— no hook address can ever change again')}`
|
|
1026
|
+
: locked === false
|
|
1027
|
+
? `${c.orange}not frozen${c.reset} ${dim('— the owner can re-point these (`abx lock-param-hooks` freezes them forever)')}`
|
|
1028
|
+
: dim('freeze state unknown (the node refused the check)');
|
|
1029
|
+
info(`param hooks ${shown.length ? shown.join(' · ') : dim('none set')} ${lockNote}`);
|
|
1030
|
+
if (hooks.transferHook !== zeroAddress) {
|
|
1031
|
+
info(dim(` the transfer hook is a VETO: if it reverts, the transfer fails — and mints too (mint = transfer from 0x0).`));
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
878
1034
|
console.log('');
|
|
879
1035
|
}
|
|
880
1036
|
/**
|