@artblocks/abx-cli 0.1.0-alpha.17 → 0.1.0-alpha.19
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 +218 -0
- package/dist/commands/deploy.d.ts +181 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +2974 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/project.d.ts +134 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +919 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/reads.d.ts +64 -0
- package/dist/commands/reads.d.ts.map +1 -0
- package/dist/commands/reads.js +635 -0
- package/dist/commands/reads.js.map +1 -0
- package/dist/commands/scaffold.d.ts +69 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +508 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/service.d.ts +41 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +618 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/storage.d.ts +37 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +335 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/config.d.ts +72 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +201 -4
- package/dist/config.js.map +1 -1
- package/dist/conformance.d.ts +31 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +371 -0
- package/dist/conformance.js.map +1 -0
- package/dist/deps.d.ts +6 -39
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +4 -68
- package/dist/deps.js.map +1 -1
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/flags.d.ts +34 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +74 -0
- package/dist/flags.js.map +1 -1
- package/dist/main.js +220 -6905
- package/dist/main.js.map +1 -1
- package/dist/mintpage.d.ts +3 -1
- package/dist/mintpage.d.ts.map +1 -1
- package/dist/mintpage.js +48 -48
- package/dist/mintpage.js.map +1 -1
- package/dist/output.d.ts +137 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +504 -0
- package/dist/output.js.map +1 -0
- package/dist/ownerops.d.ts +23 -44
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +151 -233
- package/dist/ownerops.js.map +1 -1
- package/dist/preview.d.ts +8 -4
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +8 -17
- package/dist/preview.js.map +1 -1
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +10 -7
- package/dist/provision.js.map +1 -1
- package/dist/remote.d.ts +72 -7
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +211 -9
- package/dist/remote.js.map +1 -1
- package/dist/riskgate.d.ts +29 -0
- package/dist/riskgate.d.ts.map +1 -0
- package/dist/riskgate.js +91 -0
- package/dist/riskgate.js.map +1 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +12 -16
- package/dist/schema.js.map +1 -1
- package/dist/served.d.ts +1 -17
- package/dist/served.d.ts.map +1 -1
- package/dist/served.js +47 -0
- package/dist/served.js.map +1 -1
- package/dist/signer.d.ts +13 -0
- package/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +42 -19
- package/dist/signer.js.map +1 -1
- package/dist/update-check.d.ts +27 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +30 -0
- package/dist/update-check.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +7 -11
- package/skill/reference/code-projects.md +3 -3
- package/skill/reference/hosting.md +6 -6
- package/skill/reference/operating.md +2 -5
- package/skill/reference/setup.md +2 -2
- package/skill/reference/troubleshooting.md +1 -1
- package/dist/gas.d.ts +0 -63
- package/dist/gas.d.ts.map +0 -1
- package/dist/gas.js +0 -76
- package/dist/gas.js.map +0 -1
- package/dist/inspect.d.ts +0 -48
- package/dist/inspect.d.ts.map +0 -1
- package/dist/inspect.js +0 -290
- package/dist/inspect.js.map +0 -1
- package/dist/migrate.d.ts +0 -65
- package/dist/migrate.d.ts.map +0 -1
- package/dist/migrate.js +0 -180
- package/dist/migrate.js.map +0 -1
- package/dist/onchain-uri.d.ts +0 -81
- package/dist/onchain-uri.d.ts.map +0 -1
- package/dist/onchain-uri.js +0 -251
- package/dist/onchain-uri.js.map +0 -1
- package/dist/resume.d.ts +0 -96
- package/dist/resume.d.ts.map +0 -1
- package/dist/resume.js +0 -95
- package/dist/resume.js.map +0 -1
- package/dist/upload.d.ts +0 -28
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -41
- package/dist/upload.js.map +0 -1
package/dist/ownerops.js
CHANGED
|
@@ -6,24 +6,28 @@
|
|
|
6
6
|
* served state reflects the change. The agent picks the lane; the human only
|
|
7
7
|
* approves (wallet lane) or it's the env key (hot lane).
|
|
8
8
|
*/
|
|
9
|
-
import { assertChainId, ensureChunkStore as sdkEnsureChunkStore, predictFixedPriceMinter,
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { hasParamEnumeration } from './onchain-uri.js';
|
|
9
|
+
import { assertChainId, ensureChunkStore as sdkEnsureChunkStore, predictFixedPriceMinter, makeHotSender, makePublicClient, makeWalletClient, oneOfOneImageAbi, seriesImageAbi, deployFixedPriceMinter, prepareConfigureSale, preparePurchase, readSaleConfig, prepareLockContractField, prepareLockContractURI, prepareLockTokenField, prepareLockTokenURI, prepareMint, mintedTokenIds, prepareSeriesMintMany, prepareSetMinter, prepareSetMaxInvocations, prepareSetPrimaryPayee, prepareSetPaused, prepareSetContractField, prepareSetContractURIBase, prepareSetContractURIOverride, prepareSetContractURIRenderer, prepareSetParamHooks, prepareSetParamSchema, prepareRetireParam, readParamSchema, PARAM_TYPES, prepareSetRoyalty, prepareSetTokenField, batchOps, prepareSetTokenURIBase, prepareSetTokenURIOverride, prepareSetTokenURIRenderer, prepareSetTransferValidator, prepareTransfer, prepareTransferOwnership, readCreatorTokenStatus, resolveRecommendedTransferValidator, RECOMMENDED_TRANSFER_VALIDATOR, KNOWN_CHAIN_KEYS, resolveChain, resolveRpcUrl, redactRpcUrl, encodeTag, METADATA_FIELD as F, METADATA_REPRESENTATION as R, stageFieldContent, planStagedContent, exceedsOnchainSoftLimit, ONCHAIN_IMAGE_SOFT_LIMIT, ONCHAIN_PROJECT_SOFT_LIMIT, } from '@artblocks/abx-sdk';
|
|
10
|
+
import { encodeScalarParam, encodeTag as encodeTagSdk, isAccepted, prepareConfigureTokenParam, prepareConfigureTokenParamData, prepareSetContractParam, prepareSetContractParamData, seriesCodeAbi, tryReadContract, } from '@artblocks/abx-sdk';
|
|
11
|
+
import { hasParamEnumeration } from '@artblocks/abx-sdk';
|
|
13
12
|
import { decodeFieldRenderer, resolveGenerator, DEP_RESOLUTION, parseDependencyRef, prepareLockDependencies, prepareRemoveLastDependency, prepareSetDependency, prepareSetDependencyRegistry, } from '@artblocks/abx-sdk';
|
|
14
13
|
import { contentTypeFromPath } from '@artblocks/abx-storage';
|
|
15
14
|
import { toHex as toHexSdk } from 'viem';
|
|
16
15
|
import { readFileSync } from 'node:fs';
|
|
17
16
|
import { basename, resolve as resolvePath } from 'node:path';
|
|
18
17
|
import { gzipSync } from 'node:zlib';
|
|
19
|
-
import {
|
|
20
|
-
import { fixedPriceMinterAddress } from './config.js';
|
|
21
|
-
import {
|
|
18
|
+
import { formatEther, getAddress, isAddress, parseEther, toHex, zeroAddress } from 'viem';
|
|
19
|
+
import { CHAIN, chainId, fixedPriceMinterAddress, localIndexer } from './config.js';
|
|
20
|
+
import { CliError } from './errors.js';
|
|
21
|
+
import { openWalletSession } from './signer.js';
|
|
22
|
+
import { gatedSend, laneFromFlags } from './riskgate.js';
|
|
22
23
|
import { withJson } from './jsonout.js';
|
|
23
24
|
import { resolveRemote, serviceClient } from './remote.js';
|
|
24
|
-
import { positionalArgs, unknownFlags } from './flags.js';
|
|
25
|
+
import { isDryRun, positionalArgs, unknownFlags } from './flags.js';
|
|
25
26
|
import { parseSchemaSpecs, describeSchema } from './schema.js';
|
|
26
|
-
|
|
27
|
+
// Re-exported for main.ts (the on-chain-vs-off-chain cost model + the compression-mode type now
|
|
28
|
+
// live in the SDK's staging.ts, layered on planChunks/planContentTxs — see the comment at their
|
|
29
|
+
// definition below).
|
|
30
|
+
export { ONCHAIN_PROJECT_SOFT_LIMIT };
|
|
27
31
|
// ── ANSI (local) ─────────────────────────────────────────────────────────────
|
|
28
32
|
const C = { reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m', green: '\x1b[38;5;115m', yellow: '\x1b[38;5;221m' };
|
|
29
33
|
const dim = (s) => `${C.dim}${s}${C.reset}`;
|
|
@@ -58,7 +62,7 @@ function fitsLiteralBytes32(value) {
|
|
|
58
62
|
return value.length <= 31 && /^[\x20-\x7e]+$/.test(value);
|
|
59
63
|
}
|
|
60
64
|
/** Flags `abx attach` recognizes — anything else warns (non-fatal), so a silent no-op flag surfaces. */
|
|
61
|
-
const ATTACH_FLAGS = ['file', 'compress', 'collection', 'token', 'send', 'sign', 'unsigned', 'yes', 'dry-run', 'port', 'sign-url-file', 'remote'];
|
|
65
|
+
const ATTACH_FLAGS = ['file', 'compress', 'collection', 'token', 'send', 'sign', 'unsigned', 'yes', 'dry-run', 'confirm', 'port', 'sign-url-file', 'remote'];
|
|
62
66
|
/** Auto-detect the on-chain representation for an off-chain locator by its URI scheme. Returns null
|
|
63
67
|
* for anything that isn't a recognized durable/http locator (so `attach` can refuse it loudly
|
|
64
68
|
* rather than silently storing a bad value — the round-1 `--representation` guessing trap). */
|
|
@@ -72,21 +76,10 @@ export function representationForLocator(uri) {
|
|
|
72
76
|
return R.url;
|
|
73
77
|
return null;
|
|
74
78
|
}
|
|
75
|
-
function chainId() {
|
|
76
|
-
return resolveChain(CHAIN).id;
|
|
77
|
-
}
|
|
78
|
-
/** Resolve the signing lane from flags: --unsigned (cold) · --sign (wallet) · default hot. */
|
|
79
|
-
export function laneFromFlags(flags) {
|
|
80
|
-
if (flags.unsigned)
|
|
81
|
-
return 'unsigned';
|
|
82
|
-
if (flags.sign)
|
|
83
|
-
return 'sign';
|
|
84
|
-
return 'send';
|
|
85
|
-
}
|
|
86
79
|
function requireAddress(address, usage) {
|
|
87
80
|
if (!address || address.startsWith('--')) {
|
|
88
81
|
console.error(`usage: ${usage}\n`);
|
|
89
|
-
|
|
82
|
+
throw new CliError('', 1, true); // already printed above — see CliError's alreadyPrinted doc
|
|
90
83
|
}
|
|
91
84
|
return address;
|
|
92
85
|
}
|
|
@@ -94,12 +87,10 @@ function requireFlag(flags, name, usage) {
|
|
|
94
87
|
const v = flags[name];
|
|
95
88
|
if (!v || v === 'true') {
|
|
96
89
|
console.error(`missing --${name}\nusage: ${usage}\n`);
|
|
97
|
-
|
|
90
|
+
throw new CliError('', 1, true); // already printed above — see CliError's alreadyPrinted doc
|
|
98
91
|
}
|
|
99
92
|
return v;
|
|
100
93
|
}
|
|
101
|
-
/** Run a prepared write through the chosen lane, then re-index if the project is known.
|
|
102
|
-
* Returns whether anything was BROADCAST (false on the cold lane — the tx was only printed). */
|
|
103
94
|
/**
|
|
104
95
|
* Every owner-op's send choke point. Returns the {@link SignResult} when a tx was broadcast, else
|
|
105
96
|
* `null` (dry run, or the cold lane which prints a tx instead of sending one).
|
|
@@ -108,39 +99,20 @@ function requireFlag(flags, name, usage) {
|
|
|
108
99
|
* *receipt* — `abx mint --json` has to report which token id was actually minted, and the only
|
|
109
100
|
* authoritative answer is the Transfer log the mint emitted. Callers that only asked "did it send?"
|
|
110
101
|
* keep working: `null` is falsy, and a result object is truthy.
|
|
102
|
+
*
|
|
103
|
+
* The dry-run preview / `--confirm` prompt / lane selection live in {@link gatedSend} — the SAME
|
|
104
|
+
* choke point the deploy family's resumed-setup send routes through — so this is now just "gate,
|
|
105
|
+
* then re-index if the project is known."
|
|
111
106
|
*/
|
|
112
107
|
async function runWrite(address, provider, flags, expectedSigner) {
|
|
113
|
-
|
|
114
|
-
// owner-op, so --dry-run is uniform across them — it must never fall through to a real send just
|
|
115
|
-
// because the flag isn't wired per-command (a round-1 finding: `set-field --dry-run` silently
|
|
116
|
-
// reached the send lane and only stopped for lack of a key).
|
|
117
|
-
if (flags['dry-run']) {
|
|
118
|
-
const prepared = typeof provider === 'function' ? await provider(expectedSigner ?? zeroAddress) : provider;
|
|
119
|
-
console.log(`\n ${C.bold}◆ ${prepared.summary}${C.reset} ${dim('(dry run — nothing sent)')}`);
|
|
120
|
-
for (const [k, v] of Object.entries(prepared.fields))
|
|
121
|
-
console.log(` ${dim(k.padEnd(12))} ${v}`);
|
|
122
|
-
console.log(` ${dim('to'.padEnd(12))} ${prepared.to ?? dim('(contract deploy)')}`);
|
|
123
|
-
if (expectedSigner)
|
|
124
|
-
console.log(` ${dim('owner'.padEnd(12))} ${expectedSigner}`);
|
|
125
|
-
console.log(dim(`\n Re-run without --dry-run to send (lane: ${laneFromFlags(flags)}).\n`));
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
await assertChainId(CHAIN); // verify the RPC really is CHAIN before any irreversible write
|
|
129
|
-
const result = await signTx(provider, {
|
|
130
|
-
lane: laneFromFlags(flags),
|
|
131
|
-
chainKey: CHAIN,
|
|
132
|
-
expectedSigner,
|
|
133
|
-
yes: !!flags.yes,
|
|
134
|
-
port: flags.port ? Number(flags.port) : undefined,
|
|
135
|
-
signUrlFile: flags['sign-url-file'],
|
|
136
|
-
});
|
|
108
|
+
const result = await gatedSend(provider, flags, { chainKey: CHAIN, expectedSigner });
|
|
137
109
|
if (!result)
|
|
138
|
-
return null; // cold lane — nothing broadcast
|
|
110
|
+
return null; // dry run, or the cold lane — nothing broadcast
|
|
139
111
|
await reindexIfKnown(address, flags);
|
|
140
112
|
return result;
|
|
141
113
|
}
|
|
142
114
|
async function reindexIfKnown(address, flags) {
|
|
143
|
-
const indexer =
|
|
115
|
+
const indexer = localIndexer();
|
|
144
116
|
if (indexer.store.getRegistration(address)) {
|
|
145
117
|
const { state, elapsedMs } = await indexer.reindex(address);
|
|
146
118
|
console.log(` ${green('✓')} re-indexed ${state.name ?? address}: ${state.eventCount} events in ${elapsedMs}ms`);
|
|
@@ -181,22 +153,22 @@ async function reindexIfKnown(address, flags) {
|
|
|
181
153
|
}
|
|
182
154
|
async function read(address, functionName, args = []) {
|
|
183
155
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
156
|
+
const result = await tryReadContract(publicClient, { address, abi: oneOfOneImageAbi, functionName, args });
|
|
157
|
+
if (result !== undefined)
|
|
158
|
+
return result;
|
|
159
|
+
// A read that "returned no data" is usually not a real revert but an address with NO CONTRACT —
|
|
160
|
+
// previewing before deploy, a typo'd address, or the wrong chain. Every owner-op reads the
|
|
161
|
+
// owner/ownerOf here FIRST, so this one guard turns the opaque viem error into an actionable one
|
|
162
|
+
// across all of them. Only runs on the error path (zero cost when the contract exists).
|
|
163
|
+
await assertContractExists(address);
|
|
164
|
+
// The contract DOES exist → a genuine revert. tryReadContract already swallowed that error, so
|
|
165
|
+
// re-read once (uncaught) to surface it rather than a generic "read failed".
|
|
166
|
+
return (await publicClient.readContract({
|
|
167
|
+
address,
|
|
168
|
+
abi: oneOfOneImageAbi,
|
|
169
|
+
functionName: functionName,
|
|
170
|
+
args: args,
|
|
171
|
+
}));
|
|
200
172
|
}
|
|
201
173
|
/** Throw a clear, actionable error when `address` has no contract on the active chain — the common
|
|
202
174
|
* cause of an owner-op's raw `returned no data ("0x")`. No-op when code is present or unknowable. */
|
|
@@ -267,7 +239,8 @@ export async function cmdConfigureParam(address, rest, flags) {
|
|
|
267
239
|
const valueInput = valueParts.join(' ');
|
|
268
240
|
if (!tokenIdRaw || !key || (!valueInput && !flags.file)) {
|
|
269
241
|
console.error(`usage: ${usage}\n`);
|
|
270
|
-
process.
|
|
242
|
+
process.exitCode = 1;
|
|
243
|
+
return;
|
|
271
244
|
}
|
|
272
245
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
273
246
|
const schema = (await publicClient.readContract({
|
|
@@ -450,7 +423,8 @@ export async function cmdSetDependency(address, rest, flags) {
|
|
|
450
423
|
const [indexRaw, refRaw] = positionalArgs(rest);
|
|
451
424
|
if (indexRaw === undefined || !refRaw) {
|
|
452
425
|
console.error(`usage: ${usage}\n`);
|
|
453
|
-
process.
|
|
426
|
+
process.exitCode = 1;
|
|
427
|
+
return;
|
|
454
428
|
}
|
|
455
429
|
if (!/^\d+$/.test(indexRaw))
|
|
456
430
|
throw new Error(`<index> must be a non-negative integer (got '${indexRaw}') — the list is dense: index ≤ dependencyCount`);
|
|
@@ -474,7 +448,8 @@ export async function cmdSetDependencyRegistry(address, rest, flags) {
|
|
|
474
448
|
const [raw] = positionalArgs(rest);
|
|
475
449
|
if (!raw) {
|
|
476
450
|
console.error(`usage: ${usage}\n`);
|
|
477
|
-
process.
|
|
451
|
+
process.exitCode = 1;
|
|
452
|
+
return;
|
|
478
453
|
}
|
|
479
454
|
if (raw !== 'none' && !/^0x[0-9a-fA-F]{40}$/.test(raw)) {
|
|
480
455
|
throw new Error(`<registry> must be an address (0x + 40 hex) or 'none' to clear; got '${raw}'`);
|
|
@@ -520,7 +495,7 @@ export async function cmdMint(address, flags) {
|
|
|
520
495
|
tx = prepareMint({ contract, to, chainId: chainId() });
|
|
521
496
|
}
|
|
522
497
|
const result = await runWrite(contract, tx, flags, owner);
|
|
523
|
-
const tokenIds = result ? await mintedTokenIds(contract, result.txHash) : [];
|
|
498
|
+
const tokenIds = result ? await mintedTokenIds(makePublicClient({ chainKey: CHAIN }), contract, result.txHash) : [];
|
|
524
499
|
if (result && tokenIds.length) {
|
|
525
500
|
console.log(` ${green('✓')} minted token${tokenIds.length > 1 ? 's' : ''} ${bold(tokenIds.map((t) => '#' + t).join(', '))} → ${to}`);
|
|
526
501
|
}
|
|
@@ -539,43 +514,6 @@ export async function cmdMint(address, flags) {
|
|
|
539
514
|
};
|
|
540
515
|
});
|
|
541
516
|
}
|
|
542
|
-
/**
|
|
543
|
-
* The token ids a mint actually created, decoded from its receipt's `Transfer(from=0x0)` logs.
|
|
544
|
-
*
|
|
545
|
-
* Authoritative by construction: the ids come from the transaction that minted them, so a
|
|
546
|
-
* `--count N` batch reports all N and a concurrent mint elsewhere cannot skew the answer. Returns
|
|
547
|
-
* `[]` rather than throwing if the receipt can't be read — a mint that landed on-chain must not be
|
|
548
|
-
* reported as failed because a follow-up read hiccuped.
|
|
549
|
-
*/
|
|
550
|
-
async function mintedTokenIds(contract, txHash) {
|
|
551
|
-
try {
|
|
552
|
-
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
553
|
-
const receipt = await publicClient.getTransactionReceipt({ hash: txHash });
|
|
554
|
-
const ids = [];
|
|
555
|
-
for (const log of receipt.logs) {
|
|
556
|
-
if (log.address.toLowerCase() !== contract.toLowerCase())
|
|
557
|
-
continue;
|
|
558
|
-
try {
|
|
559
|
-
const decoded = decodeEventLog({ abi: oneOfOneImageAbi, data: log.data, topics: log.topics });
|
|
560
|
-
if (decoded.eventName !== 'Transfer')
|
|
561
|
-
continue;
|
|
562
|
-
const args = decoded.args;
|
|
563
|
-
if (args.from && args.from !== zeroAddress)
|
|
564
|
-
continue; // a transfer, not a mint
|
|
565
|
-
const id = args.id ?? args.tokenId;
|
|
566
|
-
if (id !== undefined)
|
|
567
|
-
ids.push(id.toString());
|
|
568
|
-
}
|
|
569
|
-
catch {
|
|
570
|
-
continue; // a log from another interface on the same contract
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return ids.sort((a, b) => Number(BigInt(a) - BigInt(b)));
|
|
574
|
-
}
|
|
575
|
-
catch {
|
|
576
|
-
return [];
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
517
|
// ── series owner ops (minter set · supply cap · primary payee) ────────────────
|
|
580
518
|
// The multi-token knobs. All owner-only; each runs through the same signing lane +
|
|
581
519
|
// re-index as every other owner op.
|
|
@@ -817,7 +755,8 @@ export async function cmdSetTransferValidator(address, rest, flags) {
|
|
|
817
755
|
const [raw] = positionalArgs(rest);
|
|
818
756
|
if (!raw) {
|
|
819
757
|
console.error(`usage: ${usage}\n`);
|
|
820
|
-
process.
|
|
758
|
+
process.exitCode = 1;
|
|
759
|
+
return;
|
|
821
760
|
}
|
|
822
761
|
const cid = chainId();
|
|
823
762
|
const validator = parseTransferValidatorValue(raw, { chainId: cid, chainLabel: CHAIN, allowNone: true });
|
|
@@ -859,16 +798,41 @@ export async function cmdSetTransferValidator(address, rest, flags) {
|
|
|
859
798
|
const owner = await read(contract, 'owner');
|
|
860
799
|
await runWrite(contract, prepareSetTransferValidator({ contract, validator, chainId: cid }), flags, owner);
|
|
861
800
|
}
|
|
801
|
+
// ── multi-chunk on-chain content (the `reader` path) ─────────────────────────
|
|
802
|
+
// The plan math (chunk count + tx shape), the cost-model constants, and the actual
|
|
803
|
+
// plan→resolve-store→stage→encode orchestration all live in the SDK's staging.ts, layered on
|
|
804
|
+
// planChunks/planContentTxs/stageContent/encodeReader/ensureChunkStore — see its module doc. What
|
|
805
|
+
// stays here is Node-only (reading the file, gzip) plus the narration: every console.log below is
|
|
806
|
+
// this file's onEvent handler for the SDK's `StagingEvent`, so the lines are unchanged from before
|
|
807
|
+
// the move.
|
|
862
808
|
export function parseCompress(v) {
|
|
863
809
|
const c = (v ?? 'none').toLowerCase();
|
|
864
810
|
if (c === 'none' || c === 'fastlz' || c === 'gzip')
|
|
865
811
|
return c;
|
|
866
812
|
throw new Error(`--compress must be none | fastlz | gzip (got '${v}')`);
|
|
867
813
|
}
|
|
814
|
+
/** The chunk-store bootstrap narration shared by every content-staging path below — one place so
|
|
815
|
+
* the lines are identical whether the store is resolved for a single field ({@link putContentOnChain}
|
|
816
|
+
* → the SDK's `stageFieldContent`) or once up front for a whole batch ({@link ensureChunkStore}). */
|
|
817
|
+
function printChunkStoreEvent(e) {
|
|
818
|
+
if (e.kind === 'stale')
|
|
819
|
+
console.log(yellow(` configured chunk store ${e.address} is a stale deployment (no writeContent) — deploying a current one`));
|
|
820
|
+
else if (e.kind === 'canonical')
|
|
821
|
+
console.log(dim(` using the canonical chunk store at its deterministic address ${e.address}`));
|
|
822
|
+
else if (e.kind === 'deploying')
|
|
823
|
+
console.log(dim(' deploying the canonical multi-chunk content store (AbxChunkStore) — CREATE2…'));
|
|
824
|
+
else {
|
|
825
|
+
console.log(` ${green('✓')} chunk store ${e.address}`);
|
|
826
|
+
console.log(dim(` not in the shipped manifest for ${CHAIN} — to reuse it set ABX_CHUNK_STORE=${e.address} (or add it to packages/sdk/src/deployments.ts)`));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
868
829
|
/**
|
|
869
830
|
* The chain's shared multi-chunk store/reader; deploy it once if missing, signing through
|
|
870
831
|
* `send` (the env key on the hot lane, or the connected wallet on the wallet lane) — the
|
|
871
|
-
* store is ownerless, so any funded signer can stand it up.
|
|
832
|
+
* store is ownerless, so any funded signer can stand it up. Used directly only by
|
|
833
|
+
* {@link stageImageFieldsBatch} (resolving ONE store up front for every token); a single-field
|
|
834
|
+
* stage ({@link putContentOnChain}) resolves its store through the SDK's `stageFieldContent`
|
|
835
|
+
* instead, which calls the very same SDK `ensureChunkStore` internally.
|
|
872
836
|
*/
|
|
873
837
|
async function ensureChunkStore(send, override) {
|
|
874
838
|
// The resolution logic lives in the SDK (`ensureChunkStore`) so an SDK integrator bootstraps
|
|
@@ -876,99 +840,46 @@ async function ensureChunkStore(send, override) {
|
|
|
876
840
|
// incapable store fails deep inside a mint, after transactions have landed. The CLI keeps only the
|
|
877
841
|
// narration: the SDK reports progress through `onEvent` rather than printing.
|
|
878
842
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
879
|
-
return sdkEnsureChunkStore(publicClient, send, {
|
|
880
|
-
chainId: chainId(),
|
|
881
|
-
override,
|
|
882
|
-
onEvent: (e) => {
|
|
883
|
-
if (e.kind === 'stale')
|
|
884
|
-
console.log(yellow(` configured chunk store ${e.address} is a stale deployment (no writeContent) — deploying a current one`));
|
|
885
|
-
else if (e.kind === 'canonical')
|
|
886
|
-
console.log(dim(` using the canonical chunk store at its deterministic address ${e.address}`));
|
|
887
|
-
else if (e.kind === 'deploying')
|
|
888
|
-
console.log(dim(' deploying the canonical multi-chunk content store (AbxChunkStore) — CREATE2…'));
|
|
889
|
-
else {
|
|
890
|
-
console.log(` ${green('✓')} chunk store ${e.address}`);
|
|
891
|
-
console.log(dim(` not in the shipped manifest for ${CHAIN} — to reuse it set ABX_CHUNK_STORE=${e.address} (or add it to packages/sdk/src/deployments.ts)`));
|
|
892
|
-
}
|
|
893
|
-
},
|
|
894
|
-
});
|
|
843
|
+
return sdkEnsureChunkStore(publicClient, send, { chainId: chainId(), override, onEvent: printChunkStoreEvent });
|
|
895
844
|
}
|
|
896
845
|
/**
|
|
897
|
-
* The hot-lane staging signer: the env key signs + broadcasts each chunk-store write
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
846
|
+
* The hot-lane staging signer: the env key signs + broadcasts each chunk-store write, via the
|
|
847
|
+
* SDK's `makeHotSender` (pinned nonce, pinned gas, typed revert). Built lazily so commands that
|
|
848
|
+
* never stage on-chain don't require a key. The wallet lane passes a session-backed sender
|
|
849
|
+
* instead (see {@link openWalletSession}); the cold lane can't stage interactively, so it's
|
|
850
|
+
* rejected up front by the deploy/set-field commands.
|
|
901
851
|
*/
|
|
902
852
|
export function envStagingSender() {
|
|
903
853
|
const { wallet, account } = makeWalletClient({ chainKey: CHAIN });
|
|
904
854
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
905
|
-
return
|
|
906
|
-
const hash = await wallet.sendTransaction({
|
|
907
|
-
account,
|
|
908
|
-
chain: wallet.chain,
|
|
909
|
-
to: tx.to ?? undefined,
|
|
910
|
-
data: tx.data,
|
|
911
|
-
value: 0n,
|
|
912
|
-
});
|
|
913
|
-
return publicClient.waitForTransactionReceipt({ hash });
|
|
914
|
-
};
|
|
855
|
+
return makeHotSender({ wallet, account, publicClient });
|
|
915
856
|
}
|
|
916
857
|
/** Wallet-lane staging signer: each chunk-store write is approved in the human's browser
|
|
917
|
-
* wallet through the open {@link WalletSession}, sharing one connection with the final tx.
|
|
858
|
+
* wallet through the open {@link WalletSession}, sharing one connection with the final tx. A
|
|
859
|
+
* {@link PreparedTx} already carries everything `session.send` needs, so this is a thin adapter. */
|
|
918
860
|
export function sessionStagingSender(session) {
|
|
919
|
-
|
|
920
|
-
return async (tx) => (await session.send({
|
|
921
|
-
op: 'stage',
|
|
922
|
-
to: tx.to,
|
|
923
|
-
data: tx.data,
|
|
924
|
-
value: '0x0',
|
|
925
|
-
chainId: id,
|
|
926
|
-
summary: tx.summary,
|
|
927
|
-
fields: tx.fields,
|
|
928
|
-
})).receipt;
|
|
861
|
+
return async (tx) => (await session.send(tx)).receipt;
|
|
929
862
|
}
|
|
930
|
-
/**
|
|
931
|
-
* Put `content` on-chain as SSTORE2 chunks behind the shared reader, and return the field's
|
|
932
|
-
* `(representation, value)`. `fastlz` compresses per chunk (reader decodes on read → stays
|
|
933
|
-
* on-chain renderable); `gzip` compresses the whole content (off-chain decode → the
|
|
934
|
-
* `reader-gzip` representation, not on-chain renderable).
|
|
935
|
-
*
|
|
936
|
-
* Staging is one atomic `writeContent` when the content fits a single tx's gas, else
|
|
937
|
-
* gas-bounded chunk-write `multicall`s plus a final manifest write — {@link stageContent}
|
|
938
|
-
* picks, and we print the tx plan up front so the human knows the count before signing.
|
|
939
|
-
* These store writes use the env key (the data contracts are ownerless); the owner only
|
|
940
|
-
* signs the field set that references the manifest.
|
|
941
|
-
*/
|
|
942
|
-
/**
|
|
943
|
-
* Pure staging plan for a piece of content — chunk count + transaction shape — with NO
|
|
944
|
-
* chain writes, no signer, no `ensureChunkStore`. Shared by {@link putContentOnChain}
|
|
945
|
-
* (which then actually stages) and the deploy dry-run preview, so the count the human is
|
|
946
|
-
* told up front is the same one the real staging will produce. `chunkSize` defaults to the
|
|
947
|
-
* SDK's {@link planChunks} default (currently 22 KB), so e.g. a 2.8 KB SVG is 1 chunk.
|
|
948
|
-
*/
|
|
949
|
-
// On-chain image storage costs ~200 gas/byte (SSTORE2 code deposit) + per-chunk tx overhead, so it
|
|
950
|
-
// is cost-effective ONLY for tiny art. Past these soft limits, off-chain (Arweave pay-once, or a
|
|
951
|
-
// hosted resolver + IPFS) is FAR cheaper — on-chain's remaining edge is self-resolution, not cost.
|
|
952
|
-
// Per-file limit ≈ one 22 KB storage chunk; project limit caps the whole collection's gas.
|
|
953
|
-
export const ONCHAIN_IMAGE_SOFT_LIMIT = 24 * 1024; // per file
|
|
954
|
-
export const ONCHAIN_PROJECT_SOFT_LIMIT = 256 * 1024; // whole collection
|
|
955
863
|
const kb = (n) => `${n < 10 * 1024 ? (n / 1024).toFixed(1) : Math.round(n / 1024)}KB`;
|
|
956
864
|
/** Warn (loudly) when content is too big for on-chain to be the cost-sane choice. Returns true if it warned. */
|
|
957
865
|
export function warnIfLargeForOnChain(bytesLen, label) {
|
|
958
|
-
if (bytesLen
|
|
866
|
+
if (!exceedsOnchainSoftLimit(bytesLen))
|
|
959
867
|
return false;
|
|
960
868
|
console.log(yellow(` ⚠ ${label} is ${kb(bytesLen)} — large for ON-CHAIN storage (~200 gas/byte). Past ~${kb(ONCHAIN_IMAGE_SOFT_LIMIT)}/file, on-chain is FAR more expensive than off-chain, not cheaper.`));
|
|
961
869
|
console.log(dim(` For art this size prefer off-chain: --backend arweave (pay-once, permanent) or a hosted resolver + IPFS. Use on-chain only if self-resolution is a hard requirement.`));
|
|
962
870
|
return true;
|
|
963
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* Pure staging plan for a piece of content — chunk count + transaction shape — with NO chain
|
|
874
|
+
* writes, no signer, no `ensureChunkStore`. The CLI-facing wrapper over the SDK's pure
|
|
875
|
+
* {@link planStagedContent}: `gzip` is a Node-only transform, so it happens here (not in the SDK —
|
|
876
|
+
* see staging.ts's module doc) before handing the result off to the SDK's chunk/tx-shape math.
|
|
877
|
+
* Shared by {@link putContentOnChain} (which then actually stages) and the deploy dry-run preview,
|
|
878
|
+
* so the count the human is told up front is the same one the real staging will produce.
|
|
879
|
+
*/
|
|
964
880
|
export function computeContentPlan(bytes, compress) {
|
|
965
881
|
const content = compress === 'gzip' ? new Uint8Array(gzipSync(bytes)) : new Uint8Array(bytes);
|
|
966
|
-
|
|
967
|
-
const planned = planChunks(content, { fastlz });
|
|
968
|
-
const plan = planContentTxs(planned);
|
|
969
|
-
const representation = compress === 'gzip' ? R.readerGzip : R.reader;
|
|
970
|
-
const stagedBytes = planned.reduce((n, c) => n + c.data.length, 0);
|
|
971
|
-
return { content, fastlz, chunks: planned.length, stagedBytes, plan, representation };
|
|
882
|
+
return planStagedContent(content, compress);
|
|
972
883
|
}
|
|
973
884
|
/** Human-readable size line for a content plan, e.g. `2812B → 1 chunk [fastlz 2812→904B]`. */
|
|
974
885
|
function planSizeLine(bytes, p, compress) {
|
|
@@ -990,15 +901,43 @@ function planTxLine(p, finalLabel) {
|
|
|
990
901
|
? `plan: 1 staging tx (atomic writeContent) + ${finalLabel} = 2 transactions`
|
|
991
902
|
: `plan: ${p.plan.batches.length} chunk-batch tx(s) + 1 manifest tx + ${finalLabel} = ${p.plan.txCount + 1} transactions`;
|
|
992
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Put `bytes` on-chain as SSTORE2 chunks behind the shared reader, and return the field's
|
|
906
|
+
* `(representation, value)`. `fastlz` compresses per chunk (reader decodes on read → stays
|
|
907
|
+
* on-chain renderable); `gzip` compresses the whole content (off-chain decode → the
|
|
908
|
+
* `reader-gzip` representation, not on-chain renderable) — done here, not in the SDK's
|
|
909
|
+
* `stageFieldContent` (a Node-only transform; see staging.ts's module doc).
|
|
910
|
+
*
|
|
911
|
+
* Staging is one atomic `writeContent` when the content fits a single tx's gas, else
|
|
912
|
+
* gas-bounded chunk-write `multicall`s plus a final manifest write — the SDK picks, and we
|
|
913
|
+
* print the tx plan up front (via its `onEvent`) so the human knows the count before signing.
|
|
914
|
+
* These store writes use the env key (the data contracts are ownerless); the owner only
|
|
915
|
+
* signs the field set that references the manifest.
|
|
916
|
+
*/
|
|
993
917
|
async function putContentOnChain(bytes, compress, field, send, finalLabel = '1 owner field-set', store) {
|
|
994
|
-
const p = computeContentPlan(bytes, compress);
|
|
995
918
|
warnIfLargeForOnChain(bytes.length, `'${field}'`);
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
919
|
+
const content = compress === 'gzip' ? new Uint8Array(gzipSync(bytes)) : new Uint8Array(bytes);
|
|
920
|
+
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
921
|
+
return stageFieldContent({
|
|
922
|
+
content,
|
|
923
|
+
compress,
|
|
924
|
+
field,
|
|
925
|
+
send,
|
|
926
|
+
publicClient,
|
|
927
|
+
chainId: chainId(),
|
|
928
|
+
store,
|
|
929
|
+
onEvent: (e) => {
|
|
930
|
+
if (e.kind === 'chunk-store')
|
|
931
|
+
printChunkStoreEvent(e.event);
|
|
932
|
+
else if (e.kind === 'planned') {
|
|
933
|
+
console.log(dim(` '${field}' on-chain: ${planSizeLine(bytes.length, e.contentPlan, compress)} via reader ${e.store}`));
|
|
934
|
+
console.log(dim(` ${planTxLine(e.contentPlan, finalLabel)}`));
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
console.log(dim(` staged in ${e.txHashes.length} tx(s); manifest → ${e.manifest}`));
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
});
|
|
1002
941
|
}
|
|
1003
942
|
/**
|
|
1004
943
|
* Dry-run preview of staging an image fully on-chain — the REAL chunk + transaction plan,
|
|
@@ -1113,7 +1052,7 @@ export async function cmdSetField(address, flags) {
|
|
|
1113
1052
|
const staging = !!(flags.file && flags.file !== 'true'); // large content ON-CHAIN via chunk store/reader
|
|
1114
1053
|
// --file + --dry-run: preview the on-chain staging plan and store/send NOTHING. (The locator/text
|
|
1115
1054
|
// path flows through runWrite, which previews there; staging must short-circuit before any upload.)
|
|
1116
|
-
if (staging && flags
|
|
1055
|
+
if (staging && isDryRun(flags)) {
|
|
1117
1056
|
const bytes = readFileSync(resolvePath(flags.file));
|
|
1118
1057
|
const p = computeContentPlan(bytes, parseCompress(flags.compress));
|
|
1119
1058
|
console.log(`\n ${bold('◆ set-field ' + field)} ${dim('(dry run — nothing staged or sent)')}`);
|
|
@@ -1222,13 +1161,15 @@ export async function cmdAttach(rest, flags) {
|
|
|
1222
1161
|
const contract = requireAddress(address, usage);
|
|
1223
1162
|
if (!key) {
|
|
1224
1163
|
console.error(`usage: ${usage}\n the <key> is the name your file appears under in the manifest (e.g. print, certificate, stems, readme).\n`);
|
|
1225
|
-
process.
|
|
1164
|
+
process.exitCode = 1;
|
|
1165
|
+
return;
|
|
1226
1166
|
}
|
|
1227
1167
|
assertSettableField(key);
|
|
1228
1168
|
const hasFile = !!(flags.file && flags.file !== 'true');
|
|
1229
1169
|
if (!uri && !hasFile) {
|
|
1230
1170
|
console.error(`usage: ${usage}\n pass a locator URI (ipfs://… / ar://… / https://…) or --file <path> to store bytes on-chain.\n`);
|
|
1231
|
-
process.
|
|
1171
|
+
process.exitCode = 1;
|
|
1172
|
+
return;
|
|
1232
1173
|
}
|
|
1233
1174
|
if (uri && hasFile)
|
|
1234
1175
|
throw new Error('pass EITHER a locator URI OR --file <path>, not both.');
|
|
@@ -1429,16 +1370,6 @@ async function readSeries(token, fn, args = []) {
|
|
|
1429
1370
|
args: args,
|
|
1430
1371
|
}));
|
|
1431
1372
|
}
|
|
1432
|
-
async function readSale(minter, token) {
|
|
1433
|
-
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
1434
|
-
const [configured, paymentToken, price, allocation, sold] = (await publicClient.readContract({
|
|
1435
|
-
address: minter,
|
|
1436
|
-
abi: abxFixedPriceMinterAbi,
|
|
1437
|
-
functionName: 'sales',
|
|
1438
|
-
args: [token],
|
|
1439
|
-
}));
|
|
1440
|
-
return { configured, paymentToken, price, allocation, sold };
|
|
1441
|
-
}
|
|
1442
1373
|
/** Resolve the shared minter for the chain (flag → env → manifest), or deploy it if none exists.
|
|
1443
1374
|
* It's ownerless, so any funded signer can stand it up — the sibling of `ensureChunkStore`. */
|
|
1444
1375
|
async function ensureFixedPriceMinter(override) {
|
|
@@ -1461,38 +1392,23 @@ async function ensureFixedPriceMinter(override) {
|
|
|
1461
1392
|
}
|
|
1462
1393
|
}
|
|
1463
1394
|
const { wallet, account } = makeWalletClient({ chainKey: CHAIN });
|
|
1395
|
+
const send = makeHotSender({ wallet, account, publicClient });
|
|
1464
1396
|
console.log(dim(' deploying the canonical fixed-price minter (shared, ownerless) — CREATE2…'));
|
|
1465
|
-
const { minter, txHash } = await deployFixedPriceMinter(
|
|
1397
|
+
const { minter, txHash } = await deployFixedPriceMinter(send, publicClient);
|
|
1466
1398
|
console.log(` ${green('✓')} fixed-price minter ${minter}`);
|
|
1467
1399
|
console.log(dim(` tx ${txHash}`));
|
|
1468
1400
|
console.log(dim(` not in the shipped manifest for ${CHAIN} — to reuse it set ${bold(`ABX_FIXED_PRICE_MINTER=${minter}`)} (or add it to packages/sdk/src/deployments.ts)`));
|
|
1469
1401
|
return minter;
|
|
1470
1402
|
}
|
|
1471
1403
|
/** A minter write (configure/buy). No re-index — the sale lives on the minter, not the token
|
|
1472
|
-
* projection; the token's own state is unchanged by a sale config.
|
|
1404
|
+
* projection; the token's own state is unchanged by a sale config.
|
|
1405
|
+
*
|
|
1406
|
+
* Used to hand-roll its own dry-run guard — "the minter path does not go through runWrite, so it
|
|
1407
|
+
* needs its OWN" — which is exactly the duplication {@link gatedSend} exists to end: this now shares
|
|
1408
|
+
* the identical choke point `runWrite` does, so `minter configure`/`minter buy` also pick up
|
|
1409
|
+
* `--confirm` (previously only the deploy family had it) with no extra code here. */
|
|
1473
1410
|
async function runMinterWrite(provider, flags, expectedSigner) {
|
|
1474
|
-
|
|
1475
|
-
// through runWrite, so it needs its OWN dry-run guard — without it, `minter configure --dry-run`
|
|
1476
|
-
// silently signs + broadcasts a real sale (a critical footgun: a mainnet creator "previewing"
|
|
1477
|
-
// their sale would send it). Mirrors runWrite's guard.
|
|
1478
|
-
if (flags['dry-run']) {
|
|
1479
|
-
const prepared = typeof provider === 'function' ? await provider(expectedSigner ?? zeroAddress) : provider;
|
|
1480
|
-
console.log(`\n ${bold('◆ ' + prepared.summary)} ${dim('(dry run — nothing sent)')}`);
|
|
1481
|
-
for (const [k, v] of Object.entries(prepared.fields))
|
|
1482
|
-
console.log(` ${dim(k.padEnd(12))} ${v}`);
|
|
1483
|
-
console.log(` ${dim('to'.padEnd(12))} ${prepared.to ?? dim('(contract deploy)')}`);
|
|
1484
|
-
console.log(dim(`\n Re-run without --dry-run to send (lane: ${laneFromFlags(flags)}).\n`));
|
|
1485
|
-
return;
|
|
1486
|
-
}
|
|
1487
|
-
await assertChainId(CHAIN);
|
|
1488
|
-
await signTx(provider, {
|
|
1489
|
-
lane: laneFromFlags(flags),
|
|
1490
|
-
chainKey: CHAIN,
|
|
1491
|
-
expectedSigner,
|
|
1492
|
-
yes: !!flags.yes,
|
|
1493
|
-
port: flags.port ? Number(flags.port) : undefined,
|
|
1494
|
-
signUrlFile: flags['sign-url-file'],
|
|
1495
|
-
});
|
|
1411
|
+
await gatedSend(provider, flags, { chainKey: CHAIN, expectedSigner });
|
|
1496
1412
|
}
|
|
1497
1413
|
/** Parse the sale price: `--price-raw <units>` (exact base units) or the friendly `--price <ether>`
|
|
1498
1414
|
* (18-decimals). ETH sales use `--price`; non-18-dp ERC-20s should use `--price-raw`. */
|
|
@@ -1502,7 +1418,7 @@ function parseSalePrice(flags, usage) {
|
|
|
1502
1418
|
if (flags.price && flags.price !== 'true')
|
|
1503
1419
|
return parseEther(flags.price);
|
|
1504
1420
|
console.error(`missing --price <ether> or --price-raw <units>\nusage: ${usage}\n`);
|
|
1505
|
-
|
|
1421
|
+
throw new CliError('', 1, true); // already printed above — see CliError's alreadyPrinted doc
|
|
1506
1422
|
}
|
|
1507
1423
|
/**
|
|
1508
1424
|
* `abx minter configure <token> --price <eth> --allocation <n> [--erc20 0x…]` — set the fixed-price
|
|
@@ -1519,7 +1435,7 @@ export async function cmdMinterConfigure(address, flags) {
|
|
|
1519
1435
|
const price = parseSalePrice(flags, usage);
|
|
1520
1436
|
// On --dry-run resolve the minter WITHOUT deploying — ensureFixedPriceMinter would deploy the
|
|
1521
1437
|
// shared singleton if absent, and a preview must never send. Fall back to a label if none exists.
|
|
1522
|
-
const dryRun =
|
|
1438
|
+
const dryRun = isDryRun(flags);
|
|
1523
1439
|
const knownMinter = fixedPriceMinterAddress(flags['minter-contract']);
|
|
1524
1440
|
if (dryRun && !knownMinter) {
|
|
1525
1441
|
console.log(yellow(` ⚠ no shared minter deployed on ${CHAIN} yet — a real run deploys it once (a separate tx) before configuring.`));
|
|
@@ -1575,7 +1491,7 @@ export async function cmdMinterShow(address, flags) {
|
|
|
1575
1491
|
console.log(yellow(` no shared minter known for ${CHAIN} — configure a sale (deploys it) or set ABX_FIXED_PRICE_MINTER`));
|
|
1576
1492
|
return;
|
|
1577
1493
|
}
|
|
1578
|
-
const sale = await
|
|
1494
|
+
const sale = await readSaleConfig(makePublicClient({ chainKey: CHAIN }), minter, token);
|
|
1579
1495
|
const [assignedMinter, payee, paused, max, supply] = await Promise.all([
|
|
1580
1496
|
readSeries(token, 'minter').catch(() => zeroAddress),
|
|
1581
1497
|
readSeries(token, 'primaryPayee').catch(() => zeroAddress),
|
|
@@ -1614,7 +1530,7 @@ export async function cmdMinterBuy(address, flags) {
|
|
|
1614
1530
|
const minter = fixedPriceMinterAddress(flags['minter-contract']);
|
|
1615
1531
|
if (!minter)
|
|
1616
1532
|
throw new Error(`no shared minter known for ${CHAIN} — configure a sale first, or set ABX_FIXED_PRICE_MINTER`);
|
|
1617
|
-
const sale = await
|
|
1533
|
+
const sale = await readSaleConfig(makePublicClient({ chainKey: CHAIN }), minter, token);
|
|
1618
1534
|
if (!sale.configured)
|
|
1619
1535
|
throw new Error(`no sale configured for ${token} — run: abx minter configure ${token} …`);
|
|
1620
1536
|
const isEth = sale.paymentToken === zeroAddress;
|
|
@@ -1662,7 +1578,8 @@ export async function cmdSetSchema(address, flags) {
|
|
|
1662
1578
|
const specs = parseSchemaSpecs(flags.schema);
|
|
1663
1579
|
if (specs.length !== 1) {
|
|
1664
1580
|
console.error(`usage: ${usage}\n\n One key per call — a schema write is a full-row upsert, so batching them hides which one changed.\n`);
|
|
1665
|
-
process.
|
|
1581
|
+
process.exitCode = 1;
|
|
1582
|
+
return;
|
|
1666
1583
|
}
|
|
1667
1584
|
const next = specs[0];
|
|
1668
1585
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
@@ -1734,7 +1651,8 @@ export async function cmdRetireParam(address, rest, flags) {
|
|
|
1734
1651
|
const [key] = positionalArgs(rest);
|
|
1735
1652
|
if (!key) {
|
|
1736
1653
|
console.error(`usage: ${usage}\n`);
|
|
1737
|
-
process.
|
|
1654
|
+
process.exitCode = 1;
|
|
1655
|
+
return;
|
|
1738
1656
|
}
|
|
1739
1657
|
const publicClient = makePublicClient({ chainKey: CHAIN });
|
|
1740
1658
|
const owner = await read(contract, 'owner');
|