@artblocks/abx-cli 0.1.0-alpha.10 → 0.1.0-alpha.11

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/dist/main.js CHANGED
@@ -21,6 +21,8 @@
21
21
  * abx remote [<name|url>] inspect a remote service (descriptor · chains · managed rendering · your projects there)
22
22
  * abx index [<address>] re-index a project from chain (replay; --remote to nudge a remote resolver)
23
23
  * abx verify <address> re-hash served bytes vs the on-chain commitment (no server)
24
+ * abx tokenuri <address> read tokenURI(id) from the contract + decode · contracturi: the ERC-7572 collection JSON
25
+ * (the contract holds the URL — never hand-build a resolver path)
24
26
  * abx configure-param <addr> <id> <key> <value> set a governed PostParam (typed encode; any lane)
25
27
  * abx set-param-hooks <addr> wire/clear a SeriesCode's configure/augment/transfer param hooks
26
28
  * abx render <addr> [id…] effect-runner repair lane: render missing stills/traits
@@ -177,6 +179,7 @@ async function main() {
177
179
  case 'render': return cmdRender(rest[0], rest.slice(1), flags);
178
180
  case 'effects': return cmdEffects(flags);
179
181
  case 'tokenuri': return cmdTokenUri(rest[0], flags);
182
+ case 'contracturi': return cmdContractUri(rest[0], flags);
180
183
  case 'serve': return cmdServe(flags);
181
184
  // owner operations — write + sign (hot/wallet/cold lane), then re-index
182
185
  case 'mint': return cmdMint(rest[0], flags);
@@ -4397,6 +4400,101 @@ async function cmdTokenUri(address, flags) {
4397
4400
  console.log(` ${uri.slice(0, 240)}${uri.length > 240 ? dim(`… (${uri.length} chars)`) : ''}\n`);
4398
4401
  }
4399
4402
  }
4403
+ // ── contracturi ──────────────────────────────────────────────────────────────
4404
+ /**
4405
+ * `abx contracturi <address>` — the collection-level counterpart of `tokenuri`: read
4406
+ * `contractURI()` (ERC-7572) STRAIGHT FROM THE CONTRACT, then FOLLOW it and decode the JSON.
4407
+ *
4408
+ * Why this exists, and why it follows the URL: the resolver's route grammar is committed
4409
+ * on-chain at deploy (`contractURIBase` = `<baseUrl>/c`), so the contract — not a doc, not a
4410
+ * service descriptor — is the source of truth for where a collection's metadata lives. Without
4411
+ * this command the only way to look was to hand-build the URL from memory of the grammar, and a
4412
+ * guessed path that 404s reads exactly like a broken service. Ask the chain instead.
4413
+ */
4414
+ async function cmdContractUri(address, _flags) {
4415
+ if (!address || address.startsWith('--')) {
4416
+ console.error('usage: abx contracturi <address>\n');
4417
+ process.exit(1);
4418
+ }
4419
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
4420
+ console.error(`abx contracturi: '${address}' isn't a 0x contract address.\n`);
4421
+ process.exit(1);
4422
+ }
4423
+ const publicClient = makePublicClient({ chainKey: CHAIN });
4424
+ let uri;
4425
+ try {
4426
+ uri = (await publicClient.readContract({
4427
+ address,
4428
+ abi: oneOfOneImageAbi,
4429
+ functionName: 'contractURI',
4430
+ args: [],
4431
+ }));
4432
+ }
4433
+ catch {
4434
+ const code = await publicClient.getCode({ address }).catch(() => undefined);
4435
+ if (!code || code === '0x') {
4436
+ // Name the endpoint we actually asked — "no contract here" and "you're pointed at the wrong
4437
+ // node" are indistinguishable otherwise (same reasoning as `tokenuri`).
4438
+ console.error(`abx contracturi: no contract at ${address} on ${CHAIN} (asked ${redactRpcUrl(resolveRpcUrl(CHAIN))}) — ` +
4439
+ `double-check the address, and that this endpoint is the network you deployed to. ` +
4440
+ `If you JUST deployed, give the tx a block or two to mine.\n`);
4441
+ }
4442
+ else {
4443
+ console.error(`abx contracturi: ${address} didn't return a contractURI — it may not be an ABX/ERC-7572 contract, or ` +
4444
+ `— on a large on-chain contractURI — an unauthenticated RPC read hit its gas cap (try a wallet-connected / high-gas RPC).\n`);
4445
+ }
4446
+ process.exit(1);
4447
+ }
4448
+ console.log(`\n ${bold('contractURI()')} ${dim(`— read directly from ${address} on ${CHAIN}`)}`);
4449
+ if (!uri) {
4450
+ console.error(`\n ${bold('empty')} — this contract has no contractURI set: no collection-level metadata to resolve. ` +
4451
+ `Set one with ${bold(`abx set-contract-uri ${address} --uri <base>`)}, or point it at the canonical renderer for the on-chain lane.\n`);
4452
+ process.exit(1);
4453
+ }
4454
+ const onChain = decodeOnChainJson(uri);
4455
+ if (onChain) {
4456
+ info('resolution: ON-CHAIN (data: URI from the renderer — no server in the path)');
4457
+ console.log(onChain.split('\n').map((l) => ' ' + l).join('\n') + '\n');
4458
+ return;
4459
+ }
4460
+ console.log(` ${dim('resolves to')} ${uri}`);
4461
+ if (!/^https?:\/\//i.test(uri)) {
4462
+ // ipfs:// / ar:// — a locator, not something we can fetch without choosing a gateway. Print it
4463
+ // rather than silently picking one; the creator's gateway choice is theirs.
4464
+ info(`not an http(s) URL — a ${uri.split(':')[0]}: locator needs a gateway to fetch. Nothing more to read from here.`);
4465
+ console.log('');
4466
+ return;
4467
+ }
4468
+ let body;
4469
+ try {
4470
+ const res = await fetch(uri, { headers: { accept: 'application/json' } });
4471
+ body = await res.text();
4472
+ if (!res.ok) {
4473
+ // The URL came FROM THE CHAIN, so a bad status here is genuinely about the service (or the
4474
+ // contract pointing somewhere stale) — never a mistyped path. Say which, so nobody re-guesses.
4475
+ console.error(`\n ${bold(`HTTP ${res.status}`)} from the contract's own contractURI — the URL is correct by construction (it came from ` +
4476
+ `${address} on-chain), so this is the SERVICE, not the path. Likely: the project isn't registered on that resolver ` +
4477
+ `(${bold('abx add ' + address + ' --remote')}), the node serves a different chain, or it's down. ` +
4478
+ `Response: ${body.slice(0, 200)}\n`);
4479
+ process.exit(1);
4480
+ }
4481
+ }
4482
+ catch (e) {
4483
+ console.error(`\n couldn't reach ${uri} — ${e.message}. The URL is what the contract commits to, so check that the ` +
4484
+ `host is up and publicly reachable (a localhost base URL resolves for no one but this machine).\n`);
4485
+ process.exit(1);
4486
+ }
4487
+ info('resolution: OFF-CHAIN (fetched from the URL the contract commits to)');
4488
+ try {
4489
+ console.log(JSON.stringify(JSON.parse(body), null, 2)
4490
+ .split('\n')
4491
+ .map((l) => ' ' + l)
4492
+ .join('\n') + '\n');
4493
+ }
4494
+ catch {
4495
+ console.log(` ${dim('(not JSON)')} ${body.slice(0, 400)}\n`);
4496
+ }
4497
+ }
4400
4498
  // ── serve ──────────────────────────────────────────────────────────────────--
