@artblocks/abx-cli 0.1.0-alpha.53 → 0.1.0-alpha.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @artblocks/abx-cli
2
2
 
3
+ ## 0.1.0-alpha.55
4
+
5
+ ### Patch Changes
6
+
7
+ - 8ccbede: Enable Robinhood Chain production as beta after canonical deployment, paired-testnet qualification,
8
+ and managed-service validation. Surface the same real-funds warning and testnet-first gate used for
9
+ other production beta networks.
10
+ - Updated dependencies [8ccbede]
11
+ - @artblocks/abx-sdk@0.1.0-alpha.41
12
+ - @artblocks/abx-indexer@0.1.0-alpha.42
13
+ - @artblocks/abx-storage@0.1.0-alpha.41
14
+ - @artblocks/abx-token-api@0.1.0-alpha.44
15
+
16
+ ## 0.1.0-alpha.54
17
+
18
+ ### Patch Changes
19
+
20
+ - f9d7a44: Clarify fully on-chain image deployment tradeoffs and make static-project verification report
21
+ `onChainUri.chainComplete` as not applicable instead of false.
22
+ - ee24497: Add machine-readable `--json` output to `contracturi` and `add`. Collection metadata reads now emit
23
+ the resolved document alone, while registration reports identify the target surface, chain and
24
+ address, scan floor, lifecycle status, and whether remote catch-up is complete or still backfilling.
25
+ - Updated dependencies [b9197bc]
26
+ - @artblocks/abx-sdk@0.1.0-alpha.40
27
+ - @artblocks/abx-indexer@0.1.0-alpha.41
28
+ - @artblocks/abx-storage@0.1.0-alpha.40
29
+ - @artblocks/abx-token-api@0.1.0-alpha.43
30
+
3
31
  ## 0.1.0-alpha.53
4
32
 
5
33
  ### Patch Changes
@@ -947,7 +947,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
947
947
  })()
948
948
  : 1;
