@artblocks/abx-cli 0.1.0-alpha.23 → 0.1.0-alpha.26
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 +322 -0
- package/dist/commands/deploy.d.ts +27 -4
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +220 -70
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +15 -0
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +27 -4
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/storage.d.ts +14 -0
- package/dist/commands/storage.d.ts.map +1 -1
- package/dist/commands/storage.js +31 -4
- package/dist/commands/storage.js.map +1 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +20 -1
- package/dist/conformance.js.map +1 -1
- package/dist/flag-allowlists.d.ts.map +1 -1
- package/dist/flag-allowlists.js +4 -0
- package/dist/flag-allowlists.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 +38 -8
- 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 +51 -0
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +152 -5
- package/dist/ownerops.js.map +1 -1
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +6 -1
- package/dist/provision.js.map +1 -1
- package/dist/remote.d.ts +20 -0
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +38 -0
- package/dist/remote.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/dist/signer.d.ts.map +1 -1
- package/dist/signer.js +4 -1
- package/dist/signer.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +18 -8
- package/skill/reference/code-projects.md +3 -3
- package/skill/reference/decisions.md +3 -3
- package/skill/reference/hosting.md +15 -4
- package/skill/reference/operating.md +27 -1
- 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, servingGateway, 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,82 @@ 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
|
+
/**
|
|
360
|
+
* The URL a creator's token will actually carry, for a locator a durable backend just produced.
|
|
361
|
+
*
|
|
362
|
+
* For `ipfs`/`arweave` the backend's locator is an UPLOAD-gateway URL, and the on-chain field holds
|
|
363
|
+
* only the bare CID/txid — so narrating the locator verbatim told the creator a host their token
|
|
364
|
+
* does not use (a live deploy printed `gateway.pinata.cloud/…` while committing a CID served from
|
|
365
|
+
* `ipfs.io`). Rebuild it under the serving preference instead, and say which one that is, since
|
|
366
|
+
* "the floor is filling a silence" and "you chose this" have different futures.
|
|
367
|
+
*
|
|
368
|
+
* Every other backend is returned verbatim: an https CDN locator IS the address.
|
|
369
|
+
*/
|
|
370
|
+
function servedImageUrl(backendId, locator, flags) {
|
|
371
|
+
if (backendId !== 'ipfs' && backendId !== 'arweave')
|
|
372
|
+
return { url: locator, note: '' };
|
|
373
|
+
const contentId = contentIdFromLocator(backendId, locator);
|
|
374
|
+
if (!contentId)
|
|
375
|
+
return { url: locator, note: '' };
|
|
376
|
+
const { prefix, chosen } = servingGateway(backendId, flags);
|
|
377
|
+
return {
|
|
378
|
+
url: `${prefix}${contentId}`,
|
|
379
|
+
note: chosen ? `gateway: yours, written on-chain` : `gateway: public default — repoint any time with ${bold('abx set-gateway')}`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/** How a lane should describe what it just committed, so the narration matches the field written. */
|
|
383
|
+
function describeLocatorField(backendId) {
|
|
384
|
+
return backendId === 'ipfs' || backendId === 'arweave'
|
|
385
|
+
? `ON-CHAIN image as a bare ${backendId} id — the gateway is the collection's preference, repointable with ${bold('abx set-gateway')}`
|
|
386
|
+
: 'ON-CHAIN image url (the locator IS the address)';
|
|
387
|
+
}
|
|
388
|
+
function imageLocatorField(backendId, locator, isTemplate = false) {
|
|
389
|
+
const network = backendId === 'ipfs' || backendId === 'arweave' ? backendId : null;
|
|
390
|
+
if (network) {
|
|
391
|
+
const contentId = contentIdFromLocator(network, locator);
|
|
392
|
+
if (contentId)
|
|
393
|
+
return imageContentAddressedField(network, contentId);
|
|
394
|
+
// A subdomain-style gateway (`https://<cid>.ipfs.dweb.link`), which ABX's own backends never
|
|
395
|
+
// produce. Degrade to the old shape rather than guess at the CID — and say so, because the
|
|
396
|
+
// consequence (a welded host) is exactly what the caller thought they were avoiding.
|
|
397
|
+
warn(`could not recover the ${network} content id from ${locator} — writing the gateway URL as a plain ` +
|
|
398
|
+
`'url' field instead. That URL's host is then part of the on-chain value: a gateway change ` +
|
|
399
|
+
`needs ${bold('abx set-field')}, not ${bold('abx set-gateway')}.`);
|
|
400
|
+
}
|
|
401
|
+
return isTemplate ? imageUrlTemplateField(locator) : imageUrlField(locator);
|
|
402
|
+
}
|
|
317
403
|
export const looksLikeSvg = (s) => /^\s*<(\?xml|svg)/i.test(s);
|
|
318
404
|
/**
|
|
319
405
|
* Does the IMAGE itself end up on-chain for this deploy? Two ways it can:
|
|
@@ -359,7 +445,8 @@ export function imageEndsUpOnChain(flags, onchainImage) {
|
|
|
359
445
|
export async function prepareContent(imagePath, clone, overrides, store = true, // false for --dry-run: compute the hash but don't custody bytes
|
|
360
446
|
onChain = false, // true (--onchain-uri): put the image ON-CHAIN (inline SVG) so it self-resolves
|
|
361
447
|
remoteEth, // wallet-lane Turbo uploads: sign+pay with the connected browser wallet
|
|
362
|
-
walletAddr
|
|
448
|
+
walletAddr, // the deployer wallet — checked for existing Turbo credits if the managed key is short
|
|
449
|
+
gatewayFlags = {}) {
|
|
363
450
|
if (!imagePath) {
|
|
364
451
|
// demo default = generative SVG. On-chain mode inlines it (self-resolving, no custody);
|
|
365
452
|
// off-chain mode commits its keccak and regenerates/serves it.
|
|
@@ -413,8 +500,8 @@ walletAddr) {
|
|
|
413
500
|
const backendId = backendResolution(overrides).backend;
|
|
414
501
|
if (onchainUrlLane) {
|
|
415
502
|
return {
|
|
416
|
-
tokenFields: [
|
|
417
|
-
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) → would upload to ${backendId};
|
|
503
|
+
tokenFields: [imageLocatorField(backendId, `<${backendId}-url>`)],
|
|
504
|
+
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) → would upload to ${backendId}; ${describeLocatorField(backendId)}`,
|
|
418
505
|
};
|
|
419
506
|
}
|
|
420
507
|
return {
|
|
@@ -440,10 +527,13 @@ walletAddr) {
|
|
|
440
527
|
// Keep the bytes under their hash too: `abx verify` can then still prove the served image
|
|
441
528
|
// matches what was uploaded, even though the on-chain field addresses it by URL.
|
|
442
529
|
await backend.put(hash, { bytes, contentType });
|
|
443
|
-
|
|
530
|
+
const served = servedImageUrl(backend.id, url, gatewayFlags);
|
|
531
|
+
ok(`image → ${served.url} ${dim(`(${backend.id}; baked on-chain, no server)`)}`);
|
|
532
|
+
if (served.note)
|
|
533
|
+
info(dim(` ${served.note}`));
|
|
444
534
|
return {
|
|
445
|
-
tokenFields: [
|
|
446
|
-
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) on ${backend.id} · ON-CHAIN image
|
|
535
|
+
tokenFields: [imageLocatorField(backend.id, url)],
|
|
536
|
+
contentNote: `content: ${basename(path)} (${bytes.length} bytes, ${contentType}) on ${backend.id} · ON-CHAIN image ${describeLocatorField(backend.id)}`,
|
|
447
537
|
};
|
|
448
538
|
}
|
|
449
539
|
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).`);
|
|
@@ -475,16 +565,24 @@ export const attributesInlineField = (attrs) => ({
|
|
|
475
565
|
representation: encodeTag(R.inline),
|
|
476
566
|
value: toHex(JSON.stringify(attrs)),
|
|
477
567
|
});
|
|
478
|
-
/**
|
|
479
|
-
*
|
|
480
|
-
*
|
|
568
|
+
/**
|
|
569
|
+
* Warn when IPFS/Arweave custody is pointed at a LOCAL gateway.
|
|
570
|
+
*
|
|
571
|
+
* Two distinct problems ride on one flag, and the warning names the one that actually applies. The
|
|
572
|
+
* bytes are pinned only to a node on this machine, so nothing off it can retrieve them — that is the
|
|
573
|
+
* real damage and it survives any later `set-gateway`. It ALSO used to weld that localhost host into
|
|
574
|
+
* the on-chain value; since the field holds a bare CID it no longer does (the serving prefix is the
|
|
575
|
+
* collection's preference), so promising "the served URL resolves only on THIS machine" would now be
|
|
576
|
+
* false and would point the creator at the wrong repair.
|
|
577
|
+
*/
|
|
481
578
|
export function localGatewayWarning(flags) {
|
|
482
579
|
const opts = storageOptions(storageOverrides(flags));
|
|
483
580
|
const gateway = opts.backend === 'ipfs' ? opts.ipfs?.gateway : opts.backend === 'arweave' ? opts.arweave?.gateway : undefined;
|
|
484
581
|
if (gateway && /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)\b/i.test(gateway)) {
|
|
485
|
-
return (`${opts.backend} gateway is ${gateway} — a LOCAL gateway
|
|
486
|
-
`
|
|
487
|
-
`
|
|
582
|
+
return (`${opts.backend} gateway is ${gateway} — a LOCAL gateway, so the bytes would be pinned only to a node on THIS ` +
|
|
583
|
+
`machine and nobody else could retrieve them. The on-chain field holds the bare content id (a gateway change is ` +
|
|
584
|
+
`later just ${bold('abx set-gateway')}), but no gateway can serve content that was never pinned publicly. Pin to a ` +
|
|
585
|
+
`service with a PUBLIC gateway (e.g. a Pinata dedicated gateway) via --gateway https://… or ABX_IPFS_GATEWAY.`);
|
|
488
586
|
}
|
|
489
587
|
return null;
|
|
490
588
|
}
|
|
@@ -815,6 +913,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
815
913
|
// explicit --salt is given. With the wallet lane the signer is unknown until they
|
|
816
914
|
// connect, so this runs per-signer.
|
|
817
915
|
const buildForDeployer = async (deployer) => {
|
|
916
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
818
917
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
819
918
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
820
919
|
// A baked on-chain image (--onchain-image) replaces prepareContent's image field —
|
|
@@ -825,7 +924,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
825
924
|
? { tokenFields: [bakedImage], contentNote: 'content: image staged ON-CHAIN via reader (self-resolving, no custody)' }
|
|
826
925
|
: onchainImage
|
|
827
926
|
? { tokenFields: [], contentNote: 'content: image would be staged ON-CHAIN via reader (chunk store) — dry run skips the staging write' }
|
|
828
|
-
: await prepareContent(flags.image, clone, storageOverrides(flags), !dryRun, onChainUri, uploadRemoteEth, deployer);
|
|
927
|
+
: await prepareContent(flags.image, clone, storageOverrides(flags), !dryRun, onChainUri, uploadRemoteEth, deployer, flags);
|
|
829
928
|
// Put the description ON-CHAIN (inline) when asked, or always under --onchain-uri
|
|
830
929
|
// (the on-chain renderer can only emit on-chain fields, so an off-chain-only
|
|
831
930
|
// description would simply be omitted). On-chain wins at resolve either way.
|
|
@@ -853,7 +952,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
853
952
|
transferValidator,
|
|
854
953
|
tokenFields,
|
|
855
954
|
// authorship + rights (creator / license / …) are collection-scope on-chain fields.
|
|
856
|
-
contractFields: authorshipContractFields(flags),
|
|
955
|
+
contractFields: [...authorshipContractFields(flags), ...gatewayContractFields(flags)],
|
|
857
956
|
};
|
|
858
957
|
return { clone, params, salt, contentNote };
|
|
859
958
|
};
|
|
@@ -1319,13 +1418,14 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1319
1418
|
info('off-chain fallback pointer left empty (the renderer is authoritative); set --public-base-url to bake one anyway.');
|
|
1320
1419
|
}
|
|
1321
1420
|
const buildForDeployer = async (deployer) => {
|
|
1421
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
1322
1422
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
1323
1423
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
1324
1424
|
const { tokenFields, contentNote } = bakedImage
|
|
1325
1425
|
? { tokenFields: [bakedImage], contentNote: 'content: image staged ON-CHAIN via reader (self-resolving, no custody)' }
|
|
1326
1426
|
: onchainImage
|
|
1327
1427
|
? { tokenFields: [], contentNote: 'content: image would be staged ON-CHAIN via reader (chunk store) — dry run skips the staging write' }
|
|
1328
|
-
: await prepareContent(flags.image, clone, storageOverrides(flags), !dryRun, onChainUri);
|
|
1428
|
+
: await prepareContent(flags.image, clone, storageOverrides(flags), !dryRun, onChainUri, undefined, undefined, flags);
|
|
1329
1429
|
if (flags.description && (onChainUri || flags['description-onchain'])) {
|
|
1330
1430
|
tokenFields.push({ field: encodeTag(F.description), representation: encodeTag(R.inline), value: toHex(flags.description) });
|
|
1331
1431
|
}
|
|
@@ -1349,7 +1449,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1349
1449
|
minter,
|
|
1350
1450
|
paused,
|
|
1351
1451
|
tokenFields,
|
|
1352
|
-
contractFields: authorshipContractFields(flags),
|
|
1452
|
+
contractFields: [...authorshipContractFields(flags), ...gatewayContractFields(flags)],
|
|
1353
1453
|
};
|
|
1354
1454
|
return { clone, params, salt, contentNote };
|
|
1355
1455
|
};
|
|
@@ -1659,7 +1759,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1659
1759
|
// Image custody × URI resolution (see reference/hosting.md):
|
|
1660
1760
|
// - --onchain-image → chunk store (on-chain bytes), JSON on-chain
|
|
1661
1761
|
// - --onchain-uri + a durable backend → image OFF-CHAIN (ipfs/arweave), JSON on-chain via the
|
|
1662
|
-
// (ipfs/arweave) renderer: ONE collection
|
|
1762
|
+
// (ipfs/arweave) renderer: ONE collection-scope image field when files share
|
|
1663
1763
|
// an extension (O(1) directory), else per-token `url` (O(N))
|
|
1664
1764
|
// - --onchain-uri alone → inline SVG on-chain
|
|
1665
1765
|
// - neither → off-chain custody (keccak), served by a hosted resolver
|
|
@@ -1694,6 +1794,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1694
1794
|
// early managed-key creation + funds guard and the up-front buildFields; the session branch runs them.
|
|
1695
1795
|
const remoteEthUpload = lane === 'sign' && !onchainImage && !!backend && storageSignerChoice(overrides) === 'eth' && isTurboArweave(opts);
|
|
1696
1796
|
if (backend && !remoteEthUpload) {
|
|
1797
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
1697
1798
|
ensureArweaveIdentityForUpload(opts); // Turbo identity, created on first upload
|
|
1698
1799
|
// The deployer wallet (--for, else the .env key's address) — checked for existing Turbo credits
|
|
1699
1800
|
// if the managed key is short, so we recommend `--storage-signer eth` before any top-up.
|
|
@@ -1733,23 +1834,35 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1733
1834
|
const uniform = exts[0] !== '' && exts.every((e) => e === exts[0]);
|
|
1734
1835
|
if (dryRun || !backend) {
|
|
1735
1836
|
if (uniform) {
|
|
1736
|
-
contractFields = [
|
|
1737
|
-
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image
|
|
1837
|
+
contractFields = [imageLocatorField(backendId, `<${backendId}-dir>/{id}${exts[0]}`, true)];
|
|
1838
|
+
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image <${backendId}-dir>/{id}${exts[0]} (O(1); ${describeLocatorField(backendId)})`);
|
|
1839
|
+
// Name the SERVING prefix in the plan too. Without it a preview says "the gateway is the
|
|
1840
|
+
// collection's preference" and never says what that preference resolves to, so a reader
|
|
1841
|
+
// fills the blank with the upload gateway they just typed — the same confusion the live
|
|
1842
|
+
// narration used to cause outright.
|
|
1843
|
+
if (backendId === 'ipfs' || backendId === 'arweave') {
|
|
1844
|
+
const { prefix, chosen } = servingGateway(backendId, flags);
|
|
1845
|
+
info(`served from ${bold(prefix)} ${dim(chosen ? '(your --' + backendId + '-gateway, written on-chain)' : `(public default — nothing written; repoint any time with ${bold('abx set-gateway')})`)}`);
|
|
1846
|
+
}
|
|
1738
1847
|
}
|
|
1739
1848
|
else {
|
|
1740
|
-
tokenFields = tokenPaths.map(({ tokenId }) => tokenFieldOf(tokenId,
|
|
1849
|
+
tokenFields = tokenPaths.map(({ tokenId }) => tokenFieldOf(tokenId, imageLocatorField(backendId, `<${backendId}-url-${tokenId}>`)));
|
|
1741
1850
|
info(`would upload ${tokenPaths.length} file(s) to ${backendId} → per-token url image fields (mixed extensions, O(N))`);
|
|
1742
1851
|
}
|
|
1743
1852
|
return;
|
|
1744
1853
|
}
|
|
1745
1854
|
if (uniform && backend.putDirectory) {
|
|
1746
|
-
// O(1) directory-base: upload the folder renamed to {id}{ext}; ONE collection
|
|
1855
|
+
// O(1) directory-base: upload the folder renamed to {id}{ext}; ONE collection-scope image
|
|
1856
|
+
// field. `{id}` substitutes at read on every locator representation, `ipfs`/`arweave` included.
|
|
1747
1857
|
const entries = tokenPaths.map(({ tokenId, path }) => ({ name: `${tokenId}${exts[0]}`, bytes: new Uint8Array(readFileSync(path)), contentType: contentTypeFromPath(path) }));
|
|
1748
1858
|
const { base } = await backend.putDirectory(entries);
|
|
1749
1859
|
const template = `${base}/{id}${exts[0]}`;
|
|
1750
|
-
contractFields = [
|
|
1860
|
+
contractFields = [imageLocatorField(backendId, template, true)];
|
|
1751
1861
|
info(`uploaded ${entries.length} file(s) as one ${backendId} directory`);
|
|
1752
|
-
|
|
1862
|
+
const servedTemplate = servedImageUrl(backendId, template, flags);
|
|
1863
|
+
info(`collection image: ${servedTemplate.url} ${dim(`— one field covers every token (O(1); ${describeLocatorField(backendId)})`)}`);
|
|
1864
|
+
if (servedTemplate.note)
|
|
1865
|
+
info(dim(` ${servedTemplate.note}`));
|
|
1753
1866
|
}
|
|
1754
1867
|
else {
|
|
1755
1868
|
// mixed extensions (or no directory support): per-token url fields (O(N)).
|
|
@@ -1760,10 +1873,10 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1760
1873
|
const locator = (await backend.locator?.(hash)) ?? '';
|
|
1761
1874
|
if (!locator)
|
|
1762
1875
|
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,
|
|
1764
|
-
info(`token ${tokenId} ← ${fname} → ${locator}`);
|
|
1876
|
+
tokenFields.push(tokenFieldOf(tokenId, imageLocatorField(backendId, locator)));
|
|
1877
|
+
info(`token ${tokenId} ← ${fname} → ${servedImageUrl(backendId, locator, flags).url}`);
|
|
1765
1878
|
}
|
|
1766
|
-
info(`mixed file extensions → per-token
|
|
1879
|
+
info(`mixed file extensions → per-token image fields (O(N)); a uniform extension enables the O(1) directory template`);
|
|
1767
1880
|
}
|
|
1768
1881
|
return;
|
|
1769
1882
|
}
|
|
@@ -1819,6 +1932,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1819
1932
|
// Assemble the init params for a given deployer (owner/royalty/mintTo = deployer). The clone
|
|
1820
1933
|
// address is deterministic in (factory, salt) — so URIs are baked before the tx is signed.
|
|
1821
1934
|
const buildForDeployer = async (deployer) => {
|
|
1935
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
1822
1936
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
1823
1937
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
1824
1938
|
const params = {
|
|
@@ -1839,8 +1953,8 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1839
1953
|
mintTo: mintCount > 0 ? deployer : zeroAddress,
|
|
1840
1954
|
mintCount,
|
|
1841
1955
|
tokenFields,
|
|
1842
|
-
// image contract fields (the O(1)
|
|
1843
|
-
contractFields: [...contractFields, ...authorshipContractFields(flags)],
|
|
1956
|
+
// image contract fields (the O(1) collection-scope image) + authorship/rights (creator / license / …).
|
|
1957
|
+
contractFields: [...contractFields, ...authorshipContractFields(flags), ...gatewayContractFields(flags)],
|
|
1844
1958
|
};
|
|
1845
1959
|
return { clone, params, salt };
|
|
1846
1960
|
};
|
|
@@ -1877,7 +1991,9 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
1877
1991
|
info(onchainImage
|
|
1878
1992
|
? 'resolution: ON-CHAIN via the renderer (chunked reader per token — image bytes on-chain)'
|
|
1879
1993
|
: offchainImageOnchainJson
|
|
1880
|
-
|
|
1994
|
+
// Say which SHAPE landed, not a fixed representation name: since v11 a content-addressed
|
|
1995
|
+
// backend writes an `ipfs`/`arweave` field, not `url`/`url-template`.
|
|
1996
|
+
? `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
1997
|
: onChainUri
|
|
1882
1998
|
? 'resolution: ON-CHAIN via the renderer (inline SVG per token)'
|
|
1883
1999
|
: `resolution: off-chain, base ${params.tokenURIBase}`);
|
|
@@ -2240,7 +2356,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2240
2356
|
}, 0) + 1 // + the deploy tx
|
|
2241
2357
|
: 1;
|
|
2242
2358
|
const tokenFields = [];
|
|
2243
|
-
// Collection-scope fields the image lane may add (the O(1)
|
|
2359
|
+
// Collection-scope fields the image lane may add (the O(1) directory image). Kept separate from the
|
|
2244
2360
|
// authorship fields and CONCATENATED at the params site — never assigned over them, or an
|
|
2245
2361
|
// `--creator`/`--license` value would silently vanish whenever pattern 2 is in play.
|
|
2246
2362
|
let imageContractFields = [];
|
|
@@ -2263,13 +2379,21 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2263
2379
|
const uniform = exts[0] !== '' && exts.every((e) => e === exts[0]);
|
|
2264
2380
|
if (dryRun || !backend) {
|
|
2265
2381
|
if (uniform) {
|
|
2266
|
-
imageContractFields = [
|
|
2267
|
-
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image
|
|
2382
|
+
imageContractFields = [imageLocatorField(backendId, `<${backendId}-dir>/{id}${exts[0]}`, true)];
|
|
2383
|
+
info(`would upload ${tokenPaths.length} file(s) as ONE ${backendId} directory → collection image <${backendId}-dir>/{id}${exts[0]} (O(1); ${describeLocatorField(backendId)})`);
|
|
2384
|
+
// Name the SERVING prefix in the plan too. Without it a preview says "the gateway is the
|
|
2385
|
+
// collection's preference" and never says what that preference resolves to, so a reader
|
|
2386
|
+
// fills the blank with the upload gateway they just typed — the same confusion the live
|
|
2387
|
+
// narration used to cause outright.
|
|
2388
|
+
if (backendId === 'ipfs' || backendId === 'arweave') {
|
|
2389
|
+
const { prefix, chosen } = servingGateway(backendId, flags);
|
|
2390
|
+
info(`served from ${bold(prefix)} ${dim(chosen ? '(your --' + backendId + '-gateway, written on-chain)' : `(public default — nothing written; repoint any time with ${bold('abx set-gateway')})`)}`);
|
|
2391
|
+
}
|
|
2268
2392
|
}
|
|
2269
2393
|
else {
|
|
2270
2394
|
for (const { tokenId } of tokenPaths)
|
|
2271
|
-
tokenFields.push(tokenFieldOf(tokenId,
|
|
2272
|
-
info(`would upload ${tokenPaths.length} file(s) to ${backendId} → per-id
|
|
2395
|
+
tokenFields.push(tokenFieldOf(tokenId, imageLocatorField(backendId, `<${backendId}-url-${tokenId}>`)));
|
|
2396
|
+
info(`would upload ${tokenPaths.length} file(s) to ${backendId} → per-id image fields (mixed extensions, O(N); ${describeLocatorField(backendId)})`);
|
|
2273
2397
|
}
|
|
2274
2398
|
}
|
|
2275
2399
|
else if (uniform && backend.putDirectory) {
|
|
@@ -2277,9 +2401,9 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2277
2401
|
const entries = tokenPaths.map(({ tokenId, path }) => ({ name: `${tokenId}${exts[0]}`, bytes: new Uint8Array(readFileSync(path)), contentType: contentTypeFromPath(path) }));
|
|
2278
2402
|
const { base } = await backend.putDirectory(entries);
|
|
2279
2403
|
const template = `${base}/{id}${exts[0]}`;
|
|
2280
|
-
imageContractFields = [
|
|
2404
|
+
imageContractFields = [imageLocatorField(backendId, template, true)];
|
|
2281
2405
|
info(`uploaded ${entries.length} file(s) as one ${backendId} directory`);
|
|
2282
|
-
info(`collection image
|
|
2406
|
+
info(`collection image: ${template} ${dim(`— one field covers every id (O(1); ${describeLocatorField(backendId)})`)}`);
|
|
2283
2407
|
}
|
|
2284
2408
|
else {
|
|
2285
2409
|
// mixed extensions (or no directory support): per-id url fields (O(N)).
|
|
@@ -2290,10 +2414,10 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2290
2414
|
const locator = (await backend.locator?.(hash)) ?? '';
|
|
2291
2415
|
if (!locator)
|
|
2292
2416
|
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,
|
|
2417
|
+
tokenFields.push(tokenFieldOf(tokenId, imageLocatorField(backendId, locator)));
|
|
2294
2418
|
info(`id ${tokenId} ← ${fname} → ${locator}`);
|
|
2295
2419
|
}
|
|
2296
|
-
info(`mixed file extensions → per-id
|
|
2420
|
+
info(`mixed file extensions → per-id image fields (O(N)); a uniform extension enables the O(1) directory template`);
|
|
2297
2421
|
}
|
|
2298
2422
|
if (seriesTraitsOnchain)
|
|
2299
2423
|
for (const [tokenId, attrs] of seriesTraits)
|
|
@@ -2349,6 +2473,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2349
2473
|
if (!onchainImage)
|
|
2350
2474
|
await buildFields();
|
|
2351
2475
|
const buildForDeployer = async (deployer) => {
|
|
2476
|
+
assertLaneCanSign(flags); // no upload before we know this run can be signed — see `assertLaneCanSign`
|
|
2352
2477
|
const salt = explicitSalt ?? saltFor(deployer);
|
|
2353
2478
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
2354
2479
|
const params = {
|
|
@@ -2373,7 +2498,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2373
2498
|
tokenFields,
|
|
2374
2499
|
// Concatenate — the image lane's collection-scope url-template rides ALONGSIDE the authorship
|
|
2375
2500
|
// fields. Assigning either over the other would silently drop the creator's --creator/--license.
|
|
2376
|
-
contractFields: [...authorshipContractFields(flags), ...imageContractFields],
|
|
2501
|
+
contractFields: [...authorshipContractFields(flags), ...gatewayContractFields(flags), ...imageContractFields],
|
|
2377
2502
|
};
|
|
2378
2503
|
return { clone, params, salt };
|
|
2379
2504
|
};
|
|
@@ -2738,6 +2863,11 @@ export const DEPLOY_CODE_FLAGS = new Set([
|
|
|
2738
2863
|
// Storage + signing + preview flags shared by every deploy path (mirrors storageOverrides() + the lanes).
|
|
2739
2864
|
export const SHARED_DEPLOY_FLAGS = [
|
|
2740
2865
|
'backend', 'endpoint', 'bucket', 'region', 'prefix', 'public-base', 'gateway', 'mode', 'api-url', 'upload-url', 'provider', 'storage-signer',
|
|
2866
|
+
// The collection's PREFERRED SERVING gateways, written on chain (`abx_gateway_ipfs` /
|
|
2867
|
+
// `abx_gateway_arweave`) and repointable forever with `abx set-gateway`. Distinct from `--gateway`,
|
|
2868
|
+
// which is where the CLI uploads and probes; `--gateway` seeds these for the backend in use, and
|
|
2869
|
+
// these override it when the two should differ. Every deploy path takes them.
|
|
2870
|
+
'ipfs-gateway', 'arweave-gateway',
|
|
2741
2871
|
'sign', 'unsigned', 'for', 'sign-url-file', 'dry-run', 'confirm', 'yes', 'salt', 'factory', 'bootstrap-factory', 'port', 'renderer', 'public-base-url',
|
|
2742
2872
|
'721c', // opt-in ERC-721C enrollment (recommended | 0x…) — every deploy path takes it
|
|
2743
2873
|
'json', // B19: the deployed ADDRESS as data, narration to stderr — every deploy path takes it
|
|
@@ -3006,11 +3136,9 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3006
3136
|
}
|
|
3007
3137
|
else if (scriptPath) {
|
|
3008
3138
|
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)`;
|
|
3139
|
+
scriptChunks = planOnChainScript(source);
|
|
3140
|
+
const bytes = new TextEncoder().encode(source).length;
|
|
3141
|
+
contentSummary = `script ${bytes} bytes → ${scriptChunks.length} on-chain chunk(s)`;
|
|
3014
3142
|
ok(contentSummary);
|
|
3015
3143
|
scriptAnalysis = analyzeScript(source, deps.map((d) => d.display)); // traits + PostParam reads + doc size → the Surfaces disposition below
|
|
3016
3144
|
}
|
|
@@ -3042,7 +3170,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3042
3170
|
identityFields.push('external_url');
|
|
3043
3171
|
}
|
|
3044
3172
|
// authorship + rights — same on-chain inline collection-field slot (creator / license / …).
|
|
3045
|
-
contractFields.push(...authorshipContractFields(flags));
|
|
3173
|
+
contractFields.push(...authorshipContractFields(flags), ...gatewayContractFields(flags));
|
|
3046
3174
|
for (const [flag, field] of AUTHORSHIP_DEPLOY_FIELDS)
|
|
3047
3175
|
if (flags[flag])
|
|
3048
3176
|
identityFields.push(field);
|
|
@@ -3243,7 +3371,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3243
3371
|
const undeclaredParams = (scriptAnalysis?.paramHints ?? []).filter((k) => !declaredSchemaKeys.has(k));
|
|
3244
3372
|
const paramsDisposition = scriptAnalysis
|
|
3245
3373
|
? 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)`
|
|
3374
|
+
? `⚠ 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
3375
|
: schemas.length
|
|
3248
3376
|
? `${schemas.length} declared: ${schemaSummary}`
|
|
3249
3377
|
: 'none (the script reads no collector params)'
|
|
@@ -3380,7 +3508,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3380
3508
|
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
3509
|
}
|
|
3382
3510
|
else {
|
|
3383
|
-
info(` directory branch: no-server, not chain-complete — liveness rides the gateway (
|
|
3511
|
+
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
3512
|
}
|
|
3385
3513
|
// A resolver is a "kinder serving path" ONLY when there's a live view / off-chain still to
|
|
3386
3514
|
// serve — i.e. a program. The in-chain renderer-only lane has neither, so don't imply you
|
|
@@ -3446,12 +3574,27 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3446
3574
|
if (!imageOrphaned && !hasImageRenderer) {
|
|
3447
3575
|
if (!hasPublicUrl)
|
|
3448
3576
|
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
|
-
|
|
3577
|
+
// A managed provider whose descriptor says `render.attached` owns rendering — local
|
|
3578
|
+
// ABX_STORAGE_BACKEND is irrelevant, and warning "placeholder forever" is a lie.
|
|
3579
|
+
let renderAttached = false;
|
|
3580
|
+
if (hasPublicUrl && !loopbackBaseUrl(baseUrl)) {
|
|
3581
|
+
try {
|
|
3582
|
+
const d = await new AbxServiceClient({ baseUrl, timeoutMs: 2_500 }).descriptor();
|
|
3583
|
+
renderAttached = !!d.render?.attached;
|
|
3584
|
+
}
|
|
3585
|
+
catch { /* advisory — a down/non-ABX URL must not fail the dry run */ }
|
|
3586
|
+
}
|
|
3587
|
+
if (renderAttached) {
|
|
3588
|
+
info(` ${dim('render storage home:')} ${bold('managed by the resolver')} ${dim(`(${baseUrl}) — local ABX_STORAGE_BACKEND is irrelevant; skip abx deploy-effects`)}`);
|
|
3589
|
+
}
|
|
3590
|
+
else if (renderHome === 'fs') {
|
|
3591
|
+
// The fs render-home footgun bites the RESOLVER lane hardest (a hosted resolver can't read your
|
|
3592
|
+
// laptop disk → placeholder on OpenSea), so warn on BOTH lanes and name the concrete fix.
|
|
3452
3593
|
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
|
-
|
|
3594
|
+
}
|
|
3595
|
+
else {
|
|
3454
3596
|
info(` ${dim('render storage home:')} ${bold(renderHome)} ${dim('— a public home the runner uploads to ✓')}`);
|
|
3597
|
+
}
|
|
3455
3598
|
}
|
|
3456
3599
|
(traitsBroken ? warn : info)(`traits: ${traitsDisposition}`);
|
|
3457
3600
|
const paramsNudge = !scriptAnalysis && !schemas.length && (hasImageRenderer || hasAttributesRenderer);
|
|
@@ -3467,7 +3610,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3467
3610
|
// A dropped param is its own, milder problem: the piece renders, that input just takes its
|
|
3468
3611
|
// default. Kept separate from the broken-surface alarm above (see the note there).
|
|
3469
3612
|
if (undeclaredParams.length)
|
|
3470
|
-
info(` ${dim('dropped params render with their defaults — declare
|
|
3613
|
+
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
3614
|
// The runner/verify line is for lanes that need an off-chain STILL. An on-chain image renderer has
|
|
3472
3615
|
// no still to render, host, or refresh — whether or not there's also a program driving
|
|
3473
3616
|
// animation_url — so pointing at a runner and a bucket backend there is simply wrong.
|
|
@@ -3927,7 +4070,11 @@ export const DEPLOY_CODE_EDITION_FLAGS = new Set([
|
|
|
3927
4070
|
// give them otherwise — a real but less helpful refusal (it doesn't say WHY, or the way out).
|
|
3928
4071
|
// (`resume` is NOT here: --resume + --copies is refused earlier, at cmdDeployCode's dispatch —
|
|
3929
4072
|
// this function never runs with flags.resume set, so listing it here would just be dead.)
|
|
3930
|
-
'code-dir', '
|
|
4073
|
+
'code-dir', 'image-renderer', 'attributes-renderer', 'image-base', 'no-delegation',
|
|
4074
|
+
// NOT scope cuts: --dep/--dep-registry are fully wired on EditionCode (it inherits the same
|
|
4075
|
+
// Dependencies extension, and the legs ride the same setup multicall — see the Dependencies step
|
|
4076
|
+
// below). They're plain supported flags; `abx help deploy-code` documents them as working.
|
|
4077
|
+
'dep', 'dep-registry',
|
|
3931
4078
|
]);
|
|
3932
4079
|
export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
3933
4080
|
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 +4233,14 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4086
4233
|
step('Content');
|
|
4087
4234
|
const scriptPath = flags.script;
|
|
4088
4235
|
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)`);
|
|
4236
|
+
const scriptChunks = planOnChainScript(source);
|
|
4237
|
+
ok(`script ${new TextEncoder().encode(source).length} bytes → ${scriptChunks.length} on-chain chunk(s)`);
|
|
4095
4238
|
const contractFields = [];
|
|
4096
4239
|
if (flags.description)
|
|
4097
4240
|
contractFields.push({ field: encodeTag(F.description), representation: encodeTag(R.inline), value: toHex(String(flags.description)) });
|
|
4098
4241
|
if (flags['external-url'])
|
|
4099
4242
|
contractFields.push({ field: encodeTag(F.externalUrl), representation: encodeTag(R.inline), value: toHex(String(flags['external-url'])) });
|
|
4100
|
-
contractFields.push(...authorshipContractFields(flags));
|
|
4243
|
+
contractFields.push(...authorshipContractFields(flags), ...gatewayContractFields(flags));
|
|
4101
4244
|
const onchainUriLegs = onChainUri ? onchainUriSetupCalls({ generator, metadataRenderer }) : [];
|
|
4102
4245
|
step('Deploy');
|
|
4103
4246
|
const lane = laneFromFlags(flags);
|
|
@@ -4179,7 +4322,11 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4179
4322
|
const depLegCount = dependencySetupCalls(deps, depRegistry).length;
|
|
4180
4323
|
const approvals = 1 + (scriptChunks.length + schemas.length + depLegCount + onchainUriLegs.length + mintCount > 0 ? 1 : 0);
|
|
4181
4324
|
if (!dryRun) {
|
|
4182
|
-
await confirmSend(
|
|
4325
|
+
await confirmSend(
|
|
4326
|
+
// Second site of the same missing binding as the plan line above — and the worse one: this is
|
|
4327
|
+
// the REAL deploy path, so `deploy-code --copies` threw at the confirmation prompt, after the
|
|
4328
|
+
// wallet and factory work was already done.
|
|
4329
|
+
`About to deploy an edition code project "${name}" (${symbol}) — script ${Buffer.byteLength(source, 'utf8')} bytes → ${scriptChunks.length} chunk(s); ` +
|
|
4183
4330
|
`up to ${max} id(s) × ${editionSize === 0n ? 'open' : editionSize.toString()} cop${editionSize === 1n ? 'y' : 'ies'} each; ` +
|
|
4184
4331
|
`${onChainUri ? `tokenURI ON-CHAIN via the renderer (generator ${generator})` : `resolver base ${baseUrl}`}; ` +
|
|
4185
4332
|
`mint: ${mintCount > 0 ? `${mintCount} id(s) × ${mintAmount} at deploy` : 'deferred'}; approvals: ${approvals} wallet approval(s); owner+royalty: your wallet @ ${royaltyBps / 100}%.` +
|
|
@@ -4208,7 +4355,10 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
4208
4355
|
if (shownAddr)
|
|
4209
4356
|
info(`deterministic address: ${shownAddr}`);
|
|
4210
4357
|
info(`name "${name}" · symbol ${symbol} · max ${max} id(s) · royalty ${royaltyBps / 100}%`);
|
|
4211
|
-
|
|
4358
|
+
// `source` is what this lane read; there has never been a `bytes` binding here, so this line
|
|
4359
|
+
// threw a ReferenceError for every `deploy-code --copies … --dry-run` — the dry run crashed at the
|
|
4360
|
+
// last step of the plan it was printing. Byte length, not UTF-16 code units, matching the 721 twin.
|
|
4361
|
+
info(`content: script ${Buffer.byteLength(source, 'utf8')} bytes → ${scriptChunks.length} on-chain chunk(s)`);
|
|
4212
4362
|
printSeedSourcePlan(seedSource, chainId);
|
|
4213
4363
|
info(`PostParam schema(s): ${schemas.length ? schemas.map((s) => describeSchema(s)).join(', ') : 'none'}`);
|
|
4214
4364
|
info(`mint: ${mintCount > 0 ? `${mintCount} id(s) × ${mintAmount} cop${mintAmount === 1n ? 'y' : 'ies'} → ${deployer ?? 'your wallet'} at deploy` : 'deferred (mint later / external minter)'}`);
|