@artblocks/abx-cli 0.1.0-alpha.22 → 0.1.0-alpha.25
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 +253 -0
- package/dist/commands/deploy.d.ts +15 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +155 -59
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/reads.d.ts.map +1 -1
- package/dist/commands/reads.js +6 -2
- package/dist/commands/reads.js.map +1 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +8 -1
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/kind.d.ts +8 -0
- package/dist/kind.d.ts.map +1 -1
- package/dist/kind.js +17 -0
- package/dist/kind.js.map +1 -1
- package/dist/main.js +21 -6
- package/dist/main.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +59 -2
- package/dist/output.js.map +1 -1
- package/dist/ownerops.d.ts +34 -0
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +136 -5
- package/dist/ownerops.js.map +1 -1
- package/dist/riskgate.d.ts +19 -0
- package/dist/riskgate.d.ts.map +1 -1
- package/dist/riskgate.js +31 -1
- package/dist/riskgate.js.map +1 -1
- package/dist/schema.js +4 -4
- package/dist/schema.js.map +1 -1
- package/dist/script-chunks.d.ts +8 -0
- package/dist/script-chunks.d.ts.map +1 -0
- package/dist/script-chunks.js +35 -0
- package/dist/script-chunks.js.map +1 -0
- package/package.json +6 -6
- package/skill/SKILL.md +10 -7
- package/skill/reference/code-projects.md +3 -3
- package/skill/reference/decisions.md +1 -1
- package/skill/reference/hosting.md +4 -2
- package/skill/reference/operating.md +6 -0
- package/skill/reference/setup.md +2 -1
package/dist/commands/deploy.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
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, 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';
|
|
18
|
+
import { DEFAULT_CHAIN_KEY, DEP_RESOLUTION, METADATA_FIELD as F, contentIdFromLocator, 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, AbxServiceClient, } from '@artblocks/abx-sdk';
|
|
19
19
|
import { DIRECT_URL_BACKENDS, contentTypeFromPath, decideImageContentLane, hashContent, isTurboArweave, resolveBackend, validateRenderStorageCombo, } from '@artblocks/abx-storage';
|
|
20
20
|
import { DEFAULT_PORT, contentHash, generateContent, resolveBaseUrl, startTokenApiServer } from '@artblocks/abx-token-api';
|
|
21
21
|
import { encodeFunctionData, toHex, zeroAddress } from 'viem';
|
|
@@ -24,10 +24,11 @@ import { parseDepFlag } from '../deps.js';
|
|
|
24
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
|
+
import { planOnChainScript } from '../script-chunks.js';
|
|
27
28
|
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, ONCHAIN_READ_WARN_BYTES, tokenUriGasEstimate, authorshipContractFields, computeContentPlan, envStagingSender, parseCompress, parseSeedSourceValue, canonicalSeedSource, refuseUnusableSeedSource, parseTransferValidatorValue, guardOnChainSize, previewImageStaging, sessionStagingSender, stageImageField, stageImageFieldsBatch, } from '../ownerops.js';
|
|
29
|
+
import { AUTHORSHIP_DEPLOY_FIELDS, ONCHAIN_PROJECT_SOFT_LIMIT, ONCHAIN_READ_WARN_BYTES, tokenUriGasEstimate, authorshipContractFields, gatewayContractFields, computeContentPlan, envStagingSender, parseCompress, parseSeedSourceValue, canonicalSeedSource, refuseUnusableSeedSource, parseTransferValidatorValue, guardOnChainSize, previewImageStaging, refusePrewrappedImage, sessionStagingSender, stageImageField, stageImageFieldsBatch, } from '../ownerops.js';
|
|
29
30
|
import { canonicalLabel } from '../remote.js';
|
|
30
|
-
import { confirmSend, gatedSend, laneFromFlags } from '../riskgate.js';
|
|
31
|
+
import { assertLaneCanSign, confirmSend, gatedSend, laneFromFlags } from '../riskgate.js';
|
|
31
32
|
import { describeSchema, editionSchemaAdvisory, parseSchemaSpecs } from '../schema.js';
|
|
32
33
|
import { parseSeriesTraits } from '../series-traits.js';
|
|
33
34
|
import { decodeOnChainJson } from '../served.js';
|
|
@@ -192,6 +193,8 @@ export function deployCommandLine(flags, salt) {
|
|
|
192
193
|
str('backend', flags.backend);
|
|
193
194
|
str('gateway', flags.gateway);
|
|
194
195
|
str('bucket', flags.bucket);
|
|
196
|
+
str('ipfs-gateway', flags['ipfs-gateway']);
|
|
197
|
+
str('arweave-gateway', flags['arweave-gateway']);
|
|
195
198
|
str('public-base-url', flags['public-base-url']);
|
|
196
199
|
if (flags.sign !== undefined)
|
|
197
200
|
parts.push('--sign');
|
|
@@ -223,6 +226,8 @@ export function deploySeriesCommandLine(flags, salt) {
|
|
|
223
226
|
str('backend', flags.backend);
|
|
224
227
|
str('gateway', flags.gateway);
|
|
225
228
|
str('bucket', flags.bucket);
|
|
229
|
+
str('ipfs-gateway', flags['ipfs-gateway']);
|
|
230
|
+
str('arweave-gateway', flags['arweave-gateway']);
|
|
226
231
|
str('public-base', flags['public-base']);
|
|
227
232
|
str('public-base-url', flags['public-base-url']);
|
|
228
233
|
bool('mint-all');
|
|
@@ -278,6 +283,8 @@ export function deployCodeCommandLine(flags, salt) {
|
|
|
278
283
|
str('backend', flags.backend);
|
|
279
284
|
str('gateway', flags.gateway);
|
|
280
285
|
str('public-base-url', flags['public-base-url']);
|
|
286
|
+
str('ipfs-gateway', flags['ipfs-gateway']);
|
|
287
|
+
str('arweave-gateway', flags['arweave-gateway']);
|
|
281
288
|
if (flags.sign !== undefined)
|
|
282
289
|
parts.push('--sign');
|
|
283
290
|
if (flags.unsigned !== undefined)
|
|
@@ -296,11 +303,14 @@ export const imageKeccakField = (value) => ({
|
|
|
296
303
|
representation: encodeTag(R.keccak256),
|
|
297
304
|
value,
|
|
298
305
|
});
|
|
299
|
-
export const imageInlineField = (svg) =>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
export const imageInlineField = (svg) => {
|
|
307
|
+
refusePrewrappedImage(svg, 'inline image');
|
|
308
|
+
return {
|
|
309
|
+
field: encodeTag(F.image),
|
|
310
|
+
representation: encodeTag(R.inline),
|
|
311
|
+
value: toHex(svg),
|
|
312
|
+
};
|
|
313
|
+
};
|
|
304
314
|
/** An `image` field pointing at an off-chain URL (the renderer emits it verbatim). */
|
|
305
315
|
export const imageUrlField = (url) => ({
|
|
306
316
|
field: encodeTag(F.image),
|
|
@@ -314,6 +324,59 @@ export const imageUrlTemplateField = (template) => ({
|
|
|
314
324
|
representation: encodeTag(R.urlTemplate),
|
|
315
325
|
value: toHex(template),
|
|
316
326
|
});
|
|
327
|
+
/**
|
|
328
|
+
* An `image` field carrying a CONTENT-ADDRESSED identity — the bare CID / txid, with the serving
|
|
329
|
+
* gateway supplied at read time from the collection's `abx_gateway_*` preference (renderer spec v11).
|
|
330
|
+
*
|
|
331
|
+
* This replaces what `--onchain-uri --backend ipfs|arweave` used to write. That path stored the
|
|
332
|
+
* backend's gateway HTTPS URL as a `url` field, which looked identical in a marketplace and was
|
|
333
|
+
* wrong in two ways that only showed up later: the creator's chosen gateway host was welded into a
|
|
334
|
+
* value they might have locked, so a dead gateway became a dead token rather than a repoint; and
|
|
335
|
+
* `abx_provenance` reported `source: url` for bytes that live on IPFS, so the chain stopped saying
|
|
336
|
+
* where the work actually was. The CID is identity and the gateway is a preference — one field each.
|
|
337
|
+
*
|
|
338
|
+
* `{id}` in the value still substitutes at read (the O(1) directory pattern), so a collection-scope
|
|
339
|
+
* `ipfs` field addresses a whole pinned folder exactly as `url-template` does for a plain CDN.
|
|
340
|
+
*/
|
|
341
|
+
export const imageContentAddressedField = (network, contentId) => ({
|
|
342
|
+
field: encodeTag(F.image),
|
|
343
|
+
representation: encodeTag(network === 'ipfs' ? R.ipfs : R.arweave),
|
|
344
|
+
value: toHex(contentId),
|
|
345
|
+
});
|
|
346
|
+
/**
|
|
347
|
+
* The `image` field for a locator a durable backend just produced — ONE decision point, so every
|
|
348
|
+
* lane (1/1, series, both edition twins) writes the same shape for the same backend.
|
|
349
|
+
*
|
|
350
|
+
* `ipfs` / `arweave` store IDENTITY: recover the bare CID/txid from the backend's gateway URL and
|
|
351
|
+
* commit that, leaving the serving prefix to the collection's `abx_gateway_*` preference. Any path
|
|
352
|
+
* suffix rides along, so a directory upload's `<cid>/{id}.png` is still one collection-scope field
|
|
353
|
+
* covering every token — the renderer substitutes `{id}` for `ipfs`/`arweave` exactly as it does for
|
|
354
|
+
* `url-template`.
|
|
355
|
+
*
|
|
356
|
+
* `cloud` and friends keep the plain `url` / `url-template` they always had: an https CDN locator IS
|
|
357
|
+
* the address, with no identity underneath to separate out.
|
|
358
|
+
*/
|
|
359
|
+
/** How a lane should describe what it just committed, so the narration matches the field written. */
|
|
360
|
+
function describeLocatorField(backendId) {
|
|
361
|
+
return backendId === 'ipfs' || backendId === 'arweave'
|
|
362
|
+
? `ON-CHAIN image as a bare ${backendId} id — the gateway is the collection's preference, repointable with ${bold('abx set-gateway')}`
|
|
363
|
+
: 'ON-CHAIN image url (the locator IS the address)';
|
|
364
|
+
}
|
|
365
|
+
function imageLocatorField(backendId, locator, isTemplate = false) {
|
|
366
|
+
const network = backendId === 'ipfs' || backendId === 'arweave' ? backendId : null;
|
|
367
|
+
if (network) {
|
|
368
|
+
const contentId = contentIdFromLocator(network, locator);
|
|
369
|
+
if (contentId)
|
|
370
|
+
return imageContentAddressedField(network, contentId);
|
|
371
|
+
// A subdomain-style gateway (`https://<cid>.ipfs.dweb.link`), which ABX's own backends never
|
|
372
|
+
// produce. Degrade to the old shape rather than guess at the CID — and say so, because the
|
|
373
|
+
// consequence (a welded host) is exactly what the caller thought they were avoiding.
|
|
374
|
+
warn(`could not recover the ${network} content id from ${locator} — writing the gateway URL as a plain ` +
|
|
375
|
+
`'url' field instead. That URL's host is then part of the on-chain value: a gateway change ` +
|
|
376
|
+
`needs ${bold('abx set-field')}, not ${bold('abx set-gateway')}.`);
|
|
377
|
+
}
|
|
378
|
+
return isTemplate ? imageUrlTemplateField(locator) : imageUrlField(locator);
|
|
379
|
+
}
|
|
317
380
|
export const looksLikeSvg = (s) => /^\s*<(\?xml|svg)/i.test(s);
|
|
318
381
|
/**
|
|
319
382
|
* Does the IMAGE itself end up on-chain for this deploy? Two ways it can:
|
|
@@ -413,8 +476,8 @@ walletAddr) {
|
|
|
413
476
|
const backendId = backendResolution(overrides).backend;
|
|
414
477
|
if (onchainUrlLane) {
|
|
415
478
|
return {
|
|
416
|
-
tokenFields: [
|
|
417
|
-
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) → would upload to ${backendId};
|
|
479
|
+
tokenFields: [imageLocatorField(backendId, `<${backendId}-url>`)],
|
|
480
|
+
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) → would upload to ${backendId}; ${describeLocatorField(backendId)}`,
|
|
418
481
|
};
|
|
419
482
|
}
|
|
420
483
|
return {
|
|
@@ -442,8 +505,8 @@ walletAddr) {
|
|
|
442
505
|
await backend.put(hash, { bytes, contentType });
|
|
443
506
|
ok(`image → ${url} ${dim(`(${backend.id}; baked on-chain, no server)`)}`);
|
|
444
507
|
return {
|
|
445
|
-
tokenFields: [
|
|
446
|
-
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) on ${backend.id} · ON-CHAIN image
|
|
508
|
+
tokenFields: [imageLocatorField(backend.id, url)],
|
|
509
|
+
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) on ${backend.id} · ON-CHAIN image ${describeLocatorField(backend.id)}`,
|
|
447
510
|
};
|
|
448
511
|
}
|
|
449
512
|
warn(`'${backend.id}' cannot upload a directory, so no public image URL could be baked — falling back to custody (the on-chain image will be a placeholder).`);
|
|
@@ -815,6 +878,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
815
878
|
// explicit --salt is given. With the wallet lane the signer is unknown until they
|
|
816
879
|
// connect, so this runs per-signer.
|
|
817
880
|
const buildForDeployer = async (deployer) => {
|
|
881
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
818
882
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
819
883
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
820
884
|
// A baked on-chain image (--onchain-image) replaces prepareContent's image field —
|
|
@@ -853,7 +917,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
853
917
|
transferValidator,
|
|
854
918
|
tokenFields,
|
|
855
919
|
// authorship + rights (creator / license / …) are collection-scope on-chain fields.
|
|
856
|
-
contractFields: authorshipContractFields(flags),
|
|
920
|
+
contractFields: [...authorshipContractFields(flags), ...gatewayContractFields(flags)],
|
|
857
921
|
};
|
|
858
922
|
return { clone, params, salt, contentNote };
|
|
859
923
|
};
|
|
@@ -1319,6 +1383,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1319
1383
|
info('off-chain fallback pointer left empty (the renderer is authoritative); set --public-base-url to bake one anyway.');
|
|
1320
1384
|
}
|
|
1321
1385
|
const buildForDeployer = async (deployer) => {
|
|
1386
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
1322
1387
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
1323
1388
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
1324
1389
|
const { tokenFields, contentNote } = bakedImage
|
|
@@ -1349,7 +1414,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1349
1414
|
minter,
|
|
1350
1415
|
paused,
|
|
1351
1416
|
tokenFields,
|
|
1352
|
-
contractFields: authorshipContractFields(flags),
|
|
1417
|
+
contractFields: [...authorshipContractFields(flags), ...gatewayContractFields(flags)],
|
|
1353
1418
|
};
|
|
1354
1419
|
return { clone, params, salt, contentNote };
|
|
1355
1420
|
};
|
|
@@ -1659,7 +1724,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1659
1724
|
// Image custody × URI resolution (see reference/hosting.md):
|
|
1660
1725
|
// - --onchain-image → chunk store (on-chain bytes), JSON on-chain
|
|
1661
1726
|
// - --onchain-uri + a durable backend → image OFF-CHAIN (ipfs/arweave), JSON on-chain via the
|
|
1662
|
-
// (ipfs/arweave) renderer: ONE collection
|
|
1727
|
+
// (ipfs/arweave) renderer: ONE collection-scope image field when files share
|
|
1663
1728
|
// an extension (O(1) directory), else per-token `url` (O(N))
|
|
1664
1729
|
// - --onchain-uri alone → inline SVG on-chain
|
|
1665
1730
|
// - neither → off-chain custody (keccak), served by a hosted resolver
|
|
@@ -1694,6 +1759,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1694
1759
|
// early managed-key creation + funds guard and the up-front buildFields; the session branch runs them.
|
|
1695
1760
|
const remoteEthUpload = lane === 'sign' && !onchainImage && !!backend && storageSignerChoice(overrides) === 'eth' && isTurboArweave(opts);
|
|
1696
1761
|
if (backend && !remoteEthUpload) {
|
|
1762
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
1697
1763
|
ensureArweaveIdentityForUpload(opts); // Turbo identity, created on first upload
|
|
1698
1764
|
// The deployer wallet (--for, else the .env key's address) — checked for existing Turbo credits
|
|
1699
1765
|
// if the managed key is short, so we recommend `--storage-signer eth` before any top-up.
|
|
@@ -1733,23 +1799,24 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1733
1799
|
const uniform = exts[0] !== '' && exts.every((e) => e === exts[0]);
|
|
1734
1800
|
if (dryRun || !backend) {
|
|
1735
1801
|
if (uniform) {
|
|
1736
|
-
contractFields = [
|
|
1737
|
-
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image
|
|
1802
|
+
contractFields = [imageLocatorField(backendId, `<${backendId}-dir>/{id}${exts[0]}`, true)];
|
|
1803
|
+
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image <${backendId}-dir>/{id}${exts[0]} (O(1); ${describeLocatorField(backendId)})`);
|
|
1738
1804
|
}
|
|
1739
1805
|
else {
|
|
1740
|
-
tokenFields = tokenPaths.map(({ tokenId }) => tokenFieldOf(tokenId,
|
|
1806
|
+
tokenFields = tokenPaths.map(({ tokenId }) => tokenFieldOf(tokenId, imageLocatorField(backendId, `<${backendId}-url-${tokenId}>`)));
|
|
1741
1807
|
info(`would upload ${tokenPaths.length} file(s) to ${backendId} → per-token url image fields (mixed extensions, O(N))`);
|
|
1742
1808
|
}
|
|
1743
1809
|
return;
|
|
1744
1810
|
}
|
|
1745
1811
|
if (uniform && backend.putDirectory) {
|
|
1746
|
-
// O(1) directory-base: upload the folder renamed to {id}{ext}; ONE collection
|
|
1812
|
+
// O(1) directory-base: upload the folder renamed to {id}{ext}; ONE collection-scope image
|
|
1813
|
+
// field. `{id}` substitutes at read on every locator representation, `ipfs`/`arweave` included.
|
|
1747
1814
|
const entries = tokenPaths.map(({ tokenId, path }) => ({ name: `${tokenId}${exts[0]}`, bytes: new Uint8Array(readFileSync(path)), contentType: contentTypeFromPath(path) }));
|
|
1748
1815
|
const { base } = await backend.putDirectory(entries);
|
|
1749
1816
|
const template = `${base}/{id}${exts[0]}`;
|
|
1750
|
-
contractFields = [
|
|
1817
|
+
contractFields = [imageLocatorField(backendId, template, true)];
|
|
1751
1818
|
info(`uploaded ${entries.length} file(s) as one ${backendId} directory`);
|
|
1752
|
-
info(`collection image
|
|
1819
|
+
info(`collection image: ${template} ${dim(`— one field covers every token (O(1); ${describeLocatorField(backendId)})`)}`);
|
|
1753
1820
|
}
|
|
1754
1821
|
else {
|
|
1755
1822
|
// mixed extensions (or no directory support): per-token url fields (O(N)).
|
|
@@ -1760,10 +1827,10 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1760
1827
|
const locator = (await backend.locator?.(hash)) ?? '';
|
|
1761
1828
|
if (!locator)
|
|
1762
1829
|
throw new Error(`backend ${backendId} returned no public locator for token ${tokenId} — a durable backend (ipfs/arweave) is required for --onchain-uri image hosting`);
|
|
1763
|
-
tokenFields.push(tokenFieldOf(tokenId,
|
|
1830
|
+
tokenFields.push(tokenFieldOf(tokenId, imageLocatorField(backendId, locator)));
|
|
1764
1831
|
info(`token ${tokenId} ← ${fname} → ${locator}`);
|
|
1765
1832
|
}
|
|
1766
|
-
info(`mixed file extensions → per-token
|
|
1833
|
+
info(`mixed file extensions → per-token image fields (O(N)); a uniform extension enables the O(1) directory template`);
|
|
1767
1834
|
}
|
|
1768
1835
|
return;
|
|
1769
1836
|
}
|
|
@@ -1819,6 +1886,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1819
1886
|
// Assemble the init params for a given deployer (owner/royalty/mintTo = deployer). The clone
|
|
1820
1887
|
// address is deterministic in (factory, salt) — so URIs are baked before the tx is signed.
|
|
1821
1888
|
const buildForDeployer = async (deployer) => {
|
|
1889
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
1822
1890
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
1823
1891
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
1824
1892
|
const params = {
|
|
@@ -1839,8 +1907,8 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1839
1907
|
mintTo: mintCount > 0 ? deployer : zeroAddress,
|
|
1840
1908
|
mintCount,
|
|
1841
1909
|
tokenFields,
|
|
1842
|
-
// image contract fields (the O(1)
|
|
1843
|
-
contractFields: [...contractFields, ...authorshipContractFields(flags)],
|
|
1910
|
+
// image contract fields (the O(1) collection-scope image) + authorship/rights (creator / license / …).
|
|
1911
|
+
contractFields: [...contractFields, ...authorshipContractFields(flags), ...gatewayContractFields(flags)],
|
|
1844
1912
|
};
|
|
1845
1913
|
return { clone, params, salt };
|
|
1846
1914
|
};
|
|
@@ -1877,7 +1945,9 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1877
1945
|
info(onchainImage
|
|
1878
1946
|
? 'resolution: ON-CHAIN via the renderer (chunked reader per token — image bytes on-chain)'
|
|
1879
1947
|
: offchainImageOnchainJson
|
|
1880
|
-
|
|
1948
|
+
// Say which SHAPE landed, not a fixed representation name: since v11 a content-addressed
|
|
1949
|
+
// backend writes an `ipfs`/`arweave` field, not `url`/`url-template`.
|
|
1950
|
+
? `resolution: ON-CHAIN via the renderer; image OFF-CHAIN on ${backendId} (${contractFields.length ? 'one collection-scope image field, O(1)' : 'per-token image fields, O(N)'})`
|
|
1881
1951
|
: onChainUri
|
|
1882
1952
|
? 'resolution: ON-CHAIN via the renderer (inline SVG per token)'
|
|
1883
1953
|
: `resolution: off-chain, base ${params.tokenURIBase}`);
|
|
@@ -2240,7 +2310,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2240
2310
|
}, 0) + 1 // + the deploy tx
|
|
2241
2311
|
: 1;
|
|
2242
2312
|
const tokenFields = [];
|
|
2243
|
-
// Collection-scope fields the image lane may add (the O(1)
|
|
2313
|
+
// Collection-scope fields the image lane may add (the O(1) directory image). Kept separate from the
|
|
2244
2314
|
// authorship fields and CONCATENATED at the params site — never assigned over them, or an
|
|
2245
2315
|
// `--creator`/`--license` value would silently vanish whenever pattern 2 is in play.
|
|
2246
2316
|
let imageContractFields = [];
|
|
@@ -2263,13 +2333,13 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2263
2333
|
const uniform = exts[0] !== '' && exts.every((e) => e === exts[0]);
|
|
2264
2334
|
if (dryRun || !backend) {
|
|
2265
2335
|
if (uniform) {
|
|
2266
|
-
imageContractFields = [
|
|
2267
|
-
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image
|
|
2336
|
+
imageContractFields = [imageLocatorField(backendId, `<${backendId}-dir>/{id}${exts[0]}`, true)];
|
|
2337
|
+
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image <${backendId}-dir>/{id}${exts[0]} (O(1); ${describeLocatorField(backendId)})`);
|
|
2268
2338
|
}
|
|
2269
2339
|
else {
|
|
2270
2340
|
for (const { tokenId } of tokenPaths)
|
|
2271
|
-
tokenFields.push(tokenFieldOf(tokenId,
|
|
2272
|
-
info(`would upload ${tokenPaths.length} file(s) to ${backendId} → per-id
|
|
2341
|
+
tokenFields.push(tokenFieldOf(tokenId, imageLocatorField(backendId, `<${backendId}-url-${tokenId}>`)));
|
|
2342
|
+
info(`would upload ${tokenPaths.length} file(s) to ${backendId} → per-id image fields (mixed extensions, O(N); ${describeLocatorField(backendId)})`);
|
|
2273
2343
|
}
|
|
2274
2344
|
}
|
|
2275
2345
|
else if (uniform && backend.putDirectory) {
|
|
@@ -2277,9 +2347,9 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2277
2347
|
const entries = tokenPaths.map(({ tokenId, path }) => ({ name: `${tokenId}${exts[0]}`, bytes: new Uint8Array(readFileSync(path)), contentType: contentTypeFromPath(path) }));
|
|
2278
2348
|
const { base } = await backend.putDirectory(entries);
|
|
2279
2349
|
const template = `${base}/{id}${exts[0]}`;
|
|
2280
|
-
imageContractFields = [
|
|
2350
|
+
imageContractFields = [imageLocatorField(backendId, template, true)];
|
|
2281
2351
|
info(`uploaded ${entries.length} file(s) as one ${backendId} directory`);
|
|
2282
|
-
info(`collection image
|
|
2352
|
+
info(`collection image: ${template} ${dim(`— one field covers every id (O(1); ${describeLocatorField(backendId)})`)}`);
|
|
2283
2353
|
}
|
|
2284
2354
|
else {
|
|
2285
2355
|
// mixed extensions (or no directory support): per-id url fields (O(N)).
|
|
@@ -2290,10 +2360,10 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2290
2360
|
const locator = (await backend.locator?.(hash)) ?? '';
|
|
2291
2361
|
if (!locator)
|
|
2292
2362
|
throw new Error(`backend ${backendId} returned no public locator for id ${tokenId} — a durable backend (ipfs/arweave/cloud) is required for --onchain-uri image hosting`);
|
|
2293
|
-
tokenFields.push(tokenFieldOf(tokenId,
|
|
2363
|
+
tokenFields.push(tokenFieldOf(tokenId, imageLocatorField(backendId, locator)));
|
|
2294
2364
|
info(`id ${tokenId} ← ${fname} → ${locator}`);
|
|
2295
2365
|
}
|
|
2296
|
-
info(`mixed file extensions → per-id
|
|
2366
|
+
info(`mixed file extensions → per-id image fields (O(N)); a uniform extension enables the O(1) directory template`);
|
|
2297
2367
|
}
|
|
2298
2368
|
if (seriesTraitsOnchain)
|
|
2299
2369
|
for (const [tokenId, attrs] of seriesTraits)
|
|
@@ -2349,6 +2419,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2349
2419
|
if (!onchainImage)
|
|
2350
2420
|
await buildFields();
|
|
2351
2421
|
const buildForDeployer = async (deployer) => {
|
|
2422
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
2352
2423
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
2353
2424
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
2354
2425
|
const params = {
|
|
@@ -2373,7 +2444,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2373
2444
|
tokenFields,
|
|
2374
2445
|
// Concatenate — the image lane's collection-scope url-template rides ALONGSIDE the authorship
|
|
2375
2446
|
// fields. Assigning either over the other would silently drop the creator's --creator/--license.
|
|
2376
|
-
contractFields: [...authorshipContractFields(flags), ...imageContractFields],
|
|
2447
|
+
contractFields: [...authorshipContractFields(flags), ...gatewayContractFields(flags), ...imageContractFields],
|
|
2377
2448
|
};
|
|
2378
2449
|
return { clone, params, salt };
|
|
2379
2450
|
};
|
|
@@ -2738,6 +2809,11 @@ export const DEPLOY_CODE_FLAGS = new Set([
|
|
|
2738
2809
|
// Storage + signing + preview flags shared by every deploy path (mirrors storageOverrides() + the lanes).
|
|
2739
2810
|
export const SHARED_DEPLOY_FLAGS = [
|
|
2740
2811
|
'backend', 'endpoint', 'bucket', 'region', 'prefix', 'public-base', 'gateway', 'mode', 'api-url', 'upload-url', 'provider', 'storage-signer',
|
|
2812
|
+
// The collection's PREFERRED SERVING gateways, written on chain (`abx_gateway_ipfs` /
|
|
2813
|
+
// `abx_gateway_arweave`) and repointable forever with `abx set-gateway`. Distinct from `--gateway`,
|
|
2814
|
+
// which is where the CLI uploads and probes; `--gateway` seeds these for the backend in use, and
|
|
2815
|
+
// these override it when the two should differ. Every deploy path takes them.
|
|
2816
|
+
'ipfs-gateway', 'arweave-gateway',
|
|
2741
2817
|
'sign', 'unsigned', 'for', 'sign-url-file', 'dry-run', 'confirm', 'yes', 'salt', 'factory', 'bootstrap-factory', 'port', 'renderer', 'public-base-url',
|
|
2742
2818
|
'721c', // opt-in ERC-721C enrollment (recommended | 0x…) — every deploy path takes it
|
|
2743
2819
|
'json', // B19: the deployed ADDRESS as data, narration to stderr — every deploy path takes it
|
|
@@ -3006,11 +3082,9 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3006
3082
|
}
|
|
3007
3083
|
else if (scriptPath) {
|
|
3008
3084
|
const source = readFileSync(resolvePath(scriptPath), 'utf8');
|
|
3009
|
-
|
|
3010
|
-
const
|
|
3011
|
-
|
|
3012
|
-
scriptChunks.push(toHex(bytes.slice(off, off + CHUNK)));
|
|
3013
|
-
contentSummary = `script ${bytes.length} bytes → ${scriptChunks.length} on-chain chunk(s)`;
|
|
3085
|
+
scriptChunks = planOnChainScript(source);
|
|
3086
|
+
const bytes = new TextEncoder().encode(source).length;
|
|
3087
|
+
contentSummary = `script ${bytes} bytes → ${scriptChunks.length} on-chain chunk(s)`;
|
|
3014
3088
|
ok(contentSummary);
|
|
3015
3089
|
scriptAnalysis = analyzeScript(source, deps.map((d) => d.display)); // traits + PostParam reads + doc size → the Surfaces disposition below
|
|
3016
3090
|
}
|
|
@@ -3042,7 +3116,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3042
3116
|
identityFields.push('external_url');
|
|
3043
3117
|
}
|
|
3044
3118
|
// authorship + rights — same on-chain inline collection-field slot (creator / license / …).
|
|
3045
|
-
contractFields.push(...authorshipContractFields(flags));
|
|
3119
|
+
contractFields.push(...authorshipContractFields(flags), ...gatewayContractFields(flags));
|
|
3046
3120
|
for (const [flag, field] of AUTHORSHIP_DEPLOY_FIELDS)
|
|
3047
3121
|
if (flags[flag])
|
|
3048
3122
|
identityFields.push(field);
|
|
@@ -3243,7 +3317,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3243
3317
|
const undeclaredParams = (scriptAnalysis?.paramHints ?? []).filter((k) => !declaredSchemaKeys.has(k));
|
|
3244
3318
|
const paramsDisposition = scriptAnalysis
|
|
3245
3319
|
? undeclaredParams.length
|
|
3246
|
-
? `⚠ the script READS ${undeclaredParams.join(', ')} but ${undeclaredParams.length === 1 ? "it isn't" : "they aren't"} in --schema → dropped at render (default used). Add --schema ${undeclaredParams.map((k) => `${k}:<Type>:<Auth>`).join(',')} (a palette collectors set = HexColor:TokenOwner)`
|
|
3320
|
+
? `⚠ the script READS ${undeclaredParams.join(', ')} but ${undeclaredParams.length === 1 ? "it isn't" : "they aren't"} in --schema → dropped at render (default used) UNLESS an augment hook supplies ${undeclaredParams.length === 1 ? 'it' : 'them'}. Add --schema ${undeclaredParams.map((k) => `${k}:<Type>:<Auth>`).join(',')} only for collector/creator inputs (a palette collectors set = HexColor:TokenOwner); ignore this for keys your augment hook derives`
|
|
3247
3321
|
: schemas.length
|
|
3248
3322
|
? `${schemas.length} declared: ${schemaSummary}`
|
|
3249
3323
|
: 'none (the script reads no collector params)'
|
|
@@ -3380,7 +3454,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3380
3454
|
info(` ${g('fully on-chain')} — the whole tokenURI (name + image SVG + traits) is computed on-chain; zero dependency outside the EVM ${dim('(abx verify confirms from chain)')}`);
|
|
3381
3455
|
}
|
|
3382
3456
|
else {
|
|
3383
|
-
info(` directory branch: no-server, not chain-complete — liveness rides the gateway (
|
|
3457
|
+
info(` directory branch: no-server, not chain-complete — liveness rides the gateway (public default; ${bold('abx set-gateway')} repoints it), and the params ride the URL (8KB budget — abx verify reports urlOverBudget).`);
|
|
3384
3458
|
}
|
|
3385
3459
|
// A resolver is a "kinder serving path" ONLY when there's a live view / off-chain still to
|
|
3386
3460
|
// serve — i.e. a program. The in-chain renderer-only lane has neither, so don't imply you
|
|
@@ -3446,12 +3520,27 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3446
3520
|
if (!imageOrphaned && !hasImageRenderer) {
|
|
3447
3521
|
if (!hasPublicUrl)
|
|
3448
3522
|
info(` ${dim('render mode (a still is never on-chain):')} ${bold('service')} (auto-render every mint/param change — a live/for-sale drop) · ${bold('once')} (fixed supply) · ${bold('none')} (placeholder). ${dim('freshness: no resolver ⇒ no chain-watcher ⇒ MANUAL/backfill (`abx render` after mints + each param change; the on-chain animation updates live, the bucket still does NOT). Continuous/live stills ⇒ run a resolver.')}`);
|
|
3449
|
-
//
|
|
3450
|
-
//
|
|
3451
|
-
|
|
3523
|
+
// A managed provider whose descriptor says `render.attached` owns rendering — local
|
|
3524
|
+
// ABX_STORAGE_BACKEND is irrelevant, and warning "placeholder forever" is a lie.
|
|
3525
|
+
let renderAttached = false;
|
|
3526
|
+
if (hasPublicUrl && !loopbackBaseUrl(baseUrl)) {
|
|
3527
|
+
try {
|
|
3528
|
+
const d = await new AbxServiceClient({ baseUrl, timeoutMs: 2_500 }).descriptor();
|
|
3529
|
+
renderAttached = !!d.render?.attached;
|
|
3530
|
+
}
|
|
3531
|
+
catch { /* advisory — a down/non-ABX URL must not fail the dry run */ }
|
|
3532
|
+
}
|
|
3533
|
+
if (renderAttached) {
|
|
3534
|
+
info(` ${dim('render storage home:')} ${bold('managed by the resolver')} ${dim(`(${baseUrl}) — local ABX_STORAGE_BACKEND is irrelevant; skip abx deploy-effects`)}`);
|
|
3535
|
+
}
|
|
3536
|
+
else if (renderHome === 'fs') {
|
|
3537
|
+
// The fs render-home footgun bites the RESOLVER lane hardest (a hosted resolver can't read your
|
|
3538
|
+
// laptop disk → placeholder on OpenSea), so warn on BOTH lanes and name the concrete fix.
|
|
3452
3539
|
warn(` render storage home is ${bold('fs')} — LOCAL to this machine, so ${hasPublicUrl ? 'a hosted resolver' : 'a marketplace'} can't read the rendered stills → placeholder forever. FIX before you render: ${bold('set ABX_STORAGE_BACKEND=arweave|s3|ipfs')} in .env (a PUBLIC home the runner uploads to).`);
|
|
3453
|
-
|
|
3540
|
+
}
|
|
3541
|
+
else {
|
|
3454
3542
|
info(` ${dim('render storage home:')} ${bold(renderHome)} ${dim('— a public home the runner uploads to ✓')}`);
|
|
3543
|
+
}
|
|
3455
3544
|
}
|
|
3456
3545
|
(traitsBroken ? warn : info)(`traits: ${traitsDisposition}`);
|
|
3457
3546
|
const paramsNudge = !scriptAnalysis && !schemas.length && (hasImageRenderer || hasAttributesRenderer);
|
|
@@ -3467,7 +3556,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3467
3556
|
// A dropped param is its own, milder problem: the piece renders, that input just takes its
|
|
3468
3557
|
// default. Kept separate from the broken-surface alarm above (see the note there).
|
|
3469
3558
|
if (undeclaredParams.length)
|
|
3470
|
-
info(` ${dim('dropped params render with their defaults — declare
|
|
3559
|
+
info(` ${dim('dropped params render with their defaults — declare collector/creator inputs with')} ${bold('--schema <key>:<Type>:<Auth>')} ${dim('(skip this for keys an augment hook supplies)')}`);
|
|
3471
3560
|
// The runner/verify line is for lanes that need an off-chain STILL. An on-chain image renderer has
|
|
3472
3561
|
// no still to render, host, or refresh — whether or not there's also a program driving
|
|
3473
3562
|
// animation_url — so pointing at a runner and a bucket backend there is simply wrong.
|
|
@@ -3927,7 +4016,11 @@ export const DEPLOY_CODE_EDITION_FLAGS = new Set([
|
|
|
3927
4016
|
// give them otherwise — a real but less helpful refusal (it doesn't say WHY, or the way out).
|
|
3928
4017
|
// (`resume` is NOT here: --resume + --copies is refused earlier, at cmdDeployCode's dispatch —
|
|
3929
4018
|
// this function never runs with flags.resume set, so listing it here would just be dead.)
|
|
3930
|
-
'code-dir', '
|
|
4019
|
+
'code-dir', 'image-renderer', 'attributes-renderer', 'image-base', 'no-delegation',
|
|
4020
|
+
// NOT scope cuts: --dep/--dep-registry are fully wired on EditionCode (it inherits the same
|
|
4021
|
+
// Dependencies extension, and the legs ride the same setup multicall — see the Dependencies step
|
|
4022
|
+
// below). They're plain supported flags; `abx help deploy-code` documents them as working.
|
|
4023
|
+
'dep', 'dep-registry',
|
|
3931
4024
|
]);
|
|
3932
4025
|
export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
3933
4026
|
const usage = 'abx deploy-code --copies <n|open> --script <file> --name "Title" --symbol SYM (--public-base-url https://your.resolver.domain | --onchain-uri) ' +
|
|
@@ -4086,18 +4179,14 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4086
4179
|
step('Content');
|
|
4087
4180
|
const scriptPath = flags.script;
|
|
4088
4181
|
const source = readFileSync(resolvePath(scriptPath), 'utf8');
|
|
4089
|
-
const
|
|
4090
|
-
|
|
4091
|
-
const scriptChunks = [];
|
|
4092
|
-
for (let off = 0; off < bytes.length; off += CHUNK)
|
|
4093
|
-
scriptChunks.push(toHex(bytes.slice(off, off + CHUNK)));
|
|
4094
|
-
ok(`script ${bytes.length} bytes → ${scriptChunks.length} on-chain chunk(s)`);
|
|
4182
|
+
const scriptChunks = planOnChainScript(source);
|
|
4183
|
+
ok(`script ${new TextEncoder().encode(source).length} bytes → ${scriptChunks.length} on-chain chunk(s)`);
|
|
4095
4184
|
const contractFields = [];
|
|
4096
4185
|
if (flags.description)
|
|
4097
4186
|
contractFields.push({ field: encodeTag(F.description), representation: encodeTag(R.inline), value: toHex(String(flags.description)) });
|
|
4098
4187
|
if (flags['external-url'])
|
|
4099
4188
|
contractFields.push({ field: encodeTag(F.externalUrl), representation: encodeTag(R.inline), value: toHex(String(flags['external-url'])) });
|
|
4100
|
-
contractFields.push(...authorshipContractFields(flags));
|
|
4189
|
+
contractFields.push(...authorshipContractFields(flags), ...gatewayContractFields(flags));
|
|
4101
4190
|
const onchainUriLegs = onChainUri ? onchainUriSetupCalls({ generator, metadataRenderer }) : [];
|
|
4102
4191
|
step('Deploy');
|
|
4103
4192
|
const lane = laneFromFlags(flags);
|
|
@@ -4179,7 +4268,11 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4179
4268
|
const depLegCount = dependencySetupCalls(deps, depRegistry).length;
|
|
4180
4269
|
const approvals = 1 + (scriptChunks.length + schemas.length + depLegCount + onchainUriLegs.length + mintCount > 0 ? 1 : 0);
|
|
4181
4270
|
if (!dryRun) {
|
|
4182
|
-
await confirmSend(
|
|
4271
|
+
await confirmSend(
|
|
4272
|
+
// Second site of the same missing binding as the plan line above — and the worse one: this is
|
|
4273
|
+
// the REAL deploy path, so `deploy-code --copies` threw at the confirmation prompt, after the
|
|
4274
|
+
// wallet and factory work was already done.
|
|
4275
|
+
`About to deploy an edition code project "${name}" (${symbol}) — script ${Buffer.byteLength(source, 'utf8')} bytes → ${scriptChunks.length} chunk(s); ` +
|
|
4183
4276
|
`up to ${max} id(s) × ${editionSize === 0n ? 'open' : editionSize.toString()} cop${editionSize === 1n ? 'y' : 'ies'} each; ` +
|
|
4184
4277
|
`${onChainUri ? `tokenURI ON-CHAIN via the renderer (generator ${generator})` : `resolver base ${baseUrl}`}; ` +
|
|
4185
4278
|
`mint: ${mintCount > 0 ? `${mintCount} id(s) × ${mintAmount} at deploy` : 'deferred'}; approvals: ${approvals} wallet approval(s); owner+royalty: your wallet @ ${royaltyBps / 100}%.` +
|
|
@@ -4208,7 +4301,10 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4208
4301
|
if (shownAddr)
|
|
4209
4302
|
info(`deterministic address: ${shownAddr}`);
|
|
4210
4303
|
info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}%`);
|
|
4211
|
-
|
|
4304
|
+
// `source` is what this lane read; there has never been a `bytes` binding here, so this line
|
|
4305
|
+
// threw a ReferenceError for every `deploy-code --copies … --dry-run` — the dry run crashed at the
|
|
4306
|
+
// last step of the plan it was printing. Byte length, not UTF-16 code units, matching the 721 twin.
|
|
4307
|
+
info(`content: script ${Buffer.byteLength(source, 'utf8')} bytes → ${scriptChunks.length} on-chain chunk(s)`);
|
|
4212
4308
|
printSeedSourcePlan(seedSource, chainId);
|
|
4213
4309
|
info(`PostParam schema(s): ${schemas.length ? schemas.map((s) => describeSchema(s)).join(', ') : 'none'}`);
|
|
4214
4310
|
info(`mint: ${mintCount > 0 ? `${mintCount} id(s) × ${mintAmount} cop${mintAmount === 1n ? 'y' : 'ies'} → ${deployer ?? 'your wallet'} at deploy` : 'deferred (mint later / external minter)'}`);
|