4401
4499
  async function cmdServe(flags) {
4402
4500
  const port = Number(flags.port ?? process.env.ABX_PORT ?? DEFAULT_PORT);
@@ -5951,7 +6049,14 @@ const COMMAND_HELP = {
5951
6049
  ${dim(`${g('onChainStatus')} (branch · chain-complete · unresolved refs · URL budget) and decodes ${g('tokenURI')} straight from the contract.`)}`,
5952
6050
  tokenuri: `
5953
6051
  ${bold('abx tokenuri')} <address> [--token <id>] ${dim('— read tokenURI(id) straight from the contract on-chain + decode the JSON (read-only; no server).')}
5954
- ${dim('The proof a fully on-chain token self-resolves: any RPC returns the renderer-assembled metadata. Default token 0.')}`,
6052
+ ${dim('The proof a fully on-chain token self-resolves: any RPC returns the renderer-assembled metadata. Default token 0.')}
6053
+ ${dim('Collection-level (ERC-7572) counterpart:')} ${g('abx contracturi <address>')}`,
6054
+ contracturi: `
6055
+ ${bold('abx contracturi')} <address> ${dim('— read contractURI() (ERC-7572 collection metadata) from the contract, FOLLOW it, and decode (read-only).')}
6056
+ ${dim('The collection-level counterpart of')} ${g('tokenuri')}${dim('. On-chain lane: decodes the data: URI. Off-chain lane: fetches the')}
6057
+ ${dim('URL the contract itself commits to and prints the JSON.')}
6058
+ ${bold('Never hand-build a resolver URL to check this')} ${dim('— the contract holds the answer, so a URL from here is right by')}
6059
+ ${dim('construction. A bad status is then about the SERVICE (unregistered project · wrong chain · down), never a mistyped path.')}`,
5955
6060
  forget: `
5956
6061
  ${bold('abx forget')} <address> ${dim('— drop a project’s local registration + projection. On-chain data is untouched.')}
5957
6062
  ${g('--remote [name|url]')} deregister on a REMOTE resolver instead (it stops serving the project; re-add any time)`,
@@ -6022,6 +6127,7 @@ function help() {
6022
6127
  ${g('abx index')} [<address>] re-index from chain (incremental by default; ${g('--full')} forces a replay from deploy)
6023
6128
  ${g('abx verify')} <addr> re-hash served bytes vs the on-chain commitment (no server needed)
6024
6129
  ${g('abx tokenuri')} <addr> read tokenURI(0) on-chain + decode the JSON (proof a self-resolving token works)
6130
+ ${g('abx contracturi')} <addr> read contractURI() (ERC-7572 collection metadata) on-chain, follow it, decode — never hand-build the URL
6025
6131
  ${g('abx state')} <addr> one-glance on-chain snapshot: owner · supply · paused · minter · payee · royalty · renderer
6026
6132
  ${g('abx serve')} [--port ..] serve the token API + dashboard — and WATCH the chain: auto-index every registered
6027
6133
  project + notify the effects layer on change (${g('ABX_WATCH_INTERVAL_MS')}; 0 = off)