@artblocks/abx-cli 0.1.0-alpha.8 → 0.1.0-alpha.9
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 +280 -105
- package/dist/main.js.map +1 -1
- package/dist/migrate.js +1 -1
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +17 -25
- package/dist/ownerops.js.map +1 -1
- package/dist/remote.d.ts +63 -53
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +128 -43
- package/dist/remote.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +17 -12
- package/skill/reference/hosting.md +19 -8
- package/skill/reference/operating.md +5 -3
- package/skill/reference/setup.md +7 -1
- package/skill/reference/troubleshooting.md +1 -1
package/dist/main.js
CHANGED
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
* abx preview run a code project on localhost while it's still being made (no chain)
|
|
18
18
|
* abx deploy-code deploy a code project (SeriesCode): --script <file> (on-chain template)
|
|
19
19
|
* or --code-dir <dir> (build directory → ipfs/arweave `code` field)
|
|
20
|
-
* abx add <address> register + index a project (--remote
|
|
21
|
-
* abx
|
|
20
|
+
* abx add <address> register + index a project (--remote <name|url>: on a remote resolver, not this machine)
|
|
21
|
+
* abx remote [<name|url>] inspect a remote service (descriptor · chains · managed rendering · your projects there)
|
|
22
|
+
* abx index [<address>] re-index a project from chain (replay; --remote to nudge a remote resolver)
|
|
22
23
|
* abx verify <address> re-hash served bytes vs the on-chain commitment (no server)
|
|
23
24
|
* abx configure-param <addr> <id> <key> <value> set a governed PostParam (typed encode; any lane)
|
|
24
25
|
* abx set-param-hooks <addr> wire/clear a SeriesCode's configure/augment/transfer param hooks
|
|
@@ -70,13 +71,13 @@ const SCHEMA_CATALOG = 'Types: Bool·Select·Uint256Range·Int256Range·DecimalR
|
|
|
70
71
|
'Select needs options — key:Select[A|B|C]:Auth; a Range takes bounds — key:Uint256Range[0..100]:Auth. ' +
|
|
71
72
|
'A palette collectors set = palette:HexColor:TokenOwner';
|
|
72
73
|
import { uploadAndLocate } from './upload.js';
|
|
73
|
-
import { assertChainId, discoverDeployBlock, deployFactory, deploySeriesFactory, deploySeries, deployOneOfOne, deployRenderer, predictRenderer, predictSeedSource, encodeTag, encodeFieldRenderer, isCodeProject, loadDotEnv, makePublicClient, makeWalletClient, oneOfOneImageAbi, oneOfOneImageFactoryAbi, seriesImageAbi, seriesImageFactoryAbi, abxMetadataRendererAbi, predictClone, probeRpcEndpoints, prepareDeployOneOfOne, prepareDeploySeries, reconstructProject, saltFor, saltGuard, resolveChain, explorerUrl, DEFAULT_CHAIN_KEY, normalizeAttributes, parseTraitPairs, METADATA_FIELD as F, METADATA_REPRESENTATION as R, } from '@artblocks/abx-sdk';
|
|
74
|
+
import { assertChainId, discoverDeployBlock, deployFactory, deploySeriesFactory, deploySeries, deployOneOfOne, deployRenderer, predictRenderer, predictSeedSource, encodeTag, encodeFieldRenderer, isCodeProject, loadDotEnv, makePublicClient, makeWalletClient, oneOfOneImageAbi, oneOfOneImageFactoryAbi, seriesImageAbi, seriesImageFactoryAbi, abxMetadataRendererAbi, predictClone, probeRpcEndpoints, prepareDeployOneOfOne, prepareDeploySeries, reconstructProject, saltFor, saltGuard, resolveChain, resolveRpcUrl, redactRpcUrl, explorerUrl, DEFAULT_CHAIN_KEY, normalizeAttributes, parseTraitPairs, METADATA_FIELD as F, METADATA_REPRESENTATION as R, AbxServiceError, } from '@artblocks/abx-sdk';
|
|
74
75
|
import { SelfHostIndexer, SqliteStore } from '@artblocks/abx-indexer';
|
|
75
76
|
import { artContentHash, currentRenderArtifact, generateArt, resolveBaseUrl, startChainWatcher, startTokenApiServer, verifyProject, watchIntervalMs, DEFAULT_PORT, } from '@artblocks/abx-token-api';
|
|
76
77
|
import { ARWEAVE_FREE_UPLOAD_LIMIT, arweaveAddress, arweaveFunding, contentTypeFromPath, hashContent, resolveBackend, turboBalanceForAddress, turboUploadCostUsd, turboUploadWinc } from '@artblocks/abx-storage';
|
|
77
78
|
import { cmdTransfer, cmdMint, cmdSetMinter, cmdSetMaxInvocations, cmdConfigureParam, cmdSetParamHooks, cmdSetDependency, cmdRemoveLastDependency, cmdSetDependencyRegistry, cmdLockDependencies, cmdSetPrimaryPayee, cmdPause, cmdUnpause, cmdRefresh, cmdSetTokenUri, cmdSetContractUri, cmdSetRoyalty, cmdSetField, cmdAttach, cmdLockField, cmdSetRenderer, cmdLockUri, cmdSetAdmin, cmdMinterConfigure, cmdMinterShow, cmdMinterBuy, computeContentPlan, envStagingSender, laneFromFlags, ONCHAIN_PROJECT_SOFT_LIMIT, parseCompress, previewImageStaging, sessionStagingSender, stageImageField, stageImageFieldsBatch, authorshipContractFields, AUTHORSHIP_DEPLOY_FIELDS, } from './ownerops.js';
|
|
78
79
|
import { openWalletSession, signTx } from './signer.js';
|
|
79
|
-
import {
|
|
80
|
+
import { describeRemoteError, listConfiguredRemotes, misnamedRemoteVars, requireRemoteToken, resolveRemote, serviceClient } from './remote.js';
|
|
80
81
|
import { buildMigrationPlan, repinNodeCustody, verifyParity } from './migrate.js';
|
|
81
82
|
import { activeBackendId, arweaveKeyFilePath, backendResolution, ensureArweaveJwk, factoryAddress, seriesFactoryAddress, fixedPriceMinterAddress, loadArweaveJwk, loopbackBaseUrl, faucetHint, rendererAddress, storageOptions, storageSignerChoice, } from './config.js';
|
|
82
83
|
const CHAIN = process.env.ABX_CHAIN ?? DEFAULT_CHAIN_KEY;
|
|
@@ -207,6 +208,7 @@ async function main() {
|
|
|
207
208
|
case 'set-admin': return cmdSetAdmin(rest[0], flags);
|
|
208
209
|
case 'forget': return cmdForget(rest[0], flags);
|
|
209
210
|
case 'migrate': return cmdMigrate(rest[0], flags);
|
|
211
|
+
case 'remote': return cmdRemote(rest[0], flags);
|
|
210
212
|
case 'storage': return cmdStorage(rest);
|
|
211
213
|
case 'status': return cmdStatus();
|
|
212
214
|
case 'state': return cmdState(rest[0], flags);
|
|
@@ -223,6 +225,65 @@ async function main() {
|
|
|
223
225
|
process.exit(1);
|
|
224
226
|
}
|
|
225
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* A `--dry-run` computes the deterministic deploy address, which is a pure function of
|
|
230
|
+
* (factory, salt, deployer) — so it needs a deployer even though it signs nothing. Resolve it the
|
|
231
|
+
* same way the preview will (`--for`, else an env key) and fail EARLY with the fix if neither
|
|
232
|
+
* exists, rather than after the preview has printed several steps of work.
|
|
233
|
+
*/
|
|
234
|
+
/**
|
|
235
|
+
* A dry run only checks whether a factory address is CONFIGURED, not whether it has code on this
|
|
236
|
+
* chain — and the manifest always has an address, so a chain where the trust anchor isn't deployed
|
|
237
|
+
* (a private/local chain, or a wrong-network RPC) sailed past this and died inside
|
|
238
|
+
* `predictDeterministicAddress` with a raw `returned no data ("0x")` and a list of ABI hypotheses.
|
|
239
|
+
* The real deploy and `abx predict` both explain that case; a preview of the same deploy must too.
|
|
240
|
+
* Returns false when the caller should stop (message already printed).
|
|
241
|
+
*/
|
|
242
|
+
async function previewFactoryLive(client, factory, label) {
|
|
243
|
+
const code = await client.getCode({ address: factory }).catch(() => undefined);
|
|
244
|
+
if (code && code !== '0x')
|
|
245
|
+
return true;
|
|
246
|
+
warn(`the configured ${label} ${factory} has no code on '${CHAIN}' (asked ${redactRpcUrl(resolveRpcUrl(CHAIN))}).`);
|
|
247
|
+
info('so this preview can\'t compute the deterministic address. Either point at a chain where the trust anchor is deployed');
|
|
248
|
+
info(`(${bold('ABX_CHAIN=' + DEFAULT_CHAIN_KEY)} is the default and has one), or deploy your own on this chain with ${bold('--bootstrap-factory')}`);
|
|
249
|
+
info(dim('(a private anchor — platforms won\'t recognize its clones, so it\'s for private/sandbox chains).'));
|
|
250
|
+
console.log(`\n ${g('dry run')} ${dim('— nothing sent.')}\n`);
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Placeholder-identity guard, shared by ALL THREE deploy commands. `name`/`symbol` are written
|
|
255
|
+
* on-chain as the public collection identity and are effectively permanent, so a real deploy must
|
|
256
|
+
* never bake a tool default silently: warn in a preview, HARD-STOP a real send unless `--yes`.
|
|
257
|
+
*
|
|
258
|
+
* It was duplicated per command, and `deploy-series` simply never got a copy — its default
|
|
259
|
+
* "ABX Series"/"ABXS" went on-chain with at most a warning, while the skill promises the CLI
|
|
260
|
+
* refuses demo defaults. (deploy-code's copy even said "mirror deploy/deploy-series", which made
|
|
261
|
+
* the gap look closed.) One predicate now, like `loopbackBaseUrl()`, so a fourth command can't drift.
|
|
262
|
+
*/
|
|
263
|
+
function assertRealIdentity(flags, o) {
|
|
264
|
+
if (flags.name && flags.symbol)
|
|
265
|
+
return;
|
|
266
|
+
if (!flags.name)
|
|
267
|
+
warn(`no --name → default "${o.name}" would be the on-chain collection name`);
|
|
268
|
+
if (!flags.symbol)
|
|
269
|
+
warn(`no --symbol → default "${o.symbol}" would be the on-chain symbol`);
|
|
270
|
+
if (o.dryRun || flags.yes)
|
|
271
|
+
return; // a preview still runs; --yes is the explicit opt-in
|
|
272
|
+
throw new Error('refusing to write tool placeholders as your public on-chain identity — pass --name "Your Title" --symbol SYM ' +
|
|
273
|
+
'(or --yes to accept the defaults). On-chain identity is effectively permanent.');
|
|
274
|
+
}
|
|
275
|
+
function assertPreviewDeployer(flags) {
|
|
276
|
+
if (flags.for)
|
|
277
|
+
return;
|
|
278
|
+
try {
|
|
279
|
+
makeWalletClient({ chainKey: CHAIN });
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
throw new Error('dry run needs a deployer address to compute the deterministic deploy address — pass --for 0x.. ' +
|
|
283
|
+
'(a preview signs nothing, so no key is needed). For the REAL deploy with no key in .env, use the ' +
|
|
284
|
+
'wallet lane: --sign --for 0x.. (you approve in your own wallet).');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
226
287
|
// When the user/agent opts in (--yes), lift the getLogs chunk cap so a large
|
|
227
288
|
// reconstruction proceeds despite a range-limited RPC (otherwise it stops early with
|
|
228
289
|
// guidance — see GetLogsScanTooLargeError + the skill's "Choosing an RPC" decision).
|
|
@@ -1010,23 +1071,20 @@ async function cmdDeploy(flags, serveAfter) {
|
|
|
1010
1071
|
}
|
|
1011
1072
|
}
|
|
1012
1073
|
const dryRun = !serveAfter && !!flags['dry-run']; // preview only — no send, no custody, no factory deploy
|
|
1074
|
+
// A keyless preview needs `--for` (the address is a pure function of factory+salt+deployer). Check
|
|
1075
|
+
// it HERE, before the trust-anchor/content/plan steps print — hitting this after a wall of output
|
|
1076
|
+
// reads as "it half-worked", and a first-timer previewing with no key in .env always hits it.
|
|
1077
|
+
if (dryRun)
|
|
1078
|
+
assertPreviewDeployer(flags);
|
|
1013
1079
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
1014
1080
|
// Verify the RPC really is CHAIN before any send (factory/renderer/staging/deploy). A dry run
|
|
1015
1081
|
// sends nothing, but it DOES read the chain (predict address, resolve the factory/renderer), so
|
|
1016
1082
|
// a wrong-network RPC must still be caught with the clear mismatch message rather than failing
|
|
1017
1083
|
// opaquely inside predict; `allowUnreachable` keeps a genuinely offline dry-run previewable.
|
|
1018
1084
|
await assertChainId(CHAIN, { allowUnreachable: dryRun });
|
|
1019
|
-
//
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (!flags.name)
|
|
1023
|
-
warn(`no --name → default "${name}" would be the on-chain collection name`);
|
|
1024
|
-
if (!flags.symbol)
|
|
1025
|
-
warn(`no --symbol → default "${symbol}" would be the on-chain symbol`);
|
|
1026
|
-
if (!dryRun && !flags.yes) {
|
|
1027
|
-
throw new Error('refusing to write demo placeholders as your public on-chain identity — pass --name "Your Title" --symbol SYM (or --yes to accept the defaults).');
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1085
|
+
// `demo` is exempt: its whole job is a zero-argument first token.
|
|
1086
|
+
if (!serveAfter)
|
|
1087
|
+
assertRealIdentity(flags, { name, symbol, dryRun });
|
|
1030
1088
|
// Funding preflight (real deploys): warn now if the signer is unfunded, not at the tx.
|
|
1031
1089
|
if (!dryRun) {
|
|
1032
1090
|
let signer = flags.for;
|
|
@@ -1055,6 +1113,8 @@ async function cmdDeploy(flags, serveAfter) {
|
|
|
1055
1113
|
return;
|
|
1056
1114
|
}
|
|
1057
1115
|
factory = existing;
|
|
1116
|
+
if (!(await previewFactoryLive(publicClient, factory, 'factory')))
|
|
1117
|
+
return;
|
|
1058
1118
|
info(`would reuse canonical factory ${factory}`);
|
|
1059
1119
|
}
|
|
1060
1120
|
else {
|
|
@@ -1561,21 +1621,15 @@ async function cmdDeploySeries(flags) {
|
|
|
1561
1621
|
throw new Error(`--count ${count} exceeds the ${files.length} media file(s) in ${dirPath}`);
|
|
1562
1622
|
const slots = files.slice(0, count);
|
|
1563
1623
|
const dryRun = !!flags['dry-run'];
|
|
1624
|
+
if (dryRun)
|
|
1625
|
+
assertPreviewDeployer(flags); // fail fast, before the preview does any work (see cmdDeploy)
|
|
1626
|
+
assertRealIdentity(flags, { name, symbol, dryRun });
|
|
1564
1627
|
const lane = laneFromFlags(flags);
|
|
1565
1628
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
1566
1629
|
// Catch a wrong-network RPC with the clear mismatch message even on dry-run (which still reads
|
|
1567
1630
|
// the chain to predict the address); tolerate an unreachable RPC so an offline preview still works.
|
|
1568
1631
|
await assertChainId(CHAIN, { allowUnreachable: dryRun });
|
|
1569
|
-
//
|
|
1570
|
-
if (!flags.name || !flags.symbol) {
|
|
1571
|
-
if (!flags.name)
|
|
1572
|
-
warn(`no --name → default "${name}" would be the on-chain collection name`);
|
|
1573
|
-
if (!flags.symbol)
|
|
1574
|
-
warn(`no --symbol → default "${symbol}" would be the on-chain symbol`);
|
|
1575
|
-
if (!dryRun && !flags.yes) {
|
|
1576
|
-
throw new Error('refusing to write demo placeholders as your public on-chain identity — pass --name "Your Title" --symbol SYM (or --yes).');
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1632
|
+
// (identity guard already ran above, via the shared assertRealIdentity — before any RPC)
|
|
1579
1633
|
// Mint timing: mint-all → the whole series; mint-count N → the first N; else deferred.
|
|
1580
1634
|
const mintCount = flags['mint-all'] !== undefined ? count : flags['mint-count'] ? Number(flags['mint-count']) : 0;
|
|
1581
1635
|
if (mintCount > count)
|
|
@@ -1619,6 +1673,8 @@ async function cmdDeploySeries(flags) {
|
|
|
1619
1673
|
return;
|
|
1620
1674
|
}
|
|
1621
1675
|
factory = existing;
|
|
1676
|
+
if (!(await previewFactoryLive(publicClient, factory, 'Series factory')))
|
|
1677
|
+
return;
|
|
1622
1678
|
info(`would reuse canonical Series factory ${factory}`);
|
|
1623
1679
|
}
|
|
1624
1680
|
else {
|
|
@@ -2080,18 +2136,11 @@ async function cmdPredict(flags) {
|
|
|
2080
2136
|
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)'));
|
|
2081
2137
|
console.log('');
|
|
2082
2138
|
}
|
|
2083
|
-
//
|
|
2084
|
-
//
|
|
2085
|
-
//
|
|
2086
|
-
function
|
|
2087
|
-
|
|
2088
|
-
if (v === undefined)
|
|
2089
|
-
return null;
|
|
2090
|
-
const base = v !== 'true' ? v : process.env.ABX_PUBLIC_BASE_URL;
|
|
2091
|
-
if (!base) {
|
|
2092
|
-
throw new Error('`--remote` needs a resolver URL: pass `--remote https://host` or set ABX_PUBLIC_BASE_URL in .env');
|
|
2093
|
-
}
|
|
2094
|
-
return base;
|
|
2139
|
+
// The `--remote <name|url>` target (remote.ts owns the convention): a named remote's
|
|
2140
|
+
// `ABX_REMOTE_<NAME>_URL/_TOKEN`, an ad-hoc URL, or bare `--remote` = the self-host default.
|
|
2141
|
+
// Returns null for a local op (the default).
|
|
2142
|
+
function remoteFlag(flags) {
|
|
2143
|
+
return resolveRemote(flags.remote, flags['remote-token']);
|
|
2095
2144
|
}
|
|
2096
2145
|
/**
|
|
2097
2146
|
* Ensure a resolver admin token exists locally, generating + persisting one to `.env`
|
|
@@ -2124,22 +2173,13 @@ function ensureEffectsToken() {
|
|
|
2124
2173
|
process.env.ABX_EFFECTS_TOKEN = token;
|
|
2125
2174
|
return { token, generated: true };
|
|
2126
2175
|
}
|
|
2127
|
-
/** The shared secret that authorizes remote indexing control (never on-chain signing). */
|
|
2128
|
-
function requireAdminToken() {
|
|
2129
|
-
const t = process.env.ABX_RESOLVER_ADMIN_TOKEN;
|
|
2130
|
-
if (!t) {
|
|
2131
|
-
throw new Error('remote ops need ABX_RESOLVER_ADMIN_TOKEN in your .env — it must match the token set on the resolver ' +
|
|
2132
|
-
'(`abx deploy-resolver` generates one and wires both sides).');
|
|
2133
|
-
}
|
|
2134
|
-
return t;
|
|
2135
|
-
}
|
|
2136
2176
|
// ── add ──────────────────────────────────────────────────────────────────────
|
|
2137
2177
|
// Register + index a project this node didn't deploy. LOCAL by default (this
|
|
2138
2178
|
// machine's store); `--remote [url]` instead tells a HOSTED resolver to index it —
|
|
2139
2179
|
// the bridge a local deploy can't make on its own (separate projection stores).
|
|
2140
2180
|
async function cmdAdd(address, flags) {
|
|
2141
2181
|
if (!address || address.startsWith('--')) {
|
|
2142
|
-
console.error('usage: abx add <address> [--from-block N] [--factory 0x..] [--label "..."] [--remote [url]]\n');
|
|
2182
|
+
console.error('usage: abx add <address> [--from-block N] [--factory 0x..] [--label "..."] [--remote [name|url]]\n');
|
|
2143
2183
|
process.exit(1);
|
|
2144
2184
|
}
|
|
2145
2185
|
// `--attributes` is lane-aware here exactly as at deploy: a PER-TOKEN payload edits a Series'
|
|
@@ -2153,10 +2193,10 @@ async function cmdAdd(address, flags) {
|
|
|
2153
2193
|
if (flags.traits)
|
|
2154
2194
|
flagTraits.push(...parseTraitPairs(flags.traits));
|
|
2155
2195
|
const editedTokenAttributes = perTokenEdit ? parseSeriesTraitsById(attrRaw) : undefined;
|
|
2156
|
-
const remote =
|
|
2196
|
+
const remote = remoteFlag(flags);
|
|
2157
2197
|
if (remote) {
|
|
2158
|
-
|
|
2159
|
-
// Bridge what a
|
|
2198
|
+
requireRemoteToken(remote);
|
|
2199
|
+
// Bridge what a remote resolver can't derive itself: the off-chain traits and the durable
|
|
2160
2200
|
// content locators (ipfs://…). Prefer flags; otherwise forward what the LOCAL deploy stored
|
|
2161
2201
|
// (the local registration), and compute locators from this machine's content index if needed.
|
|
2162
2202
|
const localReg = new SelfHostIndexer().store.getRegistration(address);
|
|
@@ -2165,7 +2205,7 @@ async function cmdAdd(address, flags) {
|
|
|
2165
2205
|
: localReg?.attributes
|
|
2166
2206
|
? normalizeAttributes(JSON.parse(localReg.attributes))
|
|
2167
2207
|
: undefined;
|
|
2168
|
-
// Bridge a Series' per-token off-chain traits to the
|
|
2208
|
+
// Bridge a Series' per-token off-chain traits to the remote resolver (the resolver has no other
|
|
2169
2209
|
// way to derive them — they're operator metadata, not chain state). A fresh per-token `--attributes`
|
|
2170
2210
|
// EDITS them; otherwise forward what the LOCAL deploy stored. Best-effort parse.
|
|
2171
2211
|
let tokenAttributes;
|
|
@@ -2191,6 +2231,7 @@ async function cmdAdd(address, flags) {
|
|
|
2191
2231
|
// (Its ABSENCE here was the bug: a hosted resolver defaulted to genesis and scanned the whole
|
|
2192
2232
|
// chain.) Re-sending the same floor stays incremental server-side, so a nudge ≠ a re-scan.
|
|
2193
2233
|
const body = {
|
|
2234
|
+
chainId: resolveChain(CHAIN).id,
|
|
2194
2235
|
address,
|
|
2195
2236
|
fromBlock: await resolveScanFloor(address, localReg?.fromBlock, flags),
|
|
2196
2237
|
factory: await detectCanonicalFactory(address, flags.factory, localReg?.factory),
|
|
@@ -2202,12 +2243,18 @@ async function cmdAdd(address, flags) {
|
|
|
2202
2243
|
contentLocators: Object.keys(contentLocators).length ? contentLocators : undefined,
|
|
2203
2244
|
full: flags.full ? true : undefined,
|
|
2204
2245
|
};
|
|
2205
|
-
info(`${bold('REMOTE')} → ${remote} ${dim('(registering with the
|
|
2246
|
+
info(`${bold('REMOTE')} → ${remote.url} ${dim('(registering with the remote resolver — NOT this machine)')}`);
|
|
2206
2247
|
if (body.contentLocators)
|
|
2207
2248
|
info(`bridging image locator → ${Object.values(body.contentLocators)[0]} ${dim('(so the resolver points at IPFS, not its own localhost)')}`);
|
|
2208
|
-
|
|
2249
|
+
let r;
|
|
2250
|
+
try {
|
|
2251
|
+
r = await serviceClient(remote).registerProject(body);
|
|
2252
|
+
}
|
|
2253
|
+
catch (err) {
|
|
2254
|
+
throw describeRemoteError(err, remote, 'remote add');
|
|
2255
|
+
}
|
|
2209
2256
|
ok(`remote resolver indexed ${r.project.name ?? address}: ${r.project.eventCount} events ${dim(`(${r.mode}, ${r.elapsedMs}ms)`)}`);
|
|
2210
|
-
info(`it now serves ${remote.
|
|
2257
|
+
info(`it now serves ${remote.url}/t/${body.chainId}/${address.toLowerCase()}/0`);
|
|
2211
2258
|
return;
|
|
2212
2259
|
}
|
|
2213
2260
|
allowLargeScan(flags);
|
|
@@ -2790,17 +2837,7 @@ async function cmdDeployCode(flags) {
|
|
|
2790
2837
|
const dryRun = !!flags['dry-run'];
|
|
2791
2838
|
const name = flags.name ?? 'ABX Code';
|
|
2792
2839
|
const symbol = flags.symbol ?? 'ABXC';
|
|
2793
|
-
|
|
2794
|
-
// on-chain identity. dry-run only warns so a preview still runs without --name/--symbol.
|
|
2795
|
-
if (!flags.name || !flags.symbol) {
|
|
2796
|
-
if (!flags.name)
|
|
2797
|
-
warn(`no --name → default "${name}" would be the on-chain collection name`);
|
|
2798
|
-
if (!flags.symbol)
|
|
2799
|
-
warn(`no --symbol → default "${symbol}" would be the on-chain symbol`);
|
|
2800
|
-
if (!dryRun && !flags.yes) {
|
|
2801
|
-
throw new Error('refusing to write demo placeholders as your public on-chain identity — pass --name "Your Title" --symbol SYM (or --yes).');
|
|
2802
|
-
}
|
|
2803
|
-
}
|
|
2840
|
+
assertRealIdentity(flags, { name, symbol, dryRun });
|
|
2804
2841
|
const maxProvided = flags.max !== undefined;
|
|
2805
2842
|
const max = Number(flags.max ?? 16);
|
|
2806
2843
|
if (!Number.isInteger(max) || max <= 0)
|
|
@@ -3641,7 +3678,7 @@ async function loadEffects() {
|
|
|
3641
3678
|
*/
|
|
3642
3679
|
async function cmdRender(address, tokenIds, flags) {
|
|
3643
3680
|
if (!address || address.startsWith('--')) {
|
|
3644
|
-
console.error('usage: abx render <address> [tokenId…] [--force] [--remote [url]] (missing stills/traits; --force re-renders an existing one — abx render --help)\n');
|
|
3681
|
+
console.error('usage: abx render <address> [tokenId…] [--force] [--remote [name|url]] (missing stills/traits; --force re-renders an existing one — abx render --help)\n');
|
|
3645
3682
|
process.exit(1);
|
|
3646
3683
|
}
|
|
3647
3684
|
// Token ids are decimal (mint order). Keep only those — `parseFlags` leaves `--flag value` pairs
|
|
@@ -3678,12 +3715,12 @@ async function cmdRender(address, tokenIds, flags) {
|
|
|
3678
3715
|
ok(line);
|
|
3679
3716
|
return;
|
|
3680
3717
|
}
|
|
3681
|
-
// --remote publishes each render to the
|
|
3718
|
+
// --remote publishes each render to the remote resolver's control plane (the locator bridge) so a
|
|
3682
3719
|
// laptop render lands on a resolver that doesn't share this disk; republish=true makes a re-run
|
|
3683
3720
|
// restore a resolver that lost its volume without re-rendering. Local (no --remote): shared backend.
|
|
3684
|
-
const remote =
|
|
3685
|
-
const resolverUrl = (remote ?? process.env.ABX_RESOLVER_URL ?? resolveBaseUrl()).replace(/\/$/, '');
|
|
3686
|
-
const adminToken = remote ?
|
|
3721
|
+
const remote = remoteFlag(flags);
|
|
3722
|
+
const resolverUrl = (remote?.url ?? process.env.ABX_RESOLVER_URL ?? resolveBaseUrl()).replace(/\/$/, '');
|
|
3723
|
+
const adminToken = remote ? requireRemoteToken(remote) : undefined;
|
|
3687
3724
|
// Co-located (no --remote): record each declared output into the shared store's artifact
|
|
3688
3725
|
// registry so the local resolver's `artifacts` manifest enumerates it. Remote: the publish
|
|
3689
3726
|
// lane (adminToken) records rows on the hosted resolver instead.
|
|
@@ -3718,8 +3755,11 @@ async function cmdRender(address, tokenIds, flags) {
|
|
|
3718
3755
|
* single time and exits (vs `abx render <addr>` which is the per-project repair lane).
|
|
3719
3756
|
*/
|
|
3720
3757
|
async function cmdEffects(flags) {
|
|
3721
|
-
const
|
|
3722
|
-
const
|
|
3758
|
+
const effectsRemote = remoteFlag(flags);
|
|
3759
|
+
const resolverUrl = (effectsRemote?.url ?? process.env.ABX_RESOLVER_URL ?? resolveBaseUrl()).replace(/\/$/, '');
|
|
3760
|
+
// Token optional BY DESIGN: no token = the co-located topology (shared store, no publish lane).
|
|
3761
|
+
// A named remote brings its own token; otherwise the self-host env token.
|
|
3762
|
+
const adminToken = effectsRemote?.token ?? process.env.ABX_RESOLVER_ADMIN_TOKEN;
|
|
3723
3763
|
// Co-located (no admin token — we share the resolver's store): record each declared output into
|
|
3724
3764
|
// the shared artifact registry so the resolver's `artifacts` manifest enumerates it. With an
|
|
3725
3765
|
// admin token, the publish lane records rows on the hosted resolver instead.
|
|
@@ -3759,15 +3799,21 @@ async function cmdEffects(flags) {
|
|
|
3759
3799
|
await new Promise(() => { }); // block like `serve`
|
|
3760
3800
|
}
|
|
3761
3801
|
async function cmdIndex(address, flags) {
|
|
3762
|
-
const remote =
|
|
3802
|
+
const remote = remoteFlag(flags);
|
|
3763
3803
|
if (remote) {
|
|
3764
3804
|
if (!address || address.startsWith('--')) {
|
|
3765
|
-
console.error('usage: abx index <address> --remote [url] (re-index one project on a
|
|
3805
|
+
console.error('usage: abx index <address> --remote [name|url] (re-index one project on a remote resolver)\n');
|
|
3766
3806
|
process.exit(1);
|
|
3767
3807
|
}
|
|
3768
|
-
|
|
3769
|
-
info(`${bold('REMOTE')} → ${remote} ${dim('(re-indexing on the
|
|
3770
|
-
|
|
3808
|
+
requireRemoteToken(remote);
|
|
3809
|
+
info(`${bold('REMOTE')} → ${remote.url} ${dim('(re-indexing on the remote resolver — the post-deploy nudge)')}`);
|
|
3810
|
+
let r;
|
|
3811
|
+
try {
|
|
3812
|
+
r = await serviceClient(remote).registerProject({ chainId: resolveChain(CHAIN).id, address, full: flags.full ? true : undefined });
|
|
3813
|
+
}
|
|
3814
|
+
catch (err) {
|
|
3815
|
+
throw describeRemoteError(err, remote, 'remote index');
|
|
3816
|
+
}
|
|
3771
3817
|
ok(`remote resolver re-indexed ${r.project.name ?? address}: ${r.project.eventCount} events ${dim(`(${r.mode}, ${r.elapsedMs}ms)`)}`);
|
|
3772
3818
|
return;
|
|
3773
3819
|
}
|
|
@@ -3798,9 +3844,9 @@ async function cmdVerify(address, flags) {
|
|
|
3798
3844
|
process.exit(1);
|
|
3799
3845
|
}
|
|
3800
3846
|
allowLargeScan(flags);
|
|
3801
|
-
const remote =
|
|
3847
|
+
const remote = remoteFlag(flags);
|
|
3802
3848
|
if (remote)
|
|
3803
|
-
return cmdVerifyRemote(address, remote);
|
|
3849
|
+
return cmdVerifyRemote(address, remote.url);
|
|
3804
3850
|
const indexer = new SelfHostIndexer();
|
|
3805
3851
|
let state = indexer.getProject(address);
|
|
3806
3852
|
if (indexer.store.getRegistration(address)) {
|
|
@@ -4072,7 +4118,12 @@ async function cmdTokenUri(address, flags) {
|
|
|
4072
4118
|
catch {
|
|
4073
4119
|
const code = await publicClient.getCode({ address }).catch(() => undefined);
|
|
4074
4120
|
if (!code || code === '0x') {
|
|
4075
|
-
|
|
4121
|
+
// Name the endpoint we actually asked. "No contract here" is indistinguishable from "you're
|
|
4122
|
+
// pointed at the wrong node", and a chain KEY doesn't disambiguate that — two endpoints can
|
|
4123
|
+
// both claim `sepolia` (a fork, a stale duplicate .env line) and only one has your contract.
|
|
4124
|
+
console.error(`abx tokenuri: no contract at ${address} on ${CHAIN} (asked ${redactRpcUrl(resolveRpcUrl(CHAIN))}) — ` +
|
|
4125
|
+
`double-check the address, and that this endpoint is the network you deployed to. ` +
|
|
4126
|
+
`If you JUST deployed, give the tx a block or two to mine.\n`);
|
|
4076
4127
|
}
|
|
4077
4128
|
else {
|
|
4078
4129
|
console.error(`abx tokenuri: ${address} didn't return a tokenURI for token ${tokenId} — it may not be an ABX/ERC-721 token, token ${tokenId} may be unminted (try --token <id>), or — on a large on-chain tokenURI — an unauthenticated RPC read hit its gas cap (try a wallet-connected / high-gas RPC).\n`);
|
|
@@ -4415,15 +4466,24 @@ async function cmdStatus() {
|
|
|
4415
4466
|
// test/junk deploys. On-chain data is untouched; `abx add` can re-register it.
|
|
4416
4467
|
async function cmdForget(address, flags) {
|
|
4417
4468
|
if (!address || address.startsWith('--')) {
|
|
4418
|
-
console.error('usage: abx forget <address> [--remote [url]]\n');
|
|
4469
|
+
console.error('usage: abx forget <address> [--remote [name|url]]\n');
|
|
4419
4470
|
process.exit(1);
|
|
4420
4471
|
}
|
|
4421
|
-
const remote =
|
|
4472
|
+
const remote = remoteFlag(flags);
|
|
4422
4473
|
if (remote) {
|
|
4423
|
-
|
|
4424
|
-
info(`${bold('REMOTE')} → ${remote} ${dim('(deregistering on the
|
|
4425
|
-
|
|
4426
|
-
|
|
4474
|
+
requireRemoteToken(remote);
|
|
4475
|
+
info(`${bold('REMOTE')} → ${remote.url} ${dim('(deregistering on the remote resolver — NOT this machine)')}`);
|
|
4476
|
+
let removed;
|
|
4477
|
+
try {
|
|
4478
|
+
({ removed } = await serviceClient(remote).removeProject(resolveChain(CHAIN).id, address));
|
|
4479
|
+
}
|
|
4480
|
+
catch (err) {
|
|
4481
|
+
throw describeRemoteError(err, remote, 'remote forget');
|
|
4482
|
+
}
|
|
4483
|
+
if (removed)
|
|
4484
|
+
ok(`remote resolver forgot ${address} — it will stop serving it. On-chain data is untouched.`);
|
|
4485
|
+
else
|
|
4486
|
+
console.log(dim(` ${address} wasn't registered on ${remote.url} — nothing to forget.`));
|
|
4427
4487
|
return;
|
|
4428
4488
|
}
|
|
4429
4489
|
const indexer = new SelfHostIndexer();
|
|
@@ -4434,6 +4494,94 @@ async function cmdForget(address, flags) {
|
|
|
4434
4494
|
indexer.store.deregister(address);
|
|
4435
4495
|
ok(`forgot ${address} — dropped its registration + projection. On-chain data is untouched.`);
|
|
4436
4496
|
}
|
|
4497
|
+
// ── remote ────────────────────────────────────────────────────────────────--
|
|
4498
|
+
// Inspect a remote service. Bare `abx remote` lists the named remotes configured in .env plus the
|
|
4499
|
+
// self-host default pair (URLs + whether a token is set — never the secret itself). With a target,
|
|
4500
|
+
// fetches its PUBLIC service descriptor (what it serves: interfaces, chains, auth, managed
|
|
4501
|
+
// rendering) and — when a token resolves — lists the projects visible to that token, which makes
|
|
4502
|
+
// this the one-command "is my provider key valid?" check. Read-only; registers nothing.
|
|
4503
|
+
async function cmdRemote(spec, flags) {
|
|
4504
|
+
if (!spec || spec.startsWith('--')) {
|
|
4505
|
+
const remotes = listConfiguredRemotes();
|
|
4506
|
+
console.log(`\n ${bold('named remotes')} ${dim('(ABX_REMOTE_<NAME>_URL/_TOKEN in .env — inspect one: abx remote <name>)')}`);
|
|
4507
|
+
if (remotes.length === 0)
|
|
4508
|
+
console.log(dim(' none configured'));
|
|
4509
|
+
for (const r of remotes) {
|
|
4510
|
+
console.log(` ${g('●')} ${r.name.toLowerCase()} ${dim(r.url)} ${r.hasToken ? g('token set') : dim('no token')}`);
|
|
4511
|
+
}
|
|
4512
|
+
// A near-miss var reads as "no token" while the value is sitting in .env under the wrong name.
|
|
4513
|
+
for (const bad of misnamedRemoteVars()) {
|
|
4514
|
+
warn(`${bad.key} isn't a recognized remote var — the convention is ${bold(bad.suggestion)} (only _URL and _TOKEN are read).`);
|
|
4515
|
+
}
|
|
4516
|
+
const def = process.env.ABX_PUBLIC_BASE_URL ?? process.env.ABX_RESOLVER_URL;
|
|
4517
|
+
console.log(`\n ${bold('self-host default')} ${dim('(bare --remote)')}`);
|
|
4518
|
+
console.log(def
|
|
4519
|
+
? ` ${g('●')} ${def} ${process.env.ABX_RESOLVER_ADMIN_TOKEN ? g('token set') : dim('no ABX_RESOLVER_ADMIN_TOKEN')}`
|
|
4520
|
+
: dim(' none (set ABX_PUBLIC_BASE_URL in .env)'));
|
|
4521
|
+
console.log('');
|
|
4522
|
+
return;
|
|
4523
|
+
}
|
|
4524
|
+
const target = resolveRemote(spec, flags['remote-token']);
|
|
4525
|
+
if (!target)
|
|
4526
|
+
return;
|
|
4527
|
+
const client = serviceClient(target);
|
|
4528
|
+
console.log(`\n ${bold(target.name ? `remote ${target.name.toLowerCase()}` : 'remote')} ${dim(`→ ${target.url}`)}`);
|
|
4529
|
+
let d;
|
|
4530
|
+
try {
|
|
4531
|
+
d = await client.descriptor();
|
|
4532
|
+
}
|
|
4533
|
+
catch (err) {
|
|
4534
|
+
// Two very different situations, and the fix differs — so don't nest the raw client error
|
|
4535
|
+
// (it repeats the URL and leaks `GET`/`fetch failed` at a creator).
|
|
4536
|
+
const status = err instanceof AbxServiceError ? err.status : -1;
|
|
4537
|
+
if (status === 0) {
|
|
4538
|
+
throw new Error(`nothing responded at ${target.url} — check the address. A provider gives you an https:// base ` +
|
|
4539
|
+
`(e.g. https://meta.provider.xyz); if it's your own node, is it running?`);
|
|
4540
|
+
}
|
|
4541
|
+
throw new Error(`${target.url} answered, but serves no ABX service descriptor at /.well-known/abx-service. ` +
|
|
4542
|
+
`That's either an older self-hosted node (fine if it's yours — the remote commands still work against it) ` +
|
|
4543
|
+
`or not an ABX service at all. Verify the URL before registering anything with it.`);
|
|
4544
|
+
}
|
|
4545
|
+
info(`service ${d.service?.name ?? '—'} ${dim(d.service?.version ?? '')}`);
|
|
4546
|
+
info(`serves ${(d.interfaces ?? []).join(' · ') || '—'}`);
|
|
4547
|
+
const chainId = resolveChain(CHAIN).id;
|
|
4548
|
+
const coversChain = (d.chains ?? []).includes(chainId);
|
|
4549
|
+
info(`chains ${(d.chains ?? []).join(', ') || '—'} ${coversChain ? g(`✓ covers ${CHAIN} (${chainId})`) : `${c.orange}⚠${c.reset} does NOT cover ${CHAIN} (${chainId}) — registrations will be refused`}`);
|
|
4550
|
+
if (d.render?.attached) {
|
|
4551
|
+
const outputs = d.render.effects?.flatMap((e) => e.outputs.map((o) => `${e.key}/${o.key}`)).join(', ');
|
|
4552
|
+
info(`rendering managed behind this service${outputs ? ` (${outputs})` : d.render.effects === null ? dim(' (attached — runner unverified right now)') : ''} — code drops need no effects runner here`);
|
|
4553
|
+
}
|
|
4554
|
+
if (d.auth) {
|
|
4555
|
+
info(`auth bearer${d.auth.signupUrl ? ` · get a key: ${d.auth.signupUrl}` : ''}${d.auth.docsUrl ? ` · docs: ${d.auth.docsUrl}` : ''}`);
|
|
4556
|
+
}
|
|
4557
|
+
else {
|
|
4558
|
+
info(`auth none advertised ${dim('(control plane disabled on this node)')}`);
|
|
4559
|
+
}
|
|
4560
|
+
if (!target.token) {
|
|
4561
|
+
// Same near-miss check the register path does — this is where someone lands FIRST when their
|
|
4562
|
+
// credential is set under a name the CLI doesn't read, so the hint has to be here too.
|
|
4563
|
+
const nearMiss = misnamedRemoteVars().find((v) => v.suggestion === target.tokenVar);
|
|
4564
|
+
if (nearMiss)
|
|
4565
|
+
warn(`${nearMiss.key} is set but is NOT read — the convention is ${bold(target.tokenVar)} (only _URL and _TOKEN). Rename it and re-run.`);
|
|
4566
|
+
else
|
|
4567
|
+
info(dim(`no token resolved (set ${target.tokenVar} or pass --remote-token) — descriptor only; can't list your projects.`));
|
|
4568
|
+
console.log('');
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
try {
|
|
4572
|
+
const projects = await client.listProjects();
|
|
4573
|
+
ok(`token accepted — ${projects.length} project(s) visible to it`);
|
|
4574
|
+
for (const p of projects.slice(0, 10)) {
|
|
4575
|
+
console.log(` ${g('●')} ${p.name ?? p.label ?? p.address} ${dim(`${p.address} · ${p.tokenCount ?? '?'} token(s)`)}`);
|
|
4576
|
+
}
|
|
4577
|
+
if (projects.length > 10)
|
|
4578
|
+
console.log(dim(` … and ${projects.length - 10} more`));
|
|
4579
|
+
}
|
|
4580
|
+
catch (err) {
|
|
4581
|
+
throw describeRemoteError(err, target, 'remote list');
|
|
4582
|
+
}
|
|
4583
|
+
console.log('');
|
|
4584
|
+
}
|
|
4437
4585
|
// ── migrate ───────────────────────────────────────────────────────────────--
|
|
4438
4586
|
// Move a contract's OFF-CHAIN operator state from one resolver to another (e.g. fly.io →
|
|
4439
4587
|
// a droplet). The destination replays all ON-CHAIN state from chain itself; this bridges
|
|
@@ -4442,13 +4590,17 @@ async function cmdForget(address, flags) {
|
|
|
4442
4590
|
// and write through the admin control plane. It does NOT cut over — after a clean migration the
|
|
4443
4591
|
// operator re-points DNS (custom domain) or the on-chain base URI (provider endpoint).
|
|
4444
4592
|
async function cmdMigrate(address, flags) {
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4593
|
+
// Both sides accept a named remote or a URL. Only the DESTINATION needs a credential — the
|
|
4594
|
+
// source is read via its PUBLIC api (the exit ramp works with zero provider cooperation).
|
|
4595
|
+
const fromTarget = typeof flags.from === 'string' ? resolveRemote(flags.from) : null;
|
|
4596
|
+
const toTarget = typeof flags.to === 'string' ? resolveRemote(flags.to, flags['remote-token']) : null;
|
|
4597
|
+
if (!address || address.startsWith('--') || !fromTarget || !toTarget) {
|
|
4598
|
+
console.error('usage: abx migrate <address> --from <source-resolver name|url> --to <dest-resolver name|url> [--from-block N]\n');
|
|
4449
4599
|
process.exit(1);
|
|
4450
4600
|
}
|
|
4451
|
-
|
|
4601
|
+
requireRemoteToken(toTarget);
|
|
4602
|
+
const from = fromTarget.url;
|
|
4603
|
+
const to = toTarget.url;
|
|
4452
4604
|
const chainId = resolveChain(CHAIN).id;
|
|
4453
4605
|
allowLargeScan(flags);
|
|
4454
4606
|
console.log(bold(`\n abx migrate ${dim('— port off-chain state between resolvers (no cutover)')}`));
|
|
@@ -4505,6 +4657,7 @@ async function cmdMigrate(address, flags) {
|
|
|
4505
4657
|
step('Populate the destination');
|
|
4506
4658
|
const locCount = Object.keys(plan.contentLocators).length;
|
|
4507
4659
|
const body = {
|
|
4660
|
+
chainId,
|
|
4508
4661
|
address,
|
|
4509
4662
|
// The deploy block (locally known); if somehow absent, omit it so the destination derives it
|
|
4510
4663
|
// rather than scanning from genesis (never bake a from-0 floor into a fresh resolver).
|
|
@@ -4517,8 +4670,14 @@ async function cmdMigrate(address, flags) {
|
|
|
4517
4670
|
contentLocators: locCount ? plan.contentLocators : undefined,
|
|
4518
4671
|
full: true, // first registration on the destination — replay from the deploy block
|
|
4519
4672
|
};
|
|
4520
|
-
info(`${bold('REMOTE')} → ${to} ${dim('(
|
|
4521
|
-
|
|
4673
|
+
info(`${bold('REMOTE')} → ${to} ${dim('(control plane — chain replay + off-chain enrichment)')}`);
|
|
4674
|
+
let r;
|
|
4675
|
+
try {
|
|
4676
|
+
r = await serviceClient(toTarget).registerProject(body);
|
|
4677
|
+
}
|
|
4678
|
+
catch (err) {
|
|
4679
|
+
throw describeRemoteError(err, toTarget, 'migrate destination');
|
|
4680
|
+
}
|
|
4522
4681
|
ok(`destination indexed ${r.project.name ?? address}: ${r.project.eventCount} events ${dim(`(${r.mode}, ${r.elapsedMs}ms)`)}`);
|
|
4523
4682
|
// 5) Parity check — does the destination now serve the same metadata as the source? Sample a
|
|
4524
4683
|
// token we did NOT re-pin (a re-pinned image is durable-locator-on-dest vs old-host-on-source
|
|
@@ -4854,7 +5013,8 @@ async function cmdDeployResolver(flags) {
|
|
|
4854
5013
|
const url = art.baseUrl;
|
|
4855
5014
|
info(`abx deploy --image <art> --name … --public-base-url ${url} (or export ABX_PUBLIC_BASE_URL=${url})`);
|
|
4856
5015
|
info(`the contract derives ${url}/t/${resolveChain(CHAIN).id}/{address}/{tokenId} from that base.`);
|
|
4857
|
-
info(`${bold('then')} abx add <clone> --remote ${dim('# tell the
|
|
5016
|
+
info(`${bold('then')} abx add <clone> --remote ${dim('# tell the remote resolver to index it — a LOCAL deploy does NOT')}`);
|
|
5017
|
+
info(dim(`prefer addressing it by name? add ABX_REMOTE_<NAME>_URL=${url} (+ ABX_REMOTE_<NAME>_TOKEN=<the same token>) to .env → abx add <clone> --remote <name>`));
|
|
4858
5018
|
console.log('');
|
|
4859
5019
|
}
|
|
4860
5020
|
// ── deploy-effects: scaffold the render runner (the resolver's browser-bearing companion) ─────
|
|
@@ -5153,8 +5313,9 @@ const COMMAND_HELP = {
|
|
|
5153
5313
|
No tokenId → sweeps all minted tokens; pass ids (${g('0 1 2')}) to target specific tokens.
|
|
5154
5314
|
${g('--force')} RE-RENDER even when the still already exists — the fix for a bad / blank / timed-out capture
|
|
5155
5315
|
(the art is otherwise deterministic, so a plain render idempotent-skips an existing still). Overwrites it (+ republishes on --remote).
|
|
5156
|
-
${g('--remote [url]')}
|
|
5157
|
-
Idempotent; a re-run restores a resolver that lost its volume. Needs
|
|
5316
|
+
${g('--remote [name|url]')} publish each render to a REMOTE resolver (the locator bridge: upload to ${bold('ABX_STORAGE_BACKEND')}, POST /v1/effect-artifacts).
|
|
5317
|
+
Idempotent; a re-run restores a resolver that lost its volume. Needs its token (a named remote's
|
|
5318
|
+
${g('ABX_REMOTE_<NAME>_TOKEN')}, else ${g('ABX_RESOLVER_ADMIN_TOKEN')}).
|
|
5158
5319
|
--effects-url <url> enqueue on a running effect-runner service instead of rendering inline (else ${g('ABX_EFFECTS_URL')})
|
|
5159
5320
|
${dim('Inline (no --remote) renders on THIS machine (needs `npx playwright install chromium` once), pointing Chromium at the live')}
|
|
5160
5321
|
${dim(`view of ${g('ABX_RESOLVER_URL')} (else your configured base / local ${g('abx serve')}), and stores to the resolved backend.`)}
|
|
@@ -5170,8 +5331,8 @@ const COMMAND_HELP = {
|
|
|
5170
5331
|
--port <n> HTTP port (default ${g('ABX_EFFECTS_PORT')} / 8788) — ${g('POST /notify')} enqueues (watcher lane) · ${g('POST /run')} sweeps synchronously (command lane)
|
|
5171
5332
|
--interval-ms <n> the SAFETY-FLOOR sweep (default ${g('ABX_EFFECTS_INTERVAL_MS')} / 300000) — catches a missed notify / cold start; the watcher is the trigger
|
|
5172
5333
|
--concurrency <n> parallel renders while draining (default ${g('ABX_EFFECTS_CONCURRENCY')} / 1 — Chromium is heavy; raise deliberately)
|
|
5173
|
-
${dim('Co-located with a local `abx serve` (same store) → no tokens needed. Against a
|
|
5174
|
-
${dim('
|
|
5334
|
+
${dim('Co-located with a local `abx serve` (same store) → no tokens needed. Against a REMOTE resolver: --remote <name|url> (its')}
|
|
5335
|
+
${dim('token publishes renders + reports status), or ABX_RESOLVER_URL + ABX_RESOLVER_ADMIN_TOKEN. PUBLIC runner? set ABX_EFFECTS_TOKEN — it gates /run + /notify.')}
|
|
5175
5336
|
${dim('To HOST the runner (fly/docker), use `abx deploy-effects`.')}`,
|
|
5176
5337
|
'configure-param': `
|
|
5177
5338
|
${bold('abx configure-param')} <address> <tokenId|-> <key> <value> ${dim('— set a PostParam (typed, canonical encode). Sends a tx.')}
|
|
@@ -5182,7 +5343,7 @@ const COMMAND_HELP = {
|
|
|
5182
5343
|
like ${g('params.keys')} / ${g('display.gateway')}. ≤31 printable-ASCII chars ride as a literal bytes32; longer takes the data path.
|
|
5183
5344
|
After a token write, a project whose ${g('params.keys')} doesn't list the key gets a one-line fix suggestion.
|
|
5184
5345
|
--file <path> read the value from a file (String / Bytes payloads)
|
|
5185
|
-
${g('--remote [url]')}
|
|
5346
|
+
${g('--remote [name|url]')} nudge a REMOTE resolver to re-index IMMEDIATELY after the change (else ABX_PUBLIC_BASE_URL) — it pings
|
|
5186
5347
|
the resolver's effect runner, so the thumbnail re-renders without waiting. Usually OPTIONAL now: a
|
|
5187
5348
|
resolver running the chain watcher (the ${g('abx serve')} default) sees the change on its next poll (~12s)
|
|
5188
5349
|
and auto-re-renders on its own. Keep --remote for a watcher-disabled resolver or when seconds matter.
|
|
@@ -5366,13 +5527,16 @@ const COMMAND_HELP = {
|
|
|
5366
5527
|
${bold('abx add')} <address> ${dim('— register + index a project. Also edits off-chain display metadata + traits.')}
|
|
5367
5528
|
--from-block <n> --factory 0x.. --label "<s>" --description "<s>" --external-url <url> [--full] [--yes]
|
|
5368
5529
|
--traits "K=V; K2=V2" / --attributes <file.json> set the off-chain operator traits (on-chain attributes always win)
|
|
5369
|
-
${g('--remote [url]')}
|
|
5530
|
+
${g('--remote [name|url]')} target a REMOTE resolver instead of this machine — bridges the image locator (ipfs://…) + traits to it.
|
|
5531
|
+
A ${bold('name')} reads ${g('ABX_REMOTE_<NAME>_URL')} + ${g('ABX_REMOTE_<NAME>_TOKEN')} from .env (a managed provider's API key);
|
|
5532
|
+
a URL (or bare --remote = ${g('ABX_PUBLIC_BASE_URL')}) uses ${g('ABX_RESOLVER_ADMIN_TOKEN')}. Inspect first: ${g('abx remote <name>')}
|
|
5533
|
+
--remote-token <t> override the token for this invocation (--token means a token ID elsewhere, hence the name)`,
|
|
5370
5534
|
index: `
|
|
5371
5535
|
${bold('abx index')} [<address>] ${dim('— re-index from chain (read-only). Incremental by default.')}
|
|
5372
5536
|
${g('--full')} force a full replay from the deploy block (the durability proof) --yes allow a very large scan`,
|
|
5373
5537
|
verify: `
|
|
5374
5538
|
${bold('abx verify')} <address> ${dim('— re-hash the served bytes against the on-chain commitment (read-only; no server).')}
|
|
5375
|
-
${g('--remote [url]')}
|
|
5539
|
+
${g('--remote [name|url]')} verify what a REMOTE resolver actually serves (else ABX_PUBLIC_BASE_URL) — probes its \`/image\` (302→locator
|
|
5376
5540
|
or 200 bytes), so it accounts for a render PUBLISHED to that resolver. ${bold('Use this for a code project whose')}
|
|
5377
5541
|
${bold('renders were published to a hosted resolver')} — a plain \`abx verify\` only checks THIS machine's store and will
|
|
5378
5542
|
report a false placeholder for a render that lives on the resolver.
|
|
@@ -5382,11 +5546,21 @@ const COMMAND_HELP = {
|
|
|
5382
5546
|
${bold('abx tokenuri')} <address> [--token <id>] ${dim('— read tokenURI(id) straight from the contract on-chain + decode the JSON (read-only; no server).')}
|
|
5383
5547
|
${dim('The proof a fully on-chain token self-resolves: any RPC returns the renderer-assembled metadata. Default token 0.')}`,
|
|
5384
5548
|
forget: `
|
|
5385
|
-
${bold('abx forget')} <address> ${dim('— drop a project’s local registration + projection. On-chain data is untouched.')}
|
|
5549
|
+
${bold('abx forget')} <address> ${dim('— drop a project’s local registration + projection. On-chain data is untouched.')}
|
|
5550
|
+
${g('--remote [name|url]')} deregister on a REMOTE resolver instead (it stops serving the project; re-add any time)`,
|
|
5551
|
+
remote: `
|
|
5552
|
+
${bold('abx remote')} [<name|url>] ${dim('— inspect a remote service (read-only; registers nothing).')}
|
|
5553
|
+
Bare: list the named remotes in .env (${g('ABX_REMOTE_<NAME>_URL')} / ${g('_TOKEN')} — token shown as set/unset, never printed)
|
|
5554
|
+
plus the self-host default (bare --remote = ${g('ABX_PUBLIC_BASE_URL')} + ${g('ABX_RESOLVER_ADMIN_TOKEN')}).
|
|
5555
|
+
With a target: fetch its PUBLIC ${g('/.well-known/abx-service')} descriptor — what it serves (interfaces), which chains
|
|
5556
|
+
(flags a mismatch with your ${g('ABX_CHAIN')}), whether ${bold('rendering is managed')} behind it (code drops then need no effects
|
|
5557
|
+
runner), and where a human gets an API key (${g('auth.signupUrl')}). With a token: lists the projects visible to it —
|
|
5558
|
+
${bold('the one-command "is my provider key valid?" check')} (401 = fix the key · 403 = provider-side scoping, not a typo).`,
|
|
5386
5559
|
migrate: `
|
|
5387
5560
|
${bold('abx migrate')} <address> ${dim('— move a contract\'s OFF-CHAIN state to another resolver (read-only on both; no cutover).')}
|
|
5388
|
-
--from <url>
|
|
5389
|
-
--to <url>
|
|
5561
|
+
--from <name|url> the SOURCE resolver (currently serving the contract) — read via its PUBLIC api; no source credential needed
|
|
5562
|
+
--to <name|url> the DESTINATION resolver (its control plane) — the ONE credential migrate needs: a named remote's
|
|
5563
|
+
${g('ABX_REMOTE_<NAME>_TOKEN')}, else ${g('ABX_RESOLVER_ADMIN_TOKEN')} (your own node's, from ${g('deploy-resolver')}), else --remote-token
|
|
5390
5564
|
--from-block <n> chain scan floor for the local read (default: the deploy block, discovered on-chain — never genesis) --yes allow a very large scan
|
|
5391
5565
|
--backend <id> durable custody for re-pinning source-only images (ipfs · arweave); else config/env
|
|
5392
5566
|
${dim('replays on-chain state on the dest from chain, then bridges description / external_url / off-chain')}
|
|
@@ -5436,7 +5610,8 @@ function help() {
|
|
|
5436
5610
|
--minter 0x.. · --primary-payee 0x.. · --unpaused · ${g('--dry-run')} · see ${g('abx help deploy-series')}
|
|
5437
5611
|
${g('abx predict')} pre-compute a deploy address flags: [--salt 0x..] [--for 0x..] [--factory 0x..]
|
|
5438
5612
|
${g('abx add')} <address> register + index a project this node didn't deploy
|
|
5439
|
-
flags: --from-block --factory --label
|
|
5613
|
+
flags: --from-block --factory --label · ${g('--remote <name|url>')} registers on a REMOTE resolver instead
|
|
5614
|
+
${g('abx remote')} [<name|url>] inspect a remote service: its descriptor (chains · managed rendering · where to get a key) + your projects there
|
|
5440
5615
|
${g('abx index')} [<address>] re-index from chain (incremental by default; ${g('--full')} forces a replay from deploy)
|
|
5441
5616
|
${g('abx verify')} <addr> re-hash served bytes vs the on-chain commitment (no server needed)
|
|
5442
5617
|
${g('abx tokenuri')} <addr> read tokenURI(0) on-chain + decode the JSON (proof a self-resolving token works)
|