@artblocks/abx-cli 0.1.0-alpha.21 → 0.1.0-alpha.23
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 +906 -0
- package/assets/renderer-scaffold/README.md +2 -2
- package/assets/renderer-scaffold/src/MyRenderer.sol +2 -2
- package/assets/renderer-scaffold/src/interfaces/IAbxFieldRenderer.sol +1 -1
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +2 -2
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +1 -1
- package/dist/commands/deploy.d.ts +30 -17
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +234 -97
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/project.d.ts +16 -0
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +150 -10
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reads.d.ts.map +1 -1
- package/dist/commands/reads.js +8 -4
- package/dist/commands/reads.js.map +1 -1
- package/dist/commands/scaffold.d.ts +3 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +64 -20
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/service.js +1 -1
- package/dist/commands/service.js.map +1 -1
- package/dist/commands/submit-app.d.ts +58 -0
- package/dist/commands/submit-app.d.ts.map +1 -0
- package/dist/commands/submit-app.js +512 -0
- package/dist/commands/submit-app.js.map +1 -0
- package/dist/config.d.ts +1 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/flag-allowlists.d.ts.map +1 -1
- package/dist/flag-allowlists.js +5 -1
- package/dist/flag-allowlists.js.map +1 -1
- package/dist/flags.d.ts +4 -0
- package/dist/flags.d.ts.map +1 -1
- package/dist/flags.js +23 -0
- package/dist/flags.js.map +1 -1
- package/dist/main.js +125 -37
- package/dist/main.js.map +1 -1
- package/dist/mintpage.d.ts.map +1 -1
- package/dist/mintpage.js +29 -4
- package/dist/mintpage.js.map +1 -1
- package/dist/output.d.ts +32 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +74 -9
- package/dist/output.js.map +1 -1
- package/dist/ownerops.d.ts +101 -13
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +357 -89
- package/dist/ownerops.js.map +1 -1
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +1 -1
- package/dist/schema.d.ts +18 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +37 -2
- package/dist/schema.js.map +1 -1
- package/dist/served.js +1 -1
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +9 -3
- package/dist/update-check.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +62 -32
- package/skill/reference/code-projects.md +78 -24
- package/skill/reference/creator-token.md +16 -3
- package/skill/reference/decisions.md +59 -13
- package/skill/reference/hosting.md +1 -1
- package/skill/reference/operating.md +33 -13
- package/skill/reference/setup.md +1 -1
- package/skill/reference/troubleshooting.md +16 -6
package/dist/commands/deploy.js
CHANGED
|
@@ -15,20 +15,20 @@
|
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
17
17
|
import { basename, extname, join as joinPath, resolve as resolvePath } from 'node:path';
|
|
18
|
-
import { DEFAULT_CHAIN_KEY, DEP_RESOLUTION, METADATA_FIELD as F, METADATA_REPRESENTATION as R, analyzeScript, assertChainId, checkRegistryDeps, dependencySetupCalls, deployOneOfOne, deploySeries, deployOneOfOneEdition, deployEditionImage, discoverDeployBlock, encodeFieldRenderer, encodeTag, expectedChainComplete, makeHotSender, makePublicClient, makeWalletClient, normalizeAttributes, onchainUriSetupCalls, oneOfOneImageAbi, oneOfOneEditionAbi, parseTraitPairs, planResume, predictClone, prepareCodeSetup, prepareDeployOneOfOne, prepareDeploySeries, prepareDeploySeriesCode, prepareDeployOneOfOneEdition, prepareDeployEditionImage, prepareDeployEditionCode, redactRpcUrl, resolveChain, resolveDepRegistryPointer, resolveGenerator, resolveRecommendedTransferValidator, resolveRpcUrl, resolveSeedSource, resolveSeriesCodeFactory, saltFor, seriesCodeAbi, seriesCodeFactoryAbi, tryReadContract, } from '@artblocks/abx-sdk';
|
|
18
|
+
import { DEFAULT_CHAIN_KEY, DEP_RESOLUTION, METADATA_FIELD as F, METADATA_REPRESENTATION as R, analyzeScript, assertChainId, checkRegistryDeps, dependencySetupCalls, deployOneOfOne, deploySeries, deployOneOfOneEdition, deployEditionImage, discoverDeployBlock, encodeFieldRenderer, encodeTag, expectedChainComplete, makeHotSender, makePublicClient, makeWalletClient, normalizeAttributes, onchainUriSetupCalls, oneOfOneImageAbi, oneOfOneEditionAbi, parseTraitPairs, planResume, predictClone, probeSeedSource, prepareCodeSetup, prepareDeployOneOfOne, prepareDeploySeries, prepareDeploySeriesCode, prepareDeployOneOfOneEdition, prepareDeployEditionImage, prepareDeployEditionCode, redactRpcUrl, resolveChain, resolveDepRegistryPointer, resolveGenerator, probeTransferValidator, resolveRecommendedTransferValidator, resolveRpcUrl, resolveSeedSource, resolveSeriesCodeFactory, saltFor, seriesCodeAbi, seriesCodeFactoryAbi, tryReadContract, } from '@artblocks/abx-sdk';
|
|
19
19
|
import { DIRECT_URL_BACKENDS, contentTypeFromPath, decideImageContentLane, hashContent, isTurboArweave, resolveBackend, validateRenderStorageCombo, } from '@artblocks/abx-storage';
|
|
20
|
-
import { DEFAULT_PORT,
|
|
20
|
+
import { DEFAULT_PORT, contentHash, generateContent, resolveBaseUrl, startTokenApiServer } from '@artblocks/abx-token-api';
|
|
21
21
|
import { encodeFunctionData, toHex, zeroAddress } from 'viem';
|
|
22
22
|
import { CHAIN, explorerBase, assertTurboFundsForUpload, backendResolution, collectContentLocators, ensureArweaveIdentityForUpload, factoryAddress, faucetHint, localIndexer, loopbackBaseUrl, noteArweavePlan, noteStorageReadiness, rendererAddress, seriesFactoryAddress, oneOfOneEditionFactoryAddress, editionFactoryAddress, editionCodeFactoryAddress, storageOptions, storageOverrides, storageSignerChoice, } from '../config.js';
|
|
23
23
|
import { parseDepFlag } from '../deps.js';
|
|
24
|
-
import { isDryRun, parseSaltFlag, refuseStrayFlags, warnSignWithoutFor } from '../flags.js';
|
|
24
|
+
import { allowUnreadableOnchain, isDryRun, parseSaltFlag, refuseStrayFlags, warnSignWithoutFor } from '../flags.js';
|
|
25
25
|
import { isEditionContract } from '../kind.js';
|
|
26
26
|
import { jsonSafe, withJson } from '../jsonout.js';
|
|
27
27
|
import { bold, c, dim, ensureFactory, ensureRenderer, ensureSeedSource, ensureSeriesCodeFactory, ensureSeriesFactory, ensureOneOfOneEditionFactory, ensureEditionFactory, ensureEditionCodeFactory, g, info, keepAlive, ok, p, portInUse, printServing, registerAndIndexLocally, reindexAfterDeploy, step, warn, } from '../output.js';
|
|
28
|
-
import { AUTHORSHIP_DEPLOY_FIELDS, ONCHAIN_PROJECT_SOFT_LIMIT, authorshipContractFields, computeContentPlan, envStagingSender, parseCompress, parseTransferValidatorValue, previewImageStaging, sessionStagingSender, stageImageField, stageImageFieldsBatch, } from '../ownerops.js';
|
|
28
|
+
import { AUTHORSHIP_DEPLOY_FIELDS, ONCHAIN_PROJECT_SOFT_LIMIT, ONCHAIN_READ_WARN_BYTES, tokenUriGasEstimate, authorshipContractFields, computeContentPlan, envStagingSender, parseCompress, parseSeedSourceValue, canonicalSeedSource, refuseUnusableSeedSource, parseTransferValidatorValue, guardOnChainSize, previewImageStaging, sessionStagingSender, stageImageField, stageImageFieldsBatch, } from '../ownerops.js';
|
|
29
29
|
import { canonicalLabel } from '../remote.js';
|
|
30
30
|
import { confirmSend, gatedSend, laneFromFlags } from '../riskgate.js';
|
|
31
|
-
import { describeSchema, parseSchemaSpecs } from '../schema.js';
|
|
31
|
+
import { describeSchema, editionSchemaAdvisory, parseSchemaSpecs } from '../schema.js';
|
|
32
32
|
import { parseSeriesTraits } from '../series-traits.js';
|
|
33
33
|
import { decodeOnChainJson } from '../served.js';
|
|
34
34
|
import { openWalletSession, signHotSequence, signTx } from '../signer.js';
|
|
@@ -261,6 +261,7 @@ export function deployCodeCommandLine(flags, salt) {
|
|
|
261
261
|
str('max', flags.max);
|
|
262
262
|
str('schema', flags.schema);
|
|
263
263
|
bool('no-seed');
|
|
264
|
+
str('seed-source', flags['seed-source']);
|
|
264
265
|
bool('onchain-uri');
|
|
265
266
|
str('generator', flags.generator);
|
|
266
267
|
str('renderer', flags.renderer);
|
|
@@ -289,7 +290,7 @@ export function deployCodeCommandLine(flags, salt) {
|
|
|
289
290
|
// Build the token's `image` field as an on-chain keccak256 commitment over the bytes,
|
|
290
291
|
// and (with --image) store those bytes in custody keyed by that hash so the resolver
|
|
291
292
|
// can serve + verify them. Without --image, fall back to the demo's generative-from-
|
|
292
|
-
// address
|
|
293
|
+
// address content (recomputable, nothing to store). Returns on-chain field inputs.
|
|
293
294
|
export const imageKeccakField = (value) => ({
|
|
294
295
|
field: encodeTag(F.image),
|
|
295
296
|
representation: encodeTag(R.keccak256),
|
|
@@ -364,12 +365,12 @@ walletAddr) {
|
|
|
364
365
|
// off-chain mode commits its keccak and regenerates/serves it.
|
|
365
366
|
if (onChain) {
|
|
366
367
|
return {
|
|
367
|
-
tokenFields: [imageInlineField(
|
|
368
|
+
tokenFields: [imageInlineField(generateContent(clone))],
|
|
368
369
|
contentNote: 'content: generative SVG, INLINE on-chain (self-resolving, no custody)',
|
|
369
370
|
};
|
|
370
371
|
}
|
|
371
372
|
return {
|
|
372
|
-
tokenFields: [imageKeccakField(
|
|
373
|
+
tokenFields: [imageKeccakField(contentHash(clone))],
|
|
373
374
|
contentNote: 'content: generative SVG from the contract address (demo default)',
|
|
374
375
|
};
|
|
375
376
|
}
|
|
@@ -509,7 +510,7 @@ export function describeTraits(traits, onchain) {
|
|
|
509
510
|
// ── deploy (+ optionally serve) ──────────────────────────────────────────────
|
|
510
511
|
export async function cmdDeploy(flags, serveAfter) {
|
|
511
512
|
// `--copies <N|open>` routes to the ERC-1155 edition twin (OneOfOneEdition) — the flagship
|
|
512
|
-
// edition product: a priced open/limited edition of ONE
|
|
513
|
+
// edition product: a priced open/limited edition of ONE work. Checked before anything else
|
|
513
514
|
// touches `flags`, so the 721 lane (`cmdDeployBody`) below is reached ONLY when `--copies` is
|
|
514
515
|
// absent, and is otherwise untouched by this feature — byte-identical to before editions shipped.
|
|
515
516
|
if (flags.copies !== undefined) {
|
|
@@ -543,7 +544,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
543
544
|
// lockable) — mirrors the description model. On-chain wins if both ever set the same trait.
|
|
544
545
|
const traits = parseDeployTraits(flags);
|
|
545
546
|
console.log(serveAfter
|
|
546
|
-
? bold(`\n ABX · your first token\n ${dim("we'll put
|
|
547
|
+
? bold(`\n ABX · your first token\n ${dim("we'll put your work on a blockchain — then delete our copy and get all of it back")}`)
|
|
547
548
|
: bold(`\n ABX Self-Host Toolkit — deploy\n ${dim('a project, served from chain alone')}`));
|
|
548
549
|
// Signing lane. `demo` used to hard-force the hot lane, which SILENTLY dropped `--sign`: with no
|
|
549
550
|
// env key it died confusingly, and WITH one it signed from that key while the operator had asked
|
|
@@ -639,17 +640,17 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
639
640
|
// otherwise leave it EMPTY rather than baking a misleading localhost into the contract.
|
|
640
641
|
const hasPublicUrl = !!(flags['public-base-url'] || process.env.ABX_PUBLIC_BASE_URL);
|
|
641
642
|
const onchainImage = !!flags['onchain-image'];
|
|
642
|
-
// `abx demo` defaults to FULLY ON-CHAIN. Its default
|
|
643
|
+
// `abx demo` defaults to FULLY ON-CHAIN. Its default content is a generative SVG, which the renderer
|
|
643
644
|
// can inline — so the token self-resolves and, crucially, NO localhost gets baked into the
|
|
644
645
|
// contract as the tokenURI base. The old default shipped a first-ever token that resolved for
|
|
645
646
|
// nobody but its author (broken on every marketplace, dead the moment `abx serve` stops) and
|
|
646
|
-
// taught that as the normal shape of an NFT. It also undercut the demo's own claim: with the
|
|
647
|
+
// taught that as the normal shape of an NFT. It also undercut the demo's own claim: with the content
|
|
647
648
|
// on-chain, "rebuilt from the chain alone" now covers the IMAGE, not just the metadata.
|
|
648
649
|
//
|
|
649
650
|
// Opting back into off-chain custody is anything that says "I have somewhere to host": a real base
|
|
650
651
|
// URL, an explicit --backend, or a RASTER --image. An SVG --image still goes on-chain — inlining is
|
|
651
652
|
// exactly what the renderer supports (v1 is SVG-only), so there's no reason to send someone's own
|
|
652
|
-
// vector
|
|
653
|
+
// vector content down the localhost path. Raster stays off-chain because forcing it on-chain would
|
|
653
654
|
// silently inline a placeholder instead of their image.
|
|
654
655
|
const demoImageInlineable = !flags.image || contentTypeFromPath(flags.image) === 'image/svg+xml';
|
|
655
656
|
const demoDefaultsOnChain = serveAfter && demoImageInlineable && !flags.backend && !hasPublicUrl;
|
|
@@ -680,12 +681,12 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
680
681
|
// and it's invisible unless someone says it out loud. Two honest versions, because the answer is
|
|
681
682
|
// genuinely different per lane. Demo only — `deploy` prints the same facts per-field.
|
|
682
683
|
if (serveAfter && onChainUri) {
|
|
683
|
-
console.log(` Your
|
|
684
|
-
console.log(` ${g('✦')} the chain will hold ${dim('your
|
|
684
|
+
console.log(` Your work goes ${bold('INTO')} the contract. Not a link to it — the image itself.`);
|
|
685
|
+
console.log(` ${g('✦')} the chain will hold ${dim('your work · your name on it · your royalty · you as owner')}`);
|
|
685
686
|
console.log(` ${g('✦')} you will need ${dim('nothing else. no server, no IPFS pin, no monthly bill to forget')}`);
|
|
686
687
|
}
|
|
687
688
|
else if (serveAfter) {
|
|
688
|
-
console.log(` ${g('✦')} the chain will hold ${dim('your name · your royalty · you as owner · a fingerprint of the
|
|
689
|
+
console.log(` ${g('✦')} the chain will hold ${dim('your name · your royalty · you as owner · a fingerprint of the work')}`);
|
|
689
690
|
console.log(` ${g('✦')} this computer holds ${dim('the image bytes themselves')}`);
|
|
690
691
|
info(dim('the chain proves the bytes are unaltered; it does not store them (that fingerprint is a keccak256 hash)'));
|
|
691
692
|
}
|
|
@@ -702,7 +703,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
702
703
|
const devAllow = process.env.ABX_DEV_ALLOW_LOCALHOST_URI === '1';
|
|
703
704
|
const msg = `Off-chain metadata needs a PUBLIC resolver URL baked on-chain — ${hasPublicUrl ? baseUrl : 'localhost'} resolves for no one ` +
|
|
704
705
|
`(not marketplaces, not wallets, not even your own browser unless \`abx serve\` is running). Pick a real path:\n` +
|
|
705
|
-
` • Fully on-chain, no host: --onchain-image --compress fastlz (best for small static
|
|
706
|
+
` • Fully on-chain, no host: --onchain-image --compress fastlz (best for small static content)\n` +
|
|
706
707
|
` • Hosted resolver: --public-base-url https://your.domain (or set ABX_PUBLIC_BASE_URL)`;
|
|
707
708
|
if (!devAllow) {
|
|
708
709
|
if (dryRun)
|
|
@@ -769,12 +770,12 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
769
770
|
}
|
|
770
771
|
step('Stage on-chain image');
|
|
771
772
|
if (dryRun) {
|
|
772
|
-
info(previewImageStaging(flags.image, parseCompress(flags.compress)));
|
|
773
|
+
info(previewImageStaging(flags.image, parseCompress(flags.compress), allowUnreadableOnchain(flags)));
|
|
773
774
|
}
|
|
774
775
|
else if (lane === 'send') {
|
|
775
776
|
// hot lane: the env key stages now (deployer-independent — the manifest doesn't depend
|
|
776
777
|
// on the clone address), then the deploy below bakes in the reader field.
|
|
777
|
-
const { field, note } = await stageImageField(flags.image, parseCompress(flags.compress), envStagingSender());
|
|
778
|
+
const { field, note } = await stageImageField(flags.image, parseCompress(flags.compress), envStagingSender(), allowUnreadableOnchain(flags));
|
|
778
779
|
bakedImage = field;
|
|
779
780
|
info(note);
|
|
780
781
|
}
|
|
@@ -851,7 +852,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
851
852
|
royaltyBps,
|
|
852
853
|
transferValidator,
|
|
853
854
|
tokenFields,
|
|
854
|
-
// authorship + rights (
|
|
855
|
+
// authorship + rights (creator / license / …) are collection-scope on-chain fields.
|
|
855
856
|
contractFields: authorshipContractFields(flags),
|
|
856
857
|
};
|
|
857
858
|
return { clone, params, salt, contentNote };
|
|
@@ -917,7 +918,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
917
918
|
return;
|
|
918
919
|
}
|
|
919
920
|
// opt-in --confirm: a final y/N before the real send (no-op without --confirm; never blocks scripts)
|
|
920
|
-
await confirmSend(`About to deploy 1/1 "${name}" (${symbol})${flags.image ? ' with your image' : ' (generative demo
|
|
921
|
+
await confirmSend(`About to deploy 1/1 "${name}" (${symbol})${flags.image ? ' with your image' : ' (generative demo content)'}; mint: ${noMint ? 'deferred' : 'token #0 at deploy'}; approvals: ${approvals} wallet approval(s); owner+royalty: your wallet @ ${royaltyBps / 100}%.` +
|
|
921
922
|
(transferValidator !== zeroAddress ? ` ERC-721C: enrolled at deploy, permanently (validator ${transferValidator}).` : ''), flags);
|
|
922
923
|
if (lane === 'send') {
|
|
923
924
|
// hot lane: the env key is the deployer, known up front.
|
|
@@ -960,7 +961,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
960
961
|
try {
|
|
961
962
|
const signer = await session.connect();
|
|
962
963
|
// Stage on-chain through the session (sets bakedImage, which buildForDeployer reads below).
|
|
963
|
-
const { field, note } = await stageImageField(flags.image, parseCompress(flags.compress), sessionStagingSender(session));
|
|
964
|
+
const { field, note } = await stageImageField(flags.image, parseCompress(flags.compress), sessionStagingSender(session), allowUnreadableOnchain(flags));
|
|
964
965
|
bakedImage = field;
|
|
965
966
|
info(note);
|
|
966
967
|
const { clone: predicted, params, salt, contentNote } = await buildForDeployer(signer);
|
|
@@ -1148,7 +1149,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
1148
1149
|
keepAlive();
|
|
1149
1150
|
}
|
|
1150
1151
|
// ── deploy --copies: OneOfOneEdition (the flagship ERC-1155 product) ─────────────────────────────
|
|
1151
|
-
// Copies of ONE
|
|
1152
|
+
// Copies of ONE work — a priced open/limited edition. The edition twin of `cmdDeployBody`,
|
|
1152
1153
|
// reusing every standard-agnostic helper it does (content custody, traits, authorship fields, the
|
|
1153
1154
|
// `--721c`/`--1155c`-shared validator resolution, salt prediction, signing lanes) but written as its
|
|
1154
1155
|
// own function rather than threaded into `cmdDeployBody` with `if (copies)` branches throughout —
|
|
@@ -1168,10 +1169,10 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1168
1169
|
// `--type` is the 721 lane's OWN dimension seam (cmdDeployBody redirects `--type series` to
|
|
1169
1170
|
// `deploy-series`) — it never even reaches this function for that redirect, since --copies routes
|
|
1170
1171
|
// here before --type is ever inspected. Refuse the confusing combination explicitly rather than
|
|
1171
|
-
// silently ignore it (a creator combining them almost certainly means the multi-
|
|
1172
|
+
// silently ignore it (a creator combining them almost certainly means the multi-work edition —
|
|
1172
1173
|
// `deploy-series --copies`, not this 1/1-edition lane).
|
|
1173
1174
|
if (flags.type !== undefined && resolveDimension(flags.type) === 'series') {
|
|
1174
|
-
throw new Error("--type series + --copies: this is the 1/1-edition lane (one
|
|
1175
|
+
throw new Error("--type series + --copies: this is the 1/1-edition lane (one work, copies of it) — for N works each with copies, " +
|
|
1175
1176
|
'use `abx deploy-series --copies <n|open> --dir <folder>` instead. Drop --type here.');
|
|
1176
1177
|
}
|
|
1177
1178
|
const editionSize = parseCopies(flags.copies);
|
|
@@ -1182,8 +1183,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1182
1183
|
const baseUrl = flags['public-base-url'] ?? resolveBaseUrl(port);
|
|
1183
1184
|
const traits = parseDeployTraits(flags);
|
|
1184
1185
|
console.log(bold(`\n ABX Self-Host Toolkit — deploy edition\n ${dim(editionSize === 0n
|
|
1185
|
-
? 'OneOfOneEdition (ERC-1155) — an OPEN edition of one
|
|
1186
|
-
: `OneOfOneEdition (ERC-1155) — ${editionSize} cop${editionSize === 1n ? 'y' : 'ies'} of one
|
|
1186
|
+
? 'OneOfOneEdition (ERC-1155) — an OPEN edition of one work, served from chain alone'
|
|
1187
|
+
: `OneOfOneEdition (ERC-1155) — ${editionSize} cop${editionSize === 1n ? 'y' : 'ies'} of one work, served from chain alone`)}`));
|
|
1187
1188
|
if (editionSize === 1n)
|
|
1188
1189
|
info(copiesOneNote('deploy'));
|
|
1189
1190
|
const lane = laneFromFlags(flags);
|
|
@@ -1257,7 +1258,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1257
1258
|
if (!hasPublicUrl || loopback) {
|
|
1258
1259
|
const devAllow = process.env.ABX_DEV_ALLOW_LOCALHOST_URI === '1';
|
|
1259
1260
|
const msg = `Off-chain metadata needs a PUBLIC resolver URL baked on-chain — ${hasPublicUrl ? baseUrl : 'localhost'} resolves for no one. ` +
|
|
1260
|
-
`Pick a real path:\n • Fully on-chain (SVG
|
|
1261
|
+
`Pick a real path:\n • Fully on-chain (SVG content): --onchain-uri\n • Fully on-chain (any media): --onchain-image --compress fastlz\n • Hosted resolver: --public-base-url https://your.domain`;
|
|
1261
1262
|
if (!devAllow) {
|
|
1262
1263
|
if (dryRun)
|
|
1263
1264
|
warn(`would REFUSE to deploy — ${msg}`);
|
|
@@ -1279,7 +1280,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1279
1280
|
// connect, so the connecting wallet pays for and owns every chunk write. `stageNow` is the hot-lane
|
|
1280
1281
|
// (and dry-run) path; the session branch below calls the same helper with a session-backed sender.
|
|
1281
1282
|
const stageOnChainImage = async (sender) => {
|
|
1282
|
-
const { field, note } = await stageImageField(flags.image, parseCompress(flags.compress), sender);
|
|
1283
|
+
const { field, note } = await stageImageField(flags.image, parseCompress(flags.compress), sender, allowUnreadableOnchain(flags));
|
|
1283
1284
|
bakedImage = field;
|
|
1284
1285
|
info(note);
|
|
1285
1286
|
};
|
|
@@ -1288,7 +1289,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1288
1289
|
throw new Error('--onchain-image needs --image <path> (the bytes to put on-chain)');
|
|
1289
1290
|
step('Stage on-chain image');
|
|
1290
1291
|
if (dryRun)
|
|
1291
|
-
info(previewImageStaging(flags.image, parseCompress(flags.compress)));
|
|
1292
|
+
info(previewImageStaging(flags.image, parseCompress(flags.compress), allowUnreadableOnchain(flags)));
|
|
1292
1293
|
else if (lane === 'send')
|
|
1293
1294
|
await stageOnChainImage(envStagingSender());
|
|
1294
1295
|
// lane === 'sign' → staged inside the wallet session further down.
|
|
@@ -1399,7 +1400,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1399
1400
|
emit(jsonSafe({ command: 'deploy', kind: '1of1-edition', copies: editionSize.toString(), dryRun: true, sent: false, address: explicitSalt ? predicted : null, chain: CHAIN, chainId: resolveChain(CHAIN).id, factory, salt, saltPinned: !!explicitSalt, name, symbol }));
|
|
1400
1401
|
return;
|
|
1401
1402
|
}
|
|
1402
|
-
await confirmSend(`About to deploy an edition of "${name}" (${symbol}) — ${editionSize === 0n ? 'open' : editionSize.toString()} copies${flags.image ? ' with your image' : ' (generative demo
|
|
1403
|
+
await confirmSend(`About to deploy an edition of "${name}" (${symbol}) — ${editionSize === 0n ? 'open' : editionSize.toString()} copies${flags.image ? ' with your image' : ' (generative demo content)'}; mint: ${mintAmount > 0n ? `${mintAmount} at deploy` : 'deferred'}; approvals: ${approvals} wallet approval(s); owner+royalty: your wallet @ ${royaltyBps / 100}%.` +
|
|
1403
1404
|
(transferValidator !== zeroAddress ? ` ERC-1155C: enrolled at deploy, permanently (validator ${transferValidator}).` : ''), flags);
|
|
1404
1405
|
if (lane === 'send') {
|
|
1405
1406
|
const { wallet, account } = makeWalletClient({ chainKey: CHAIN });
|
|
@@ -1509,7 +1510,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1509
1510
|
// token (`0..N-1`, natural-sorted) — a token's metadata is its token id, and tokens mint
|
|
1510
1511
|
// strictly in order. Content is hosted exactly like a 1/1, per token — three placements:
|
|
1511
1512
|
// • off-chain custody (default): keccak-committed, bytes served by the resolver.
|
|
1512
|
-
// • `--onchain-uri`: inline each token's SVG on-chain (tiny
|
|
1513
|
+
// • `--onchain-uri`: inline each token's SVG on-chain (tiny content; SVG only).
|
|
1513
1514
|
// • `--onchain-image [--compress fastlz]`: stage each token's bytes (raster or SVG) into the
|
|
1514
1515
|
// shared chunk store and bake a per-token `reader` field — multi-chunk on-chain content,
|
|
1515
1516
|
// self-resolving via the renderer. Implies on-chain URI resolution.
|
|
@@ -1587,13 +1588,16 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1587
1588
|
throw new Error('Staging on-chain images (--onchain-image) needs interactive signing — each chunk tx feeds ' +
|
|
1588
1589
|
"the next, so it can't run on the cold lane (--unsigned). Use the hot lane (a funded key) or --sign (browser wallet).");
|
|
1589
1590
|
}
|
|
1590
|
-
// Whole-collection cost guard:
|
|
1591
|
-
//
|
|
1591
|
+
// Whole-collection WRITE-cost guard: a set of many small files can still sum to an expensive
|
|
1592
|
+
// on-chain deploy, so flag the total once, up front. This is a cost signal only — the READ gate is
|
|
1593
|
+
// per token (`guardOnChainSize`, fired per file during staging/preview), because each token's
|
|
1594
|
+
// `tokenURI` assembles only its own content, so a 300-file collection of 5KB pieces reads fine.
|
|
1592
1595
|
if (onchainImage) {
|
|
1593
1596
|
const totalBytes = slots.reduce((n, s) => n + statSync(joinPath(dirPath, s)).size, 0);
|
|
1594
1597
|
if (totalBytes > ONCHAIN_PROJECT_SOFT_LIMIT) {
|
|
1595
|
-
warn(`on-chain collection total is ${Math.round(totalBytes / 1024)}KB across ${slots.length} file(s) — on-chain costs ~200 gas/byte, so this is expensive, not cheaper. ` +
|
|
1596
|
-
`Past ~${Math.round(ONCHAIN_PROJECT_SOFT_LIMIT / 1024)}KB total, prefer off-chain: --backend arweave (pay-once, permanent) or a hosted resolver + IPFS
|
|
1598
|
+
warn(`on-chain collection total is ${Math.round(totalBytes / 1024)}KB across ${slots.length} file(s) — on-chain costs ~200 gas/byte to WRITE, so this is expensive, not cheaper. ` +
|
|
1599
|
+
`Past ~${Math.round(ONCHAIN_PROJECT_SOFT_LIMIT / 1024)}KB total, prefer off-chain: --backend arweave (pay-once, permanent) or a hosted resolver + IPFS. ` +
|
|
1600
|
+
`(Per-token readability is judged per file, not on this total.)`);
|
|
1597
1601
|
}
|
|
1598
1602
|
}
|
|
1599
1603
|
const minter = flags.minter ?? zeroAddress;
|
|
@@ -1636,7 +1640,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1636
1640
|
if (!hasPublicUrl || loopback) {
|
|
1637
1641
|
const devAllow = process.env.ABX_DEV_ALLOW_LOCALHOST_URI === '1';
|
|
1638
1642
|
const msg = `Off-chain metadata needs a PUBLIC resolver URL baked on-chain — ${hasPublicUrl ? baseUrl : 'localhost'} resolves for no one. ` +
|
|
1639
|
-
`Pick a real path:\n • Fully on-chain (SVG
|
|
1643
|
+
`Pick a real path:\n • Fully on-chain (SVG content): --onchain-uri\n • Fully on-chain (any media): --onchain-image --compress fastlz\n • Hosted resolver: --public-base-url https://your.domain`;
|
|
1640
1644
|
if (!devAllow) {
|
|
1641
1645
|
if (dryRun)
|
|
1642
1646
|
warn(`would REFUSE to deploy — ${msg}`);
|
|
@@ -1719,7 +1723,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1719
1723
|
const buildImageFields = async (stage) => {
|
|
1720
1724
|
// --onchain-image: stage every token's bytes into ONE shared chunk store, each a `reader` field.
|
|
1721
1725
|
if (onchainImage) {
|
|
1722
|
-
const { fields } = await stageImageFieldsBatch(tokenPaths.map((s) => s.path), compress, stage, flags['chunk-store']);
|
|
1726
|
+
const { fields } = await stageImageFieldsBatch(tokenPaths.map((s) => s.path), compress, stage, flags['chunk-store'], allowUnreadableOnchain(flags));
|
|
1723
1727
|
tokenFields = tokenPaths.map(({ tokenId }, i) => tokenFieldOf(tokenId, fields[i]));
|
|
1724
1728
|
return;
|
|
1725
1729
|
}
|
|
@@ -1835,7 +1839,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1835
1839
|
mintTo: mintCount > 0 ? deployer : zeroAddress,
|
|
1836
1840
|
mintCount,
|
|
1837
1841
|
tokenFields,
|
|
1838
|
-
// image contract fields (the O(1) url-template) + authorship/rights (
|
|
1842
|
+
// image contract fields (the O(1) url-template) + authorship/rights (creator / license / …).
|
|
1839
1843
|
contractFields: [...contractFields, ...authorshipContractFields(flags)],
|
|
1840
1844
|
};
|
|
1841
1845
|
return { clone, params, salt };
|
|
@@ -1887,7 +1891,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1887
1891
|
}
|
|
1888
1892
|
if (onchainImage) {
|
|
1889
1893
|
for (const { tokenId, name: fname, path } of tokenPaths) {
|
|
1890
|
-
info(`token ${tokenId} ← ${fname}: ${previewImageStaging(path, compress)}`);
|
|
1894
|
+
info(`token ${tokenId} ← ${fname}: ${previewImageStaging(path, compress, allowUnreadableOnchain(flags))}`);
|
|
1891
1895
|
}
|
|
1892
1896
|
}
|
|
1893
1897
|
info(`approvals ${approvals} wallet approval(s)`); // B2 — TX signatures only
|
|
@@ -2062,7 +2066,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
2062
2066
|
console.log(` ${bold(`abx refresh ${clone}`)} ${dim('# nudge marketplaces once metadata is live')}\n`);
|
|
2063
2067
|
}
|
|
2064
2068
|
// ── deploy-series --copies: EditionImage ──────────────────────────────────────────────────────────
|
|
2065
|
-
// N distinct
|
|
2069
|
+
// N distinct works (one per file, like a Series) × copies each. The edition twin of
|
|
2066
2070
|
// `cmdDeploySeriesBody`, kept as its OWN function for the same reason `cmdDeployOneOfOneEditionBody`
|
|
2067
2071
|
// is (byte-identical 721 lane — see that function's module note).
|
|
2068
2072
|
//
|
|
@@ -2176,7 +2180,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2176
2180
|
if (!hasPublicUrl || loopback) {
|
|
2177
2181
|
const devAllow = process.env.ABX_DEV_ALLOW_LOCALHOST_URI === '1';
|
|
2178
2182
|
const msg = `Off-chain metadata needs a PUBLIC resolver URL baked on-chain — ${hasPublicUrl ? baseUrl : 'localhost'} resolves for no one. ` +
|
|
2179
|
-
`Pick a real path:\n • Fully on-chain (SVG
|
|
2183
|
+
`Pick a real path:\n • Fully on-chain (SVG content): --onchain-uri\n • Hosted resolver: --public-base-url https://your.domain`;
|
|
2180
2184
|
if (!devAllow) {
|
|
2181
2185
|
if (dryRun)
|
|
2182
2186
|
warn(`would REFUSE to deploy — ${msg}`);
|
|
@@ -2194,8 +2198,8 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2194
2198
|
const backendId = overrides.backend ?? process.env.ABX_STORAGE_BACKEND ?? 'fs';
|
|
2195
2199
|
// Pattern 2 — image off-chain on a durable backend, JSON rendered on-chain, NOTHING to run. Ported
|
|
2196
2200
|
// from the 721 Series twin, which has had it all along; `EditionImage` shipped without it, so a
|
|
2197
|
-
// multi-
|
|
2198
|
-
//
|
|
2201
|
+
// multi-work RASTER edition had no no-server option at all and `--onchain-uri` just refused the
|
|
2202
|
+
// content. That pushed a cold agent into deploying one contract PER work to get permanence (B28).
|
|
2199
2203
|
// Nothing on-chain needed changing: `EditionImageInitParams` already takes `contractFields`, and the
|
|
2200
2204
|
// renderer's `url-template` substitutes `{id}` from whatever id it is called with — `uri(id)` on a
|
|
2201
2205
|
// 1155 reaches it exactly as `tokenURI(id)` does on a 721.
|
|
@@ -2203,6 +2207,14 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2203
2207
|
const offchainImageOnchainJson = onChainUri && !onchainImage && directUrlBackend;
|
|
2204
2208
|
const backend = (offchainImageOnchainJson || (!onChainUri && !onchainImage)) && !dryRun ? resolveBackend(opts) : undefined;
|
|
2205
2209
|
const tokenPaths = slots.map((s, i) => ({ tokenId: i, name: s, path: joinPath(dirPath, s) }));
|
|
2210
|
+
// The render-gas gate on the dry run. Unlike the 721 Series twin this lane has no per-id
|
|
2211
|
+
// `previewImageStaging` line to hang it off, so without this an oversized edition would only be
|
|
2212
|
+
// refused once `buildFields` reached the first real staging call — i.e. after the chunk store was
|
|
2213
|
+
// already deployed. Same per-id predicate either way (`guardOnChainSize` runs again during staging).
|
|
2214
|
+
if (onchainImage && dryRun) {
|
|
2215
|
+
for (const { name, path } of tokenPaths)
|
|
2216
|
+
guardOnChainSize(statSync(path).size, name, allowUnreadableOnchain(flags));
|
|
2217
|
+
}
|
|
2206
2218
|
// Say WHICH backend holds the bytes, exactly as the 1/1 lane does. Both Series lanes resolved a
|
|
2207
2219
|
// backend and never named it, so a creator asking for "images on IPFS" had no way to confirm from
|
|
2208
2220
|
// the preview that their collection would actually pin there — the flag was accepted in silence.
|
|
@@ -2230,14 +2242,14 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2230
2242
|
const tokenFields = [];
|
|
2231
2243
|
// Collection-scope fields the image lane may add (the O(1) `url-template`). Kept separate from the
|
|
2232
2244
|
// authorship fields and CONCATENATED at the params site — never assigned over them, or an
|
|
2233
|
-
// `--
|
|
2245
|
+
// `--creator`/`--license` value would silently vanish whenever pattern 2 is in play.
|
|
2234
2246
|
let imageContractFields = [];
|
|
2235
2247
|
const buildFields = async (stage) => {
|
|
2236
2248
|
// Pattern 1: the bytes themselves on-chain, staged into the shared chunk store per id, each id's
|
|
2237
2249
|
// field pointing at its manifest. Ported from the 721 Series twin — the edition lane refused this
|
|
2238
2250
|
// outright before (B28), though nothing on-chain prevented it.
|
|
2239
2251
|
if (onchainImage) {
|
|
2240
|
-
const { fields } = await stageImageFieldsBatch(tokenPaths.map((s) => s.path), compress, stage, flags['chunk-store']);
|
|
2252
|
+
const { fields } = await stageImageFieldsBatch(tokenPaths.map((s) => s.path), compress, stage, flags['chunk-store'], allowUnreadableOnchain(flags));
|
|
2241
2253
|
tokenPaths.forEach(({ tokenId }, i) => tokenFields.push(tokenFieldOf(tokenId, fields[i])));
|
|
2242
2254
|
if (seriesTraitsOnchain)
|
|
2243
2255
|
for (const [tokenId, attrs] of seriesTraits)
|
|
@@ -2296,13 +2308,13 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2296
2308
|
const text = Buffer.from(bytes).toString('utf8');
|
|
2297
2309
|
if (!looksLikeSvg(text)) {
|
|
2298
2310
|
// Name the real alternatives, the way the 721 twin's version of this refusal does — a bare
|
|
2299
|
-
// "drop --onchain-uri" once sent a cold agent fanning a 3-
|
|
2311
|
+
// "drop --onchain-uri" once sent a cold agent fanning a 3-work edition out into THREE
|
|
2300
2312
|
// separate contracts to find permanence. The `--backend` route is now the first suggestion
|
|
2301
2313
|
// because it IS available on this lane (see `offchainImageOnchainJson` above).
|
|
2302
|
-
throw new Error(`--onchain-uri inlines SVG only; id ${tokenId} "${fname}" is ${contentType}. For raster
|
|
2314
|
+
throw new Error(`--onchain-uri inlines SVG only; id ${tokenId} "${fname}" is ${contentType}. For raster content in ONE edition contract:\n` +
|
|
2303
2315
|
` • No server, images off-chain: add --backend arweave (or ipfs/cloud) — the image URL is baked into the on-chain JSON\n` +
|
|
2304
2316
|
` • Host the metadata yourself: drop --onchain-uri and pass --public-base-url https://your.domain\n` +
|
|
2305
|
-
` • Keep it SVG: supply SVG
|
|
2317
|
+
` • Keep it SVG: supply SVG content and --onchain-uri inlines every id on-chain\n` +
|
|
2306
2318
|
` • Bytes fully on-chain: --onchain-image --compress fastlz (hot or --sign lane; --unsigned is refused)`);
|
|
2307
2319
|
}
|
|
2308
2320
|
tokenFields.push(tokenFieldOf(tokenId, imageInlineField(text)));
|
|
@@ -2319,7 +2331,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2319
2331
|
for (const [tokenId, attrs] of seriesTraits)
|
|
2320
2332
|
tokenFields.push(tokenFieldOf(tokenId, attributesInlineField(attrs)));
|
|
2321
2333
|
// AFTER the per-id content actually validated — stating "resolves ON-CHAIN, no server" and then
|
|
2322
|
-
// refusing the very
|
|
2334
|
+
// refusing the very content that was passed reads as the tool contradicting itself.
|
|
2323
2335
|
if (onChainUri) {
|
|
2324
2336
|
info(`uri()/contractURI() resolve ON-CHAIN via the renderer — no resolver, no server, no localhost.`);
|
|
2325
2337
|
if (!hasPublicUrl)
|
|
@@ -2360,7 +2372,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2360
2372
|
mintAmount,
|
|
2361
2373
|
tokenFields,
|
|
2362
2374
|
// Concatenate — the image lane's collection-scope url-template rides ALONGSIDE the authorship
|
|
2363
|
-
// fields. Assigning either over the other would silently drop the creator's --
|
|
2375
|
+
// fields. Assigning either over the other would silently drop the creator's --creator/--license.
|
|
2364
2376
|
contractFields: [...authorshipContractFields(flags), ...imageContractFields],
|
|
2365
2377
|
};
|
|
2366
2378
|
return { clone, params, salt };
|
|
@@ -2519,8 +2531,40 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2519
2531
|
* (`putDirectory`; ipfs/arweave), its root as the `code` field
|
|
2520
2532
|
*
|
|
2521
2533
|
* Schemas: --schema key:Type:Auth[,key:Type:Auth…] (e.g. palette:HexColor:TokenOwner).
|
|
2522
|
-
* Seeds: canonical
|
|
2534
|
+
* Seeds: the canonical pseudorandom seed source by default; --no-seed opts out.
|
|
2535
|
+
*/
|
|
2536
|
+
/**
|
|
2537
|
+
* The one honest sentence about the canonical seed, printed beside the `seed source` line in the
|
|
2538
|
+
* deploy plan — the moment a creator is choosing it, not an appendix. `AbxSeedSource` is
|
|
2539
|
+
* pseudorandom from on-chain values: replayable after the mint (what makes generative output
|
|
2540
|
+
* verifiable) and computable *during* it, so a buyer can decline an outcome for the price of gas.
|
|
2541
|
+
* That rules out anything lottery-like; the escape hatch is a per-project address, so we name it —
|
|
2542
|
+
* and now name it as the FLAG it is (`--seed-source 0x…`), not as an env var, because the flag is
|
|
2543
|
+
* probed and the whole point of naming an escape hatch is that a creator can actually reach it.
|
|
2544
|
+
*/
|
|
2545
|
+
const seedHonestyNote = ' pseudorandom — replayable after the mint, computable DURING it (a buyer can decline an outcome).\n' +
|
|
2546
|
+
' Right for diversifying output; NOT strong enough for a raffle or prize draw — for that, pass\n' +
|
|
2547
|
+
' --seed-source 0x… pointing at your own IAbxSeedSource over commit-reveal or a VRF oracle\n' +
|
|
2548
|
+
' (a per-project swap, not a fork). Change it later with `abx set-seed-source`.';
|
|
2549
|
+
/**
|
|
2550
|
+
* The `seed source` line of a deploy plan, for both code lanes. Three states, three different
|
|
2551
|
+
* sentences — the honesty note describes `AbxSeedSource`'s OWN properties, so printing it beside a
|
|
2552
|
+
* stranger's contract would be us vouching for randomness we have never seen.
|
|
2523
2553
|
*/
|
|
2554
|
+
function printSeedSourcePlan(seedSource, chainId) {
|
|
2555
|
+
if (seedSource === zeroAddress) {
|
|
2556
|
+
info('seed source: none (--no-seed / not configured)');
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
if (seedSource.toLowerCase() === canonicalSeedSource(chainId).toLowerCase()) {
|
|
2560
|
+
info(`seed source: ${seedSource} ${dim('(canonical AbxSeedSource)')}`);
|
|
2561
|
+
info(seedHonestyNote);
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
info(`seed source: ${seedSource} ${dim('— CUSTOM (yours, not the canonical AbxSeedSource)')}`);
|
|
2565
|
+
info(' probed: it answers seed(uint256,address) with 32 bytes. Its randomness properties are YOURS to\n' +
|
|
2566
|
+
' state to buyers — ABX makes no claim about them, and the honesty note above does not apply.');
|
|
2567
|
+
}
|
|
2524
2568
|
// ── the demo walkthrough: teaching sections, demo-only ────────────────────────
|
|
2525
2569
|
//
|
|
2526
2570
|
// `abx demo` is a TEACHING command, not a shortcut — the docs point a first-time reader here to
|
|
@@ -2608,7 +2652,7 @@ export async function walkthroughRebuild(indexer, address, before) {
|
|
|
2608
2652
|
* Which is genuinely a different act per lane, so it reads from the real source in each case rather
|
|
2609
2653
|
* than always going through the local server:
|
|
2610
2654
|
* • fully on-chain → call `tokenURI(0)` on the contract. That IS what a marketplace does, and on
|
|
2611
|
-
* this lane the whole answer (
|
|
2655
|
+
* this lane the whole answer (content included) comes back from the chain with nothing else running.
|
|
2612
2656
|
* • off-chain custody → fetch the resolver, because that's what the baked URI points at.
|
|
2613
2657
|
* Reading the on-chain lane over HTTP would have quietly implied the local server was load-bearing
|
|
2614
2658
|
* when it isn't — the opposite of the lesson.
|
|
@@ -2635,9 +2679,9 @@ export async function walkthroughReadBack(state, baseUrl, onChainUri) {
|
|
|
2635
2679
|
const img = typeof parsed.image === 'string' ? parsed.image : '';
|
|
2636
2680
|
console.log(` ${g('✓')} came back with: ${bold(String(parsed.name ?? '(no name)'))}`);
|
|
2637
2681
|
console.log(` ${dim(`image: ${img.slice(0, 48)}${img.length > 48 ? '…' : ''}`)}`);
|
|
2638
|
-
// The punchline of the whole lane: a data: URI means the
|
|
2682
|
+
// The punchline of the whole lane: a data: URI means the work travelled IN the answer.
|
|
2639
2683
|
if (img.startsWith('data:'))
|
|
2640
|
-
info(`${g('the
|
|
2684
|
+
info(`${g('the work itself came back in that answer')} ${dim('— no link to follow, nothing to go missing')}`);
|
|
2641
2685
|
}
|
|
2642
2686
|
else {
|
|
2643
2687
|
console.log(` ${dim(uri.slice(0, 160))}${uri.length > 160 ? dim('…') : ''}`);
|
|
@@ -2674,12 +2718,12 @@ export async function walkthroughReadBack(state, baseUrl, onChainUri) {
|
|
|
2674
2718
|
export const DEPLOY_CODE_FLAGS = new Set([
|
|
2675
2719
|
// content + collection identity
|
|
2676
2720
|
'script', 'code-dir', 'name', 'symbol', 'description', 'external-url', 'description-onchain',
|
|
2677
|
-
...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag), //
|
|
2721
|
+
...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag), // creator · display-notes · creator-links · license
|
|
2678
2722
|
'image-base', 'attributes-renderer', 'image-renderer',
|
|
2679
2723
|
// supply + mint + economics
|
|
2680
2724
|
'max', 'mint-count', 'mint-all', 'no-mint', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', '721c',
|
|
2681
2725
|
// params + dependencies
|
|
2682
|
-
'schema', 'no-seed', 'no-delegation', 'dep', 'dep-registry',
|
|
2726
|
+
'schema', 'no-seed', 'seed-source', 'no-delegation', 'dep', 'dep-registry',
|
|
2683
2727
|
// serving lane
|
|
2684
2728
|
'public-base-url', 'onchain-uri', 'generator', 'renderer', 'port',
|
|
2685
2729
|
// storage (directory mode) — mirrors storageOverrides()
|
|
@@ -2701,7 +2745,7 @@ export const SHARED_DEPLOY_FLAGS = [
|
|
|
2701
2745
|
// A 1/1 `abx deploy` accepts these (see `abx help deploy`). Anything else warns (typo'd/unsupported).
|
|
2702
2746
|
export const DEPLOY_FLAGS = new Set([
|
|
2703
2747
|
'image', 'name', 'symbol', 'type', 'description', 'description-onchain', 'external-url', 'traits', 'traits-onchain', 'attributes',
|
|
2704
|
-
'onchain-uri', 'onchain-image', 'compress', 'royalty-bps', 'no-mint',
|
|
2748
|
+
'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'royalty-bps', 'no-mint',
|
|
2705
2749
|
...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
|
|
2706
2750
|
...SHARED_DEPLOY_FLAGS,
|
|
2707
2751
|
]);
|
|
@@ -2709,7 +2753,7 @@ export const DEPLOY_FLAGS = new Set([
|
|
|
2709
2753
|
export const DEPLOY_SERIES_FLAGS = new Set([
|
|
2710
2754
|
'dir', 'count', 'name', 'symbol', 'description', 'external-url', 'royalty-bps', 'attributes', 'traits-onchain',
|
|
2711
2755
|
'no-mint', 'mint-all', 'mint-count', 'unpaused', 'minter', 'primary-payee',
|
|
2712
|
-
'onchain-uri', 'onchain-image', 'compress', 'chunk-store',
|
|
2756
|
+
'onchain-uri', 'onchain-image', 'compress', 'allow-unreadable-onchain', 'chunk-store',
|
|
2713
2757
|
...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
|
|
2714
2758
|
...SHARED_DEPLOY_FLAGS,
|
|
2715
2759
|
]);
|
|
@@ -2743,7 +2787,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
2743
2787
|
const usage = 'abx deploy-code (--script <file> | --code-dir <dir>) --name "Title" --symbol SYM ' +
|
|
2744
2788
|
'(--public-base-url https://your.resolver.domain | --onchain-uri) ' +
|
|
2745
2789
|
'[--description "<s>"] [--external-url <url>] [--image-base <url> | --image-renderer 0x..] [--attributes-renderer 0x..] ' +
|
|
2746
|
-
'[--max N] [--mint-count N | --mint-all] [--schema key:Type:Auth,…] [--no-seed] ' +
|
|
2790
|
+
'[--max N] [--mint-count N | --mint-all] [--schema key:Type:Auth,…] [--no-seed | --seed-source 0x..] ' +
|
|
2747
2791
|
'[--dep <name@version|0x..>[,…]] [--dep-registry 0x..] ' +
|
|
2748
2792
|
'[--unpaused] [--minter 0x..] [--primary-payee 0x..] [--royalty-bps N] [--721c recommended|0x..] [--backend ipfs|arweave] [--dry-run] [--confirm] [--bootstrap-factory]';
|
|
2749
2793
|
const scriptPath = flags.script;
|
|
@@ -2835,11 +2879,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
2835
2879
|
else {
|
|
2836
2880
|
factory = await ensureSeriesCodeFactory(publicClient, flags.factory, !!flags['bootstrap-factory']);
|
|
2837
2881
|
}
|
|
2838
|
-
const seedSource = flags
|
|
2839
|
-
? zeroAddress
|
|
2840
|
-
: dryRun
|
|
2841
|
-
? (resolveSeedSource(chainId) ?? zeroAddress)
|
|
2842
|
-
: await ensureSeedSource(publicClient);
|
|
2882
|
+
const seedSource = await resolveSeedSourceFlag(flags, publicClient, dryRun, chainId);
|
|
2843
2883
|
// --onchain-uri: resolve the two canonical singletons the lane points at. The generator is
|
|
2844
2884
|
// per-chain infrastructure (constructor-wired to the dependency registry, the abx.js/gunzip
|
|
2845
2885
|
// runtime pointers, and default gateways), so it is NEVER auto-deployed — a chain without one
|
|
@@ -2890,6 +2930,13 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
2890
2930
|
// schemas: key:Type:Auth (comma-separated), Type carrying optional Select options / Range bounds
|
|
2891
2931
|
// (Select[A|B|C], Uint256Range[0..100], …) → key, enum indices, bytes32 min/max, selectOptions.
|
|
2892
2932
|
const schemas = parseSchemaSpecs(flags.schema);
|
|
2933
|
+
// Say it where the decision is made, not in a doc the creator may never open: on an edition, a
|
|
2934
|
+
// holder-writable param is SHARED by every holder of that id. See `editionSchemaAdvisory`.
|
|
2935
|
+
if (flags.copies !== undefined) {
|
|
2936
|
+
const advisory = editionSchemaAdvisory(schemas);
|
|
2937
|
+
if (advisory)
|
|
2938
|
+
warn(advisory);
|
|
2939
|
+
}
|
|
2893
2940
|
// Dependencies: ordered `--dep` refs (repeatable / comma-separable) — index 0 = the runtime,
|
|
2894
2941
|
// by convention. `name@version` ⇒ Resolution.Registry (resolved through the soft registry
|
|
2895
2942
|
// pointer); `0x…` ⇒ Resolution.OnChain (a raw data contract, no registry involved). The legs
|
|
@@ -2969,7 +3016,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
2969
3016
|
}
|
|
2970
3017
|
else {
|
|
2971
3018
|
// Renderer-only (in-chain SVG): no program to chunk — the image/attributes are Solidity field
|
|
2972
|
-
// renderers. Nothing rides SSTORE2; the whole
|
|
3019
|
+
// renderers. Nothing rides SSTORE2; the whole work is computed on-chain per view.
|
|
2973
3020
|
contentSummary = 'no program — image + traits computed on-chain by Solidity field renderers';
|
|
2974
3021
|
ok(contentSummary);
|
|
2975
3022
|
}
|
|
@@ -2994,14 +3041,14 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
2994
3041
|
contractFields.push({ field: encodeTag(F.externalUrl), representation: encodeTag(R.inline), value: toHex(String(flags['external-url'])) });
|
|
2995
3042
|
identityFields.push('external_url');
|
|
2996
3043
|
}
|
|
2997
|
-
// authorship + rights — same on-chain inline collection-field slot (
|
|
3044
|
+
// authorship + rights — same on-chain inline collection-field slot (creator / license / …).
|
|
2998
3045
|
contractFields.push(...authorshipContractFields(flags));
|
|
2999
3046
|
for (const [flag, field] of AUTHORSHIP_DEPLOY_FIELDS)
|
|
3000
3047
|
if (flags[flag])
|
|
3001
3048
|
identityFields.push(field);
|
|
3002
3049
|
// --image-renderer: bake the on-chain `image` field to a `renderer` representation — the SVG is
|
|
3003
3050
|
// COMPUTED on-chain by a Solidity IAbxFieldRenderer (e.g. a SeedSvgRenderer fork) from the token's
|
|
3004
|
-
// seed + params. THE in-chain-
|
|
3051
|
+
// seed + params. THE in-chain-renderer lane: the marketplace still lives in tokenURI itself — no bucket,
|
|
3005
3052
|
// no effect runner, no resolver. Mutually exclusive with --image-base (both set `image`). Verified
|
|
3006
3053
|
// like --attributes-renderer: a real deploy REFUSES a codeless address; dry-run probes best-effort.
|
|
3007
3054
|
if (flags['image-renderer']) {
|
|
@@ -3210,7 +3257,15 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3210
3257
|
// TOKENURI PUBLIC-READ GAS — a large --onchain-uri document assembles on-chain per call; an
|
|
3211
3258
|
// unauthenticated public read (Etherscan "Read Contract" with no wallet connected) can hit a client
|
|
3212
3259
|
// gas cap and appear to REVERT. Expected, not a bug — a real session mis-diagnosed it as indexing lag.
|
|
3213
|
-
|
|
3260
|
+
//
|
|
3261
|
+
// Same threshold as the staged-bytes gate (`ONCHAIN_READ_WARN_BYTES`), since it is the same measured
|
|
3262
|
+
// cost: `tokenURI` string-building runs ~360-405k gas per KB of assembled document (superlinear — the
|
|
3263
|
+
// rate climbs with size). This one only WARNS
|
|
3264
|
+
// where the staged-bytes path refuses, and deliberately so: `estBytes` is an ESTIMATE (a dependency
|
|
3265
|
+
// of unknown size is undercounted — see `unknownDepSizes`), and a registry-hosted library like p5 is
|
|
3266
|
+
// legitimately ~200KB on its own, so the honest move is to name the cost and point at the piecewise
|
|
3267
|
+
// getters rather than to block a shipped lane on a heuristic.
|
|
3268
|
+
const tokenUriGasRisk = onChainUri && !codeDir && (scriptAnalysis?.doc.estBytes ?? 0) >= ONCHAIN_READ_WARN_BYTES;
|
|
3214
3269
|
// --onchain-uri legs (ride the setup multicall, before the mints): the animation_url field
|
|
3215
3270
|
// pointing at the generator, and the two URI renderers. The param surface needs NO leg — the
|
|
3216
3271
|
// generator enumerates it from the token on-chain (`contractParamKeys`/`tokenParamKeys`, kept in
|
|
@@ -3298,7 +3353,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3298
3353
|
if (!flags.description)
|
|
3299
3354
|
warn(`no --description — the on-chain metadata ships with no description (the one creators most often forget). Add --description "…" or deploy bare.`);
|
|
3300
3355
|
info(`content: ${contentSummary}`);
|
|
3301
|
-
|
|
3356
|
+
printSeedSourcePlan(seedSource, chainId);
|
|
3302
3357
|
info(`PostParam schema(s): ${schemaSummary}`);
|
|
3303
3358
|
info(`dependencies: ${deps.length ? deps.map((d, i) => `[${i}] ${d.display}`).join(' · ') + ` — ${deps.length} setDependency leg(s)` : 'none'}` +
|
|
3304
3359
|
(depRegistry ? ` + setDependencyRegistry → ${depRegistry}` : hasRegistryDeps ? ' (no registry pointer — see the warning above)' : ''));
|
|
@@ -3335,7 +3390,9 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3335
3390
|
else
|
|
3336
3391
|
info(` ${dim('nothing to run — image + traits are on-chain; a resolver is neither needed nor a benefit here.')}`);
|
|
3337
3392
|
if (tokenUriGasRisk) {
|
|
3338
|
-
|
|
3393
|
+
const docKb = Math.round((scriptAnalysis?.doc.estBytes ?? 0) / 1024);
|
|
3394
|
+
info(` ${dim('public-read note:')} this ~${docKb}KB doc assembles on-chain per call — ${bold(`~${Math.round(tokenUriGasEstimate(scriptAnalysis?.doc.estBytes ?? 0) / 1_000_000)}M gas per tokenURI`)} at the measured ~360-405k gas/KB (climbing with size). An ${bold('unauthenticated read on Etherscan')} ("Read Contract", no wallet connected) may hit its gas cap and appear to ${bold('REVERT')} — ${dim('EXPECTED, not a broken token and NOT an indexing issue. Verify with `abx tokenuri`, or connect a wallet / use a high-gas RPC. Do not run `abx index --full` to "fix" it.')}`);
|
|
3395
|
+
info(` ${dim('these are OFF-CHAIN view reads by design:')} past a node\'s cap, a client assembles from the generator\'s piecewise getters instead of one call — ${dim('`document`, `tokenDataJson`, `dependencyTag`, `abxJs`, `gunzipScript`, `registryScriptChunk`. No contract can call tokenURI inside a transaction at this size; nothing on-chain depends on it.')}`);
|
|
3339
3396
|
}
|
|
3340
3397
|
}
|
|
3341
3398
|
else {
|
|
@@ -3849,7 +3906,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3849
3906
|
// • `--code-dir` (directory-build code drops) — not ported; use `--script`.
|
|
3850
3907
|
// • `--dep` / `--dep-registry` (declared code dependencies) — not ported; ship a self-contained
|
|
3851
3908
|
// script (the skill already recommends zero-dependency vanilla JS as the cleanest option).
|
|
3852
|
-
// • `--image-renderer` / `--attributes-renderer` / `--image-base` (the in-chain-Solidity-
|
|
3909
|
+
// • `--image-renderer` / `--attributes-renderer` / `--image-base` (the in-chain-Solidity-renderer and
|
|
3853
3910
|
// deterministic-off-chain-thumbnail lanes) — not ported.
|
|
3854
3911
|
// • `--no-delegation` — refused outright: EditionCode has no TokenOwner-leg delegation to opt out
|
|
3855
3912
|
// of (its auth leg generalizes to "any holder", per the parity plan's ConfigurableParams note).
|
|
@@ -3861,7 +3918,7 @@ export const DEPLOY_CODE_EDITION_FLAGS = new Set([
|
|
|
3861
3918
|
'script', 'name', 'symbol', 'description', 'external-url', 'description-onchain',
|
|
3862
3919
|
...AUTHORSHIP_DEPLOY_FIELDS.map(([flag]) => flag),
|
|
3863
3920
|
'max', 'mint-count', 'mint-all', 'no-mint', 'mint-amount', 'unpaused', 'minter', 'primary-payee', 'royalty-bps', '721c',
|
|
3864
|
-
'schema', 'no-seed',
|
|
3921
|
+
'schema', 'no-seed', 'seed-source',
|
|
3865
3922
|
'public-base-url', 'onchain-uri', 'generator', 'renderer', 'port',
|
|
3866
3923
|
'sign', 'unsigned', 'for', 'salt', 'factory', 'bootstrap-factory', 'sign-url-file',
|
|
3867
3924
|
'dry-run', 'confirm', 'yes', 'json', 'copies',
|
|
@@ -3875,7 +3932,7 @@ export const DEPLOY_CODE_EDITION_FLAGS = new Set([
|
|
|
3875
3932
|
export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
3876
3933
|
const usage = 'abx deploy-code --copies <n|open> --script <file> --name "Title" --symbol SYM (--public-base-url https://your.resolver.domain | --onchain-uri) ' +
|
|
3877
3934
|
'[--description "<s>"] [--external-url <url>] [--max N] [--mint-count N | --mint-all] [--mint-amount N] ' +
|
|
3878
|
-
'[--schema key:Type:Auth,…] [--no-seed] [--unpaused] [--minter 0x..] [--primary-payee 0x..] [--royalty-bps N] [--721c recommended|0x..]';
|
|
3935
|
+
'[--schema key:Type:Auth,…] [--no-seed | --seed-source 0x..] [--unpaused] [--minter 0x..] [--primary-payee 0x..] [--royalty-bps N] [--721c recommended|0x..]';
|
|
3879
3936
|
refuseStrayFlags(flags, DEPLOY_CODE_EDITION_FLAGS, 'deploy-code');
|
|
3880
3937
|
warnSignWithoutFor(flags);
|
|
3881
3938
|
const editionSize = parseCopies(flags.copies);
|
|
@@ -3957,11 +4014,7 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
3957
4014
|
else {
|
|
3958
4015
|
factory = await ensureEditionCodeFactory(publicClient, flags.factory, !!flags['bootstrap-factory']);
|
|
3959
4016
|
}
|
|
3960
|
-
const seedSource = flags
|
|
3961
|
-
? zeroAddress
|
|
3962
|
-
: dryRun
|
|
3963
|
-
? (resolveSeedSource(chainId) ?? zeroAddress)
|
|
3964
|
-
: await ensureSeedSource(publicClient);
|
|
4017
|
+
const seedSource = await resolveSeedSourceFlag(flags, publicClient, dryRun, chainId);
|
|
3965
4018
|
let generator = zeroAddress;
|
|
3966
4019
|
let metadataRenderer = zeroAddress;
|
|
3967
4020
|
if (onChainUri) {
|
|
@@ -3986,6 +4039,13 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
3986
4039
|
info('off-chain fallback pointer left empty (the renderer is authoritative); set --public-base-url to bake one anyway.');
|
|
3987
4040
|
}
|
|
3988
4041
|
const schemas = parseSchemaSpecs(flags.schema);
|
|
4042
|
+
// Say it where the decision is made, not in a doc the creator may never open: on an edition, a
|
|
4043
|
+
// holder-writable param is SHARED by every holder of that id. See `editionSchemaAdvisory`.
|
|
4044
|
+
if (flags.copies !== undefined) {
|
|
4045
|
+
const advisory = editionSchemaAdvisory(schemas);
|
|
4046
|
+
if (advisory)
|
|
4047
|
+
warn(advisory);
|
|
4048
|
+
}
|
|
3989
4049
|
// Dependencies — same shape as the 721 SeriesCode twin (see its block for the full reasoning).
|
|
3990
4050
|
// `EditionCode` already inherits the `Dependencies` extension and calls `_initDependencies()`, and
|
|
3991
4051
|
// the legs are the same `setDependency`/`setDependencyRegistry` calls on the same ABI, so this was
|
|
@@ -4149,7 +4209,7 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4149
4209
|
info(`deterministic address: ${shownAddr}`);
|
|
4150
4210
|
info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}%`);
|
|
4151
4211
|
info(`content: script ${bytes.length} bytes → ${scriptChunks.length} on-chain chunk(s)`);
|
|
4152
|
-
|
|
4212
|
+
printSeedSourcePlan(seedSource, chainId);
|
|
4153
4213
|
info(`PostParam schema(s): ${schemas.length ? schemas.map((s) => describeSchema(s)).join(', ') : 'none'}`);
|
|
4154
4214
|
info(`mint: ${mintCount > 0 ? `${mintCount} id(s) × ${mintAmount} cop${mintAmount === 1n ? 'y' : 'ies'} → ${deployer ?? 'your wallet'} at deploy` : 'deferred (mint later / external minter)'}`);
|
|
4155
4215
|
info(`transactions: 2 — deploy + setup multicall (script chunks${schemas.length ? ' + schemas' : ''}${onChainUri ? ' + on-chain URI wiring' : ''}${mintCount > 0 ? ' + reserve mints' : ''})`);
|
|
@@ -4243,26 +4303,28 @@ export async function resolveTransferValidatorFlag(flags, publicClient, dryRun,
|
|
|
4243
4303
|
return zeroAddress;
|
|
4244
4304
|
const chainId = resolveChain(CHAIN).id;
|
|
4245
4305
|
const validator = parseTransferValidatorValue(raw, { chainId, chainLabel: CHAIN });
|
|
4246
|
-
//
|
|
4247
|
-
// matters; a sandbox/private chain won't have it).
|
|
4248
|
-
//
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4306
|
+
// Usability precheck — for the recommended constant too (presence on THIS chain is the fact that
|
|
4307
|
+
// matters; a sandbox/private chain won't have it). This asks the same question the factory asks:
|
|
4308
|
+
// has code AND is not a permissive fallback that would enforce nothing (a Safe, an uninitialised
|
|
4309
|
+
// proxy, a 7702-delegated EOA all pass a bare has-code check and then wave every transfer
|
|
4310
|
+
// through). A dry run tolerates an unreachable RPC (it sends nothing); a real deploy refuses to
|
|
4311
|
+
// enroll blind.
|
|
4312
|
+
const probe = await probeTransferValidator(publicClient, validator);
|
|
4313
|
+
if (probe.verdict === 'unreachable') {
|
|
4254
4314
|
if (!dryRun)
|
|
4255
|
-
throw new Error(`--721c: couldn't verify the validator
|
|
4256
|
-
|
|
4315
|
+
throw new Error(`--721c: couldn't verify the validator at ${validator} (${probe.error ?? 'RPC did not answer'}) — refusing to enroll blind; retry when the RPC answers.`);
|
|
4316
|
+
info(dim(`--721c: RPC unreachable, so the validator check is deferred — a real deploy verifies ${validator} is a usable validator first.`));
|
|
4257
4317
|
}
|
|
4258
|
-
if (
|
|
4259
|
-
info(dim(`--721c: RPC unreachable, so the validator code check is deferred — a real deploy verifies ${validator} has code first.`));
|
|
4260
|
-
}
|
|
4261
|
-
else if (!code || code === '0x') {
|
|
4318
|
+
else if (probe.verdict !== 'ok') {
|
|
4262
4319
|
const rec = resolveRecommendedTransferValidator(chainId);
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4320
|
+
const recHint = rec && rec !== validator ? ` (the known-good one: --721c recommended → ${rec})` : '';
|
|
4321
|
+
throw new Error(probe.verdict === 'no-code'
|
|
4322
|
+
? `--721c: no contract code at ${validator} on ${CHAIN} — the deploy would revert InvalidTransferValidator(). ` +
|
|
4323
|
+
`A transfer validator must be a DEPLOYED contract on this chain${recHint}.`
|
|
4324
|
+
: `--721c: ${validator} has code on ${CHAIN}, but it is not a transfer validator — it answers ANY function call ` +
|
|
4325
|
+
`successfully (a Safe, an uninitialised proxy, or a 7702-delegated EOA does this), so every transfer would ` +
|
|
4326
|
+
`silently pass validation while ERC-165 and getTransferValidator() reported enforcement as ON. The factory ` +
|
|
4327
|
+
`refuses it too (InvalidTransferValidator()). Pass a real validator contract${recHint}, or drop --721c.`);
|
|
4266
4328
|
}
|
|
4267
4329
|
// The one plain statement — printed once, only when enrolling. Never appears un-enrolled.
|
|
4268
4330
|
info(validator === resolveRecommendedTransferValidator(chainId)
|
|
@@ -4270,4 +4332,79 @@ export async function resolveTransferValidatorFlag(flags, publicClient, dryRun,
|
|
|
4270
4332
|
: `ERC-${standard}: every non-mint transfer is checked by validator ${validator} — its policy decides which operators may transfer. Manage with \`abx set-transfer-validator\`.`);
|
|
4271
4333
|
return validator;
|
|
4272
4334
|
}
|
|
4335
|
+
/**
|
|
4336
|
+
* Resolve the three-way seed decision into the InitParams `seedSource`, for both code lanes:
|
|
4337
|
+
*
|
|
4338
|
+
* - `--no-seed` → `zeroAddress`. No mint-time seed at all.
|
|
4339
|
+
* - nothing, or `canonical` → the canonical `AbxSeedSource` via `ensureSeedSource` (manifest →
|
|
4340
|
+
* self-heal to the deterministic address → CREATE2-deploy it). This
|
|
4341
|
+
* is today's behaviour, byte-for-byte, and stays the default.
|
|
4342
|
+
* - `--seed-source 0x…` → the creator's own `IAbxSeedSource`, **probed first**.
|
|
4343
|
+
*
|
|
4344
|
+
* The flag exists because we tell creators, in the docs and in this command's own help, that the fix
|
|
4345
|
+
* for "the canonical seed isn't strong enough for my raffle" is to point `seedSource` at a
|
|
4346
|
+
* commit-reveal / VRF-backed source of their own. Until now the only route was an `ABX_SEED_SOURCE`
|
|
4347
|
+
* env var checked for nothing but code length, which made a first-class promise out of a
|
|
4348
|
+
* side entrance.
|
|
4349
|
+
*
|
|
4350
|
+
* **Everything that isn't the canonical singleton gets probed**, including an address that arrived
|
|
4351
|
+
* via `ABX_SEED_SOURCE` — so the env lane keeps working and stops being the unchecked one. The
|
|
4352
|
+
* comparison is against the deterministic canonical address rather than "did a flag say so", because
|
|
4353
|
+
* that is the actual question (is this our audited bytecode, or someone's contract we know nothing
|
|
4354
|
+
* about?). A dry run tolerates an unreachable RPC and says the check is deferred; a real deploy
|
|
4355
|
+
* refuses to bake a seed source it couldn't verify.
|
|
4356
|
+
*
|
|
4357
|
+
* The probe here runs with **no `from`**, unlike the owner op's (which calls as the token). That is
|
|
4358
|
+
* not an oversight: the clone's address is a function of the CREATE2 salt, which isn't reserved yet
|
|
4359
|
+
* at this point, so a source that gates on `msg.sender` could not have been told about this token in
|
|
4360
|
+
* the first place. A gated/commit-reveal source is a deploy-then-`abx set-seed-source` flow by
|
|
4361
|
+
* construction, and the `reverted` refusal says exactly that.
|
|
4362
|
+
*/
|
|
4363
|
+
export async function resolveSeedSourceFlag(flags, publicClient, dryRun, chainId) {
|
|
4364
|
+
const raw = flags['seed-source'];
|
|
4365
|
+
// `--resume` finishes an EXISTING contract, and the seed source is an InitParams field — fixed at
|
|
4366
|
+
// creation, never in the setup multicall this replays. So there is nothing here to resolve, probe,
|
|
4367
|
+
// or (worse) lazily DEPLOY. Not a refusal: `--resume`'s own contract is "pass the same content
|
|
4368
|
+
// flags", so the creator's original command legitimately still carries `--no-seed`/`--seed-source`.
|
|
4369
|
+
// Say plainly that it's read from chain, and name the verb that can actually change it.
|
|
4370
|
+
if (flags.resume !== undefined) {
|
|
4371
|
+
if (raw !== undefined || flags['no-seed'] !== undefined) {
|
|
4372
|
+
info(dim(`--resume: the seed source was fixed when ${flags.resume} was created, so ${raw !== undefined ? '--seed-source' : '--no-seed'} has nothing to write here ` +
|
|
4373
|
+
`(it isn't part of the setup transaction being replayed). Read it with \`abx state\`; change it with \`abx set-seed-source\`.`));
|
|
4374
|
+
}
|
|
4375
|
+
return zeroAddress; // unused by the resume legs
|
|
4376
|
+
}
|
|
4377
|
+
if (flags['no-seed'] !== undefined) {
|
|
4378
|
+
// Two flags, one field. Refuse rather than pick a winner — a creator who passed both has a
|
|
4379
|
+
// belief about the outcome, and either answer betrays half of them.
|
|
4380
|
+
if (raw !== undefined) {
|
|
4381
|
+
throw new Error('--no-seed and --seed-source contradict each other: one says "no mint-time seed at all", the other names where the seed comes from. Drop one.');
|
|
4382
|
+
}
|
|
4383
|
+
return zeroAddress;
|
|
4384
|
+
}
|
|
4385
|
+
const canonical = canonicalSeedSource(chainId);
|
|
4386
|
+
const requested = raw === undefined ? canonical : parseSeedSourceValue(raw, { chainId });
|
|
4387
|
+
// The canonical singleton keeps its existing bootstrap path (deploy it if this chain lacks one).
|
|
4388
|
+
// Anything else must already exist — we never deploy a stranger's contract.
|
|
4389
|
+
const same = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
4390
|
+
let seedSource;
|
|
4391
|
+
if (same(requested, canonical)) {
|
|
4392
|
+
seedSource = dryRun ? (resolveSeedSource(chainId) ?? zeroAddress) : await ensureSeedSource(publicClient);
|
|
4393
|
+
}
|
|
4394
|
+
else {
|
|
4395
|
+
seedSource = requested;
|
|
4396
|
+
}
|
|
4397
|
+
if (seedSource === zeroAddress || same(seedSource, canonical))
|
|
4398
|
+
return seedSource;
|
|
4399
|
+
// Custom source (flag or ABX_SEED_SOURCE) — ask it the question the mint will ask.
|
|
4400
|
+
const probe = await probeSeedSource(publicClient, seedSource);
|
|
4401
|
+
if (probe.verdict === 'unreachable' && dryRun) {
|
|
4402
|
+
info(dim(`--seed-source: RPC unreachable, so the seed-source probe is deferred — a real deploy verifies ${seedSource} answers seed(uint256,address) with 32 bytes first.`));
|
|
4403
|
+
return seedSource;
|
|
4404
|
+
}
|
|
4405
|
+
if (probe.verdict !== 'ok')
|
|
4406
|
+
refuseUnusableSeedSource(probe, { flag: raw === undefined ? 'ABX_SEED_SOURCE' : '--seed-source', chainLabel: CHAIN });
|
|
4407
|
+
info(`seed source: CUSTOM ${seedSource} ${dim('— answers seed(uint256,address) with 32 bytes (probed). Its randomness properties are yours to state to buyers; ABX makes no claim about them.')}`);
|
|
4408
|
+
return seedSource;
|
|
4409
|
+
}
|
|
4273
4410
|
//# sourceMappingURL=deploy.js.map
|