949
949
  if (dryRun && onchainImage && imageEndsUpOnChain(flags, false)) {
950
- info(`${bold('one-transaction alternative:')} this SVG can be inlined with bare ${bold('--onchain-uri')} instead of staged with ` +
950
+ warn(`${bold('one-transaction alternative:')} this SVG can be inlined with bare ${bold('--onchain-uri')} instead of staged with ` +
951
951
  `${bold('--onchain-image')} (${approvals} transactions here). Dry-run that form before changing lanes.`);
952
952
  }
953
953
  // Mint-on-deploy is the default; --no-mint defers it so you can stand up + warm
@@ -2,6 +2,29 @@ import { type Address, type PublicClient, type MetadataField } from '@artblocks/
2
2
  import { type Flags } from '../flags.js';
3
3
  import { type RemoteTarget } from '../remote.js';
4
4
  export declare function cmdPredict(flags: Flags): Promise<void>;
5
+ export interface AddJsonResult extends Record<string, unknown> {
6
+ target: {
7
+ surface: 'local' | 'remote';
8
+ name: string | null;
9
+ url: string | null;
10
+ };
11
+ chainId: number;
12
+ address: Address;
13
+ status: string;
14
+ scanFloor: string;
15
+ completed: boolean;
16
+ backfilling: boolean;
17
+ eventCount?: number;
18
+ tokenCount?: number;
19
+ }
20
+ /** Keep the local lane's machine contract independent of the indexer's larger state object. */
21
+ export declare function localAddJsonResult(input: {
22
+ chainId: number;
23
+ address: Address;
24
+ scanFloor: string;
25
+ eventCount: number;
26
+ tokenCount: number;
27
+ }): AddJsonResult;
5
28
  export declare function cmdAdd(address: Address | undefined, flags: Flags): Promise<void>;
6
29
  export declare function cmdIndex(address: Address | undefined, flags: Flags): Promise<void>;
7
30
  export declare function cmdVerify(address: Address | undefined, flags: Flags): Promise<void>;
@@ -42,6 +65,10 @@ export declare function onChainImageSource(fields: MetadataField[]): string | nu
42
65
  * "no commitment", a different fact from "an unrecomputable one".
43
66
  */
44
67
  export declare function pointerOnlyImageCheck(fields: MetadataField[]): string | null;
68
+ /** `chainComplete` describes a code template's dependency graph. Static projects have no code
69
+ * graph to classify, so their structured verification result must say "not applicable" instead
70
+ * of reporting a confident `false` that reads like a failed durability check. */
71
+ export declare function verifyChainComplete(isCodeProject: boolean, chainComplete: boolean): boolean | null;
45
72
  export declare function cmdVerifyBody(address: Address, flags: Flags, emit: (p: Record<string, unknown>) => void): Promise<void>;
46
73
  export declare function cmdVerifyRemote(address: Address, remote: RemoteTarget, emit: (p: Record<string, unknown>) => void): Promise<void>;
47
74
  /** What {@link reportRemoteByteIntegrity} found, structured for the JSON payload as well as its own
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,OAAO,EAGZ,KAAK,YAAY,EAkCjB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAgB5B,OAAO,EAAC,KAAK,KAAK,EAA6C,MAAM,aAAa,CAAC;AAOnF,OAAO,EACL,KAAK,YAAY,EAalB,MAAM,cAAc,CAAC;AAQtB,wBAAsB,UAAU,CAAC,KAAK,EAAE,KAAK,iBA+E5C;AAMD,MAAM,WAAW,aAAc,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5D,MAAM,EAAE;QAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,CAAC;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,aAAa,CAYhB;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBAgItE;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBA0CxE;AAMD,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,iBAmBzE;AAiBD,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAErF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,mBAAmB,CAuBtB;AAYD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,IAAI,CAIzE;AAcD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,IAAI,CAI5E;AAED;;iFAEiF;AACjF,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAElG;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,CAuWf;AAMD,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GACzC,OAAO,CAAC,IAAI,CAAC,CAwKf;AAED;;;;;8EAK8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,IAAI,GAAG,UAAU,GAAG,gBAAgB,GAAG,aAAa,CAAC;IAC7D,MAAM,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;CACnE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,sBAAsB,GAAE,MAAM,EAAO,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAsD9B;AAqBD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BZ,CAAC;AAEX;;;;;oGAKoG;AACpG,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwBpB,CAAC;AA4HX;;;qEAGqE;AACrE,KAAK,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;AAwB/B,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,QAAQ,CAAC;IACtB,sGAAsG;IACtG,MAAM,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAC,EAAE,CAAC;IAC5C;;mFAE+E;IAC/E,MAAM,EAAE;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAC,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAC,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,SAAS,OAAO,EAAE,EACvB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,eAAe,CAAC,CAgB1B;AAiED,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,CAoNf;AAgLD;;;;;;;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"}
@@ -99,10 +99,20 @@ export async function cmdPredict(flags) {
99
99
  info(dim('(this is the 1/1 lane; a Series/code drop uses a different factory → a different address — pass --dir / --script to predict those, or use that command\'s --dry-run)'));
100
100
  console.log('');
101
101
  }
102
- // ── add ──────────────────────────────────────────────────────────────────────
103
- // Register + index a project this node didn't deploy. LOCAL by default (this
104
- // machine's store); `--remote [url]` instead tells a HOSTED resolver to index it —
105
- // the bridge a local deploy can't make on its own (separate projection stores).
102
+ /** Keep the local lane's machine contract independent of the indexer's larger state object. */
103
+ export function localAddJsonResult(input) {
104
+ return {
105
+ target: { surface: 'local', name: null, url: null },
106
+ chainId: input.chainId,
107
+ address: input.address,
108
+ status: input.eventCount > 0 ? 'live' : 'empty',
109
+ scanFloor: input.scanFloor,
110
+ completed: true,
111
+ backfilling: false,
112
+ eventCount: input.eventCount,
113
+ tokenCount: input.tokenCount,
114
+ };
115
+ }
106
116
  export async function cmdAdd(address, flags) {
107
117
  if (!address || address.startsWith('--')) {
108
118
  console.error('usage: abx add <address> [--from-block N] [--factory 0x..] [--label "..."] [--remote [name|url]]\n');
@@ -118,103 +128,123 @@ export async function cmdAdd(address, flags) {
118
128
  'Nothing here touches the chain, but it does write. To look before acting: `abx state <address>` (on-chain snapshot) ' +
119
129
  'or `abx status <address> [--remote <name>]` (what a node already has). Re-run without --dry-run when you mean it.');
120
130
  }
121
- // `--attributes` is lane-aware here exactly as at deploy: a PER-TOKEN payload edits a Series'
122
- // per-token off-chain traits; a flat payload (+ `--traits`) edits the collection/1-of-1 `attributes`.
123
- // Ambiguity defaults to flat (see looksPerTokenAttributes), so a 1/1 add is never mis-read.
124
- const attrRaw = flags.attributes ? readFileSync(resolvePath(process.cwd(), String(flags.attributes)), 'utf8') : undefined;
125
- const perTokenEdit = attrRaw != null && looksPerTokenAttributes(attrRaw);
126
- const flagTraits = [];
127
- if (attrRaw != null && !perTokenEdit)
128
- flagTraits.push(...normalizeAttributes(JSON.parse(attrRaw)));
129
- if (flags.traits)
130
- flagTraits.push(...parseTraitPairs(flags.traits));
131
- const editedTokenAttributes = perTokenEdit ? parseSeriesTraitsById(attrRaw) : undefined;
132
- const remote = remoteFlag(flags);
133
- if (remote) {
134
- requireRemoteToken(remote);
135
- // Bridge what a remote resolver can't derive itself: the off-chain traits and the durable
136
- // content locators (ipfs://…). Prefer flags; otherwise forward what the LOCAL deploy stored
137
- // (the local registration), and compute locators from this machine's content index if needed.
138
- const localReg = localIndexer().store.getRegistration(address);
139
- const attributes = flagTraits.length
140
- ? flagTraits
141
- : localReg?.attributes
142
- ? normalizeAttributes(JSON.parse(localReg.attributes))
143
- : undefined;
144
- // Bridge a Series' per-token off-chain traits to the remote resolver (the resolver has no other
145
- // way to derive them — they're operator metadata, not chain state). A fresh per-token `--attributes`
146
- // EDITS them; otherwise forward what the LOCAL deploy stored. Best-effort parse.
147
- let tokenAttributes;
148
- if (editedTokenAttributes && Object.keys(editedTokenAttributes).length) {
149
- tokenAttributes = editedTokenAttributes;
150
- }
151
- else if (localReg?.tokenAttributes) {
152
- try {
153
- const obj = JSON.parse(localReg.tokenAttributes);
154
- const norm = {};
155
- for (const [id, v] of Object.entries(obj)) {
156
- const a = normalizeAttributes(v);
157
- if (a.length)
158
- norm[id] = a;
131
+ return withJson(flags, async (emit) => {
132
+ // `--attributes` is lane-aware here exactly as at deploy: a PER-TOKEN payload edits a Series'
133
+ // per-token off-chain traits; a flat payload (+ `--traits`) edits the collection/1-of-1 `attributes`.
134
+ // Ambiguity defaults to flat (see looksPerTokenAttributes), so a 1/1 add is never mis-read.
135
+ const attrRaw = flags.attributes ? readFileSync(resolvePath(process.cwd(), String(flags.attributes)), 'utf8') : undefined;
136
+ const perTokenEdit = attrRaw != null && looksPerTokenAttributes(attrRaw);
137
+ const flagTraits = [];
138
+ if (attrRaw != null && !perTokenEdit)
139
+ flagTraits.push(...normalizeAttributes(JSON.parse(attrRaw)));
140
+ if (flags.traits)
141
+ flagTraits.push(...parseTraitPairs(flags.traits));
142
+ const editedTokenAttributes = perTokenEdit ? parseSeriesTraitsById(attrRaw) : undefined;
143
+ const remote = remoteFlag(flags);
144
+ if (remote) {
145
+ requireRemoteToken(remote);
146
+ // Bridge what a remote resolver can't derive itself: the off-chain traits and the durable
147
+ // content locators (ipfs://…). Prefer flags; otherwise forward what the LOCAL deploy stored
148
+ // (the local registration), and compute locators from this machine's content index if needed.
149
+ const localReg = localIndexer().store.getRegistration(address);
150
+ const attributes = flagTraits.length
151
+ ? flagTraits
152
+ : localReg?.attributes
153
+ ? normalizeAttributes(JSON.parse(localReg.attributes))
154
+ : undefined;
155
+ // Bridge a Series' per-token off-chain traits to the remote resolver (the resolver has no other
156
+ // way to derive them — they're operator metadata, not chain state). A fresh per-token `--attributes`
157
+ // EDITS them; otherwise forward what the LOCAL deploy stored. Best-effort parse.
158
+ let tokenAttributes;
159
+ if (editedTokenAttributes && Object.keys(editedTokenAttributes).length) {
160
+ tokenAttributes = editedTokenAttributes;
161
+ }
162
+ else if (localReg?.tokenAttributes) {
163
+ try {
164
+ const obj = JSON.parse(localReg.tokenAttributes);
165
+ const norm = {};
166
+ for (const [id, v] of Object.entries(obj)) {
167
+ const a = normalizeAttributes(v);
168
+ if (a.length)
169
+ norm[id] = a;
170
+ }
171
+ if (Object.keys(norm).length)
172
+ tokenAttributes = norm;
159
173
  }
160
- if (Object.keys(norm).length)
161
- tokenAttributes = norm;
174
+ catch { /* skip a malformed local column */ }
162
175
  }
163
- catch { /* skip a malformed local column */ }
164
- }
165
- const contentLocators = await remoteLocators(address, localReg?.contentLocators, flags);
166
- // Forward the deploy block, like every other field falls back to the local registration.
167
- // (Its ABSENCE here was the bug: a hosted resolver defaulted to genesis and scanned the whole
168
- // chain.) Re-sending the same floor stays incremental server-side, so a nudge ≠ a re-scan.
169
- const body = {
170
- chainId: resolveChain(CHAIN).id,
171
- address,
172
- fromBlock: await resolveScanFloor(address, localReg?.fromBlock, flags),
173
- factory: await detectCanonicalFactory(address, flags.factory, localReg?.factory),
174
- label: flags.label,
175
- description: flags.description ?? localReg?.description,
176
- externalUrl: flags['external-url'] ?? localReg?.externalUrl,
177
- attributes,
178
- tokenAttributes,
179
- contentLocators: Object.keys(contentLocators).length ? contentLocators : undefined,
180
- full: flags.full ? true : undefined,
181
- };
182
- info(`${bold('REMOTE')} → ${remote.url} ${dim('(registering with the remote resolver — NOT this machine)')}`);
183
- if (body.contentLocators)
184
- info(`bridging image locator → ${Object.values(body.contentLocators)[0]} ${dim('(so the resolver points at IPFS, not its own localhost)')}`);
185
- let r;
186
- try {
187
- r = await serviceClient(remote).registerProject(body);
188
- }
189
- catch (err) {
190
- throw describeRemoteError(err, remote, 'remote add');
176
+ const contentLocators = await remoteLocators(address, localReg?.contentLocators, flags);
177
+ // Forward the deploy block, like every other field falls back to the local registration.
178
+ // (Its ABSENCE here was the bug: a hosted resolver defaulted to genesis and scanned the whole
179
+ // chain.) Re-sending the same floor stays incremental server-side, so a nudge ≠ a re-scan.
180
+ const body = {
181
+ chainId: resolveChain(CHAIN).id,
182
+ address,
183
+ fromBlock: await resolveScanFloor(address, localReg?.fromBlock, flags),
184
+ factory: await detectCanonicalFactory(address, flags.factory, localReg?.factory),
185
+ label: flags.label,
186
+ description: flags.description ?? localReg?.description,
187
+ externalUrl: flags['external-url'] ?? localReg?.externalUrl,
188
+ attributes,
189
+ tokenAttributes,
190
+ contentLocators: Object.keys(contentLocators).length ? contentLocators : undefined,
191
+ full: flags.full ? true : undefined,
192
+ };
193
+ info(`${bold('REMOTE')} → ${remote.url} ${dim('(registering with the remote resolver — NOT this machine)')}`);
194
+ if (body.contentLocators)
195
+ info(`bridging image locator → ${Object.values(body.contentLocators)[0]} ${dim('(so the resolver points at IPFS, not its own localhost)')}`);
196
+ let r;
197
+ try {
198
+ r = await serviceClient(remote).registerProject(body);
199
+ }
200
+ catch (err) {
201
+ throw describeRemoteError(err, remote, 'remote add');
202
+ }
203
+ const outcome = await reportRemoteIndexing(remote, body.chainId, address, r, flags, 'indexed');
204
+ info(`it now serves ${remote.url}/t/${body.chainId}/${address.toLowerCase()}/0`);
205
+ // "Indexed" is not "correct". This line proves the service replayed the chain and will answer at
206
+ // that URL; it says nothing about whether the bytes it serves match the on-chain commitment.
207
+ // Name the step that checks.
208
+ info(`confirm what it actually serves (bytes vs. the on-chain hash): ${bold(`abx verify ${address} --remote ${remote.name?.toLowerCase() ?? remote.url}`)}`);
209
+ // Say what this registration did NOT buy. A project whose `tokenURIRenderer` is set answers
210
+ // `tokenURI` from the chain, so marketplaces and wallets read THAT document and never touch this
211
+ // service — the register is still useful (indexing, the live view, managed rendering for a code
212
+ // drop) but it changes nothing a collector sees. Without this line the readout is a list of
213
+ // successes that reads like a win, and a creator who was told "put it on a hosted service so it
214
+ // shows up properly" concludes their problem is solved when nothing about it moved.
215
+ const onChainRenderer = await tryReadContract(makePublicClient({ chainKey: CHAIN }), {
216
+ address,
217
+ abi: STATE_ABI,
218
+ functionName: 'tokenURIRenderer',
219
+ });
220
+ if (onChainRenderer && onChainRenderer !== zeroAddress) {
221
+ info(`${dim('note:')} this project resolves ${bold('tokenURI from the chain')} (renderer ${onChainRenderer}) — marketplaces read that, not this service. ` +
222
+ `Registering still gives you indexing, the live view and managed rendering, but it does not change the metadata a collector sees.`);
223
+ }
224
+ emit({
225
+ target: { surface: 'remote', name: remote.name ?? null, url: remote.url },
226
+ chainId: body.chainId,
227
+ address,
228
+ status: outcome.status,
229
+ scanFloor: body.fromBlock ?? '0',
230
+ completed: outcome.completed,
231
+ backfilling: outcome.backfilling,
232
+ ...(outcome.eventCount === undefined ? {} : { eventCount: outcome.eventCount }),
233
+ ...(outcome.tokenCount === undefined ? {} : { tokenCount: outcome.tokenCount }),
234
+ });
235
+ return;
191
236
  }
192
- await reportRemoteIndexing(remote, body.chainId, address, r, flags, 'indexed');
193
- info(`it now serves ${remote.url}/t/${body.chainId}/${address.toLowerCase()}/0`);
194
- // "Indexed" is not "correct". This line proves the service replayed the chain and will answer at
195
- // that URL; it says nothing about whether the bytes it serves match the on-chain commitment.
196
- // Name the step that checks.
197
- info(`confirm what it actually serves (bytes vs. the on-chain hash): ${bold(`abx verify ${address} --remote ${remote.name?.toLowerCase() ?? remote.url}`)}`);
198
- // Say what this registration did NOT buy. A project whose `tokenURIRenderer` is set answers
199
- // `tokenURI` from the chain, so marketplaces and wallets read THAT document and never touch this
200
- // service — the register is still useful (indexing, the live view, managed rendering for a code
201
- // drop) but it changes nothing a collector sees. Without this line the readout is a list of
202
- // successes that reads like a win, and a creator who was told "put it on a hosted service so it
203
- // shows up properly" concludes their problem is solved when nothing about it moved.
204
- const onChainRenderer = await tryReadContract(makePublicClient({ chainKey: CHAIN }), {
237
+ // The local (non-`--remote`) path is shared with the deploy family's post-setup registration —
238
+ // see registerAndIndexLocally's doc comment (output.ts).
239
+ const outcome = await registerAndIndexLocally(address, flags);
240
+ emit(localAddJsonResult({
241
+ chainId: outcome.state.chainId,
205
242
  address,
206
- abi: STATE_ABI,
207
- functionName: 'tokenURIRenderer',
208
- });
209
- if (onChainRenderer && onChainRenderer !== zeroAddress) {
210
- info(`${dim('note:')} this project resolves ${bold('tokenURI from the chain')} (renderer ${onChainRenderer}) — marketplaces read that, not this service. ` +
211
- `Registering still gives you indexing, the live view and managed rendering, but it does not change the metadata a collector sees.`);
212
- }
213
- return;
214
- }
215
- // The local (non-`--remote`) path is shared with the deploy family's post-setup registration —
216
- // see registerAndIndexLocally's doc comment (output.ts).
217
- return registerAndIndexLocally(address, flags);
243
+ scanFloor: outcome.scanFloor,
244
+ eventCount: outcome.state.eventCount,
245
+ tokenCount: outcome.state.tokens.length,
246
+ }));
247
+ });
218
248
  }
219
249
  export async function cmdIndex(address, flags) {
220
250
  const remote = remoteFlag(flags);
@@ -366,6 +396,12 @@ export function pointerOnlyImageCheck(fields) {
366
396
  return image.representation;
367
397
  return null;
368
398
  }
399
+ /** `chainComplete` describes a code template's dependency graph. Static projects have no code
400
+ * graph to classify, so their structured verification result must say "not applicable" instead
401
+ * of reporting a confident `false` that reads like a failed durability check. */
402
+ export function verifyChainComplete(isCodeProject, chainComplete) {
403
+ return isCodeProject ? chainComplete : null;
404
+ }
369
405
  export async function cmdVerifyBody(address, flags, emit) {
370
406
  const indexer = localIndexer();
371
407
  let state = indexer.getProject(address);
@@ -641,7 +677,7 @@ export async function cmdVerifyBody(address, flags, emit) {
641
677
  // renderer — the underlying probe cannot tell those apart, so neither can this field.
642
678
  tokenURIRendererCurrent: rendererCurrent,
643
679
  branch: status.branchName,
644
- chainComplete: !!status.chainComplete,
680
+ chainComplete: verifyChainComplete(isCodeProject, !!status.chainComplete),
645
681
  unresolvedRefs: [...status.unresolvedRefs],
646
682
  urlOverBudget: !!status.urlOverBudget,
647
683
  probe: probe