@artblocks/abx-cli 0.1.0-alpha.41 → 0.1.0-alpha.42
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 +77 -0
- package/dist/commands/deploy.d.ts +51 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +298 -128
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +28 -4
- package/dist/commands/project.js.map +1 -1
- package/dist/deploy-plan.d.ts +80 -9
- package/dist/deploy-plan.d.ts.map +1 -1
- package/dist/deploy-plan.js +7 -18
- package/dist/deploy-plan.js.map +1 -1
- package/dist/main.js +17 -7
- package/dist/main.js.map +1 -1
- package/dist/ownerops.d.ts +70 -1
- package/dist/ownerops.d.ts.map +1 -1
- package/dist/ownerops.js +166 -7
- package/dist/ownerops.js.map +1 -1
- package/dist/riskgate.d.ts +36 -0
- package/dist/riskgate.d.ts.map +1 -1
- package/dist/riskgate.js +91 -17
- package/dist/riskgate.js.map +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +1 -1
- package/skill/reference/code.md +14 -0
- package/skill/reference/operate.md +12 -0
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, contentIdFromLocator, METADATA_REPRESENTATION as R, analyzeScript, assertChainId, checkRegistryDeps, dependencySetupCalls, deployOneOfOne, deploySeries, deployOneOfOneEdition, deployEditionImage, discoverDeployBlock, encodeFieldRenderer, encodeTag, expectedChainComplete, makeHotSender, makePublicClient, makeWalletClient, normalizeAttributes, onchainUriSetupCalls, oneOfOneImageAbi, oneOfOneEditionAbi, editionCodeAbi, parseTraitPairs, planResume, planEditionResume, 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';
|
|
18
|
+
import { DEFAULT_CHAIN_KEY, DEP_RESOLUTION, METADATA_FIELD as F, contentIdFromLocator, METADATA_REPRESENTATION as R, analyzeScript, assertChainId, checkRegistryDeps, dependencySetupCalls, DEFAULT_TX_GAS_BUDGET, MEASURED_ESTIMATE_GAS_ALLOWANCE, SETUP_LEG_GAS, estimateChunkGasForBytes, packCallsByGas, deployOneOfOne, deploySeries, deployOneOfOneEdition, deployEditionImage, discoverDeployBlock, encodeFieldRenderer, encodeTag, expectedChainComplete, makeHotSender, makePublicClient, makeWalletClient, normalizeAttributes, onchainUriSetupCalls, oneOfOneImageAbi, oneOfOneEditionAbi, editionCodeAbi, parseTraitPairs, planResume, planEditionResume, 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, getAddress, toHex, zeroAddress } from 'viem';
|
|
@@ -926,10 +926,22 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
926
926
|
// never disagree. Counts TX signatures only: a storage upload signed by a connected wallet
|
|
927
927
|
// (Arweave via --storage-signer eth) is a message signature, not a transaction, and is already
|
|
928
928
|
// named separately (see the `remoteEthUpload` narration) — it does not add to this count.
|
|
929
|
+
// Also captures the plan object's `custody.image` file metadata (issue #123 follow-up): the SAME
|
|
930
|
+
// bytes read here for the tx-count math, hashed/typed locally (no chain read) — never re-derived
|
|
931
|
+
// at the emit site below.
|
|
932
|
+
let planImageFile = null;
|
|
929
933
|
const approvals = onchainImage
|
|
930
934
|
? (() => {
|
|
931
|
-
const
|
|
932
|
-
|
|
935
|
+
const bytes = readFileSync(resolvePath(flags.image));
|
|
936
|
+
const compress = parseCompress(flags.compress);
|
|
937
|
+
const contentPlan = computeContentPlan(bytes, compress);
|
|
938
|
+
planImageFile = {
|
|
939
|
+
bytes: bytes.length,
|
|
940
|
+
stagedBytes: contentPlan.stagedBytes,
|
|
941
|
+
mimeType: contentTypeFromPath(resolvePath(flags.image)),
|
|
942
|
+
contentHash: hashContent(bytes),
|
|
943
|
+
};
|
|
944
|
+
return (contentPlan.plan.mode === 'single' ? 1 : contentPlan.plan.txCount) + 1; // staging tx(s) + the deploy tx
|
|
933
945
|
})()
|
|
934
946
|
: 1;
|
|
935
947
|
// Mint-on-deploy is the default; --no-mint defers it so you can stand up + warm
|
|
@@ -1069,7 +1081,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
1069
1081
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
1070
1082
|
family: '1of1',
|
|
1071
1083
|
lane,
|
|
1072
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging'] : null },
|
|
1084
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging', 'deploy'] : null },
|
|
1073
1085
|
// The 1/1 lane's InitParams has no minter/primaryPayee field at all — `null` here means
|
|
1074
1086
|
// "no concept on this lane", not "unset".
|
|
1075
1087
|
roles: { signer: deployer, owner: deployer, royaltyReceiver: deployer, primaryPayee: null, minter: null },
|
|
@@ -1083,6 +1095,8 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
1083
1095
|
// "nothing baked", not an empty string to interpret.
|
|
1084
1096
|
tokenUriBase: params.tokenURIBase || null,
|
|
1085
1097
|
contractUriBase: params.contractURIBase || null,
|
|
1098
|
+
renderer: onChainUri ? renderer : null,
|
|
1099
|
+
image: planImageFile,
|
|
1086
1100
|
},
|
|
1087
1101
|
mint: { deferred: noMint, count: noMint ? 0 : 1, amountPerId: null, recipient: noMint ? null : deployer },
|
|
1088
1102
|
estimate: { ethApprox: null, gasApprox: null }, // the 1/1 lane computes no cost estimate
|
|
@@ -1216,7 +1230,7 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
1216
1230
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
1217
1231
|
family: '1of1',
|
|
1218
1232
|
lane,
|
|
1219
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging'] : null },
|
|
1233
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging', 'deploy'] : null },
|
|
1220
1234
|
roles: { signer: deployerAddr ?? null, owner: deployerAddr ?? null, royaltyReceiver: deployerAddr ?? null, primaryPayee: null, minter: null },
|
|
1221
1235
|
royalty: { bps: royaltyBps, capBps: maxRoyaltyBps, burnable },
|
|
1222
1236
|
custody: {
|
|
@@ -1227,6 +1241,8 @@ export async function cmdDeployBody(flags, serveAfter, emit) {
|
|
|
1227
1241
|
// carries `params` out to this scope) — never a new decision, the identical ternary.
|
|
1228
1242
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
1229
1243
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
1244
|
+
renderer: onChainUri ? renderer : null,
|
|
1245
|
+
image: planImageFile,
|
|
1230
1246
|
},
|
|
1231
1247
|
mint: { deferred: noMint, count: noMint ? 0 : 1, amountPerId: null, recipient: noMint ? null : deployerAddr ?? null },
|
|
1232
1248
|
estimate: { ethApprox: null, gasApprox: null },
|
|
@@ -1522,10 +1538,21 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1522
1538
|
// The edition paths shipped without this and without the readout line below, so `--copies` previews
|
|
1523
1539
|
// silently dropped the approval count the skill promises "every preview" prints — leaving an agent
|
|
1524
1540
|
// with nothing to tell the creator about how many wallet prompts to expect.
|
|
1541
|
+
// Also captures `custody.image` file metadata (issue #123 follow-up) — see the 1/1 twin's identical
|
|
1542
|
+
// local for why it's computed here rather than re-derived at the emit site.
|
|
1543
|
+
let planImageFile = null;
|
|
1525
1544
|
const approvals = onchainImage
|
|
1526
1545
|
? (() => {
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1546
|
+
const bytes = readFileSync(resolvePath(flags.image));
|
|
1547
|
+
const compress = parseCompress(flags.compress);
|
|
1548
|
+
const contentPlan = computeContentPlan(bytes, compress);
|
|
1549
|
+
planImageFile = {
|
|
1550
|
+
bytes: bytes.length,
|
|
1551
|
+
stagedBytes: contentPlan.stagedBytes,
|
|
1552
|
+
mimeType: contentTypeFromPath(resolvePath(flags.image)),
|
|
1553
|
+
contentHash: hashContent(bytes),
|
|
1554
|
+
};
|
|
1555
|
+
return (contentPlan.plan.mode === 'single' ? 1 : contentPlan.plan.txCount) + 1;
|
|
1529
1556
|
})()
|
|
1530
1557
|
: 1;
|
|
1531
1558
|
if (onChainUri) {
|
|
@@ -1625,7 +1652,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1625
1652
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
1626
1653
|
family: '1of1-edition',
|
|
1627
1654
|
lane,
|
|
1628
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging'] : null },
|
|
1655
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging', 'deploy'] : null },
|
|
1629
1656
|
roles: {
|
|
1630
1657
|
signer: deployer, owner: deployer, royaltyReceiver: deployer,
|
|
1631
1658
|
primaryPayee: primaryPayee !== zeroAddress ? primaryPayee : null,
|
|
@@ -1638,6 +1665,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1638
1665
|
backend: !onChainUri && !onchainImage ? backendResolution(storageOverrides(flags)).backend : null,
|
|
1639
1666
|
tokenUriBase: params.tokenURIBase || null,
|
|
1640
1667
|
contractUriBase: params.contractURIBase || null,
|
|
1668
|
+
renderer: onChainUri ? renderer : null,
|
|
1669
|
+
image: planImageFile,
|
|
1641
1670
|
},
|
|
1642
1671
|
mint: { deferred: mintAmount === 0n, count: mintAmount > 0n ? 1 : 0, amountPerId: mintAmount.toString(), recipient: mintAmount > 0n ? deployer : null },
|
|
1643
1672
|
estimate: { ethApprox: null, gasApprox: null },
|
|
@@ -1722,7 +1751,7 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1722
1751
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
1723
1752
|
family: '1of1-edition',
|
|
1724
1753
|
lane,
|
|
1725
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging'] : null },
|
|
1754
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging', 'deploy'] : null },
|
|
1726
1755
|
roles: {
|
|
1727
1756
|
signer: deployerAddr ?? null, owner: deployerAddr ?? null, royaltyReceiver: deployerAddr ?? null,
|
|
1728
1757
|
primaryPayee: primaryPayee !== zeroAddress ? primaryPayee : null,
|
|
@@ -1735,6 +1764,8 @@ export async function cmdDeployOneOfOneEditionBody(flags, emit) {
|
|
|
1735
1764
|
backend: !onChainUri && !onchainImage ? backendResolution(storageOverrides(flags)).backend : null,
|
|
1736
1765
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
1737
1766
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
1767
|
+
renderer: onChainUri ? renderer : null,
|
|
1768
|
+
image: planImageFile,
|
|
1738
1769
|
},
|
|
1739
1770
|
mint: { deferred: mintAmount === 0n, count: mintAmount > 0n ? 1 : 0, amountPerId: mintAmount.toString(), recipient: mintAmount > 0n ? deployerAddr ?? null : null },
|
|
1740
1771
|
estimate: { ethApprox: null, gasApprox: null },
|
|
@@ -2224,7 +2255,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
2224
2255
|
lane,
|
|
2225
2256
|
// A staged on-chain collection is many chunk-write txs, one per file — that per-token detail
|
|
2226
2257
|
// is printed above (`previewImageStaging`), not re-derived here; `legs` just names the shape.
|
|
2227
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per token)'] : null },
|
|
2258
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per token)', 'deploy'] : null },
|
|
2228
2259
|
roles: {
|
|
2229
2260
|
signer: deployer, owner: deployer, royaltyReceiver: deployer,
|
|
2230
2261
|
primaryPayee: primaryPayee !== zeroAddress ? primaryPayee : null,
|
|
@@ -2237,6 +2268,10 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
2237
2268
|
backend: offchainImageOnchainJson || !onChainUri ? backendId : null,
|
|
2238
2269
|
tokenUriBase: params.tokenURIBase || null,
|
|
2239
2270
|
contractUriBase: params.contractURIBase || null,
|
|
2271
|
+
renderer: onChainUri ? renderer : null,
|
|
2272
|
+
// Series stages ONE FILE PER TOKEN under --onchain-image — no single-file shape fits (see
|
|
2273
|
+
// this field's own doc comment) — so this stays null even when `imageOnChain` is true.
|
|
2274
|
+
image: null,
|
|
2240
2275
|
},
|
|
2241
2276
|
mint: { deferred: mintCount === 0, count: mintCount, amountPerId: null, recipient: mintCount > 0 ? deployer : null },
|
|
2242
2277
|
estimate: { ethApprox: null, gasApprox: null },
|
|
@@ -2355,7 +2390,7 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
2355
2390
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
2356
2391
|
family: 'series',
|
|
2357
2392
|
lane,
|
|
2358
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per token)'] : null },
|
|
2393
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per token)', 'deploy'] : null },
|
|
2359
2394
|
roles: {
|
|
2360
2395
|
signer: deployerAddr ?? null, owner: deployerAddr ?? null, royaltyReceiver: deployerAddr ?? null,
|
|
2361
2396
|
primaryPayee: primaryPayee !== zeroAddress ? primaryPayee : null,
|
|
@@ -2368,6 +2403,8 @@ export async function cmdDeploySeriesBody(flags, emit) {
|
|
|
2368
2403
|
backend: offchainImageOnchainJson || !onChainUri ? backendId : null,
|
|
2369
2404
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
2370
2405
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
2406
|
+
renderer: onChainUri ? renderer : null,
|
|
2407
|
+
image: null, // per-token staging — see the dry-run emit's identical comment
|
|
2371
2408
|
},
|
|
2372
2409
|
mint: { deferred: mintCount === 0, count: mintCount, amountPerId: null, recipient: mintCount > 0 ? deployerAddr ?? null : null },
|
|
2373
2410
|
estimate: { ethApprox: null, gasApprox: null },
|
|
@@ -2819,7 +2856,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2819
2856
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
2820
2857
|
family: 'series-edition',
|
|
2821
2858
|
lane,
|
|
2822
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per id)'] : null },
|
|
2859
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per id)', 'deploy'] : null },
|
|
2823
2860
|
roles: {
|
|
2824
2861
|
signer: deployer, owner: deployer, royaltyReceiver: deployer,
|
|
2825
2862
|
primaryPayee: primaryPayee !== zeroAddress ? primaryPayee : null,
|
|
@@ -2832,6 +2869,10 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2832
2869
|
backend: !onchainImage && (offchainImageOnchainJson || !onChainUri) ? backendId : null,
|
|
2833
2870
|
tokenUriBase: params.tokenURIBase || null,
|
|
2834
2871
|
contractUriBase: params.contractURIBase || null,
|
|
2872
|
+
renderer: onChainUri ? renderer : null,
|
|
2873
|
+
// Series-edition stages ONE FILE PER id under --onchain-image — no single-file shape fits
|
|
2874
|
+
// (see this field's own doc comment) — so this stays null even when `imageOnChain` is true.
|
|
2875
|
+
image: null,
|
|
2835
2876
|
},
|
|
2836
2877
|
mint: {
|
|
2837
2878
|
deferred: effectiveMintCount === 0,
|
|
@@ -2915,7 +2956,7 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2915
2956
|
schemaVersion: DEPLOY_PLAN_SCHEMA_VERSION,
|
|
2916
2957
|
family: 'series-edition',
|
|
2917
2958
|
lane,
|
|
2918
|
-
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per id)'] : null },
|
|
2959
|
+
transactions: { approvals, legs: onchainImage ? ['onchain-image-staging (per id)', 'deploy'] : null },
|
|
2919
2960
|
roles: {
|
|
2920
2961
|
signer: deployerAddr ?? null, owner: deployerAddr ?? null, royaltyReceiver: deployerAddr ?? null,
|
|
2921
2962
|
primaryPayee: primaryPayee !== zeroAddress ? primaryPayee : null,
|
|
@@ -2928,6 +2969,8 @@ export async function cmdDeployEditionImageBody(flags, emit) {
|
|
|
2928
2969
|
backend: !onchainImage && (offchainImageOnchainJson || !onChainUri) ? backendId : null,
|
|
2929
2970
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
2930
2971
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
2972
|
+
renderer: onChainUri ? renderer : null,
|
|
2973
|
+
image: null, // per-id staging — see the dry-run emit's identical comment
|
|
2931
2974
|
},
|
|
2932
2975
|
mint: {
|
|
2933
2976
|
deferred: effectiveMintCount === 0,
|
|
@@ -3245,6 +3288,76 @@ export const DEPLOY_SERIES_FLAGS = new Set([
|
|
|
3245
3288
|
// module has finished evaluating.
|
|
3246
3289
|
export const DEPLOY_EDITION_FLAGS = new Set([...DEPLOY_FLAGS, 'copies', 'mint-amount', 'minter', 'primary-payee', 'unpaused']);
|
|
3247
3290
|
export const DEPLOY_SERIES_EDITION_FLAGS = new Set([...DEPLOY_SERIES_FLAGS, 'copies', 'mint-amount']);
|
|
3291
|
+
export const chunkSetupLeg = (index, hex, data) => {
|
|
3292
|
+
const bytes = (hex.length - 2) / 2;
|
|
3293
|
+
return { calls: [data], gas: estimateChunkGasForBytes(bytes), label: `script chunk [${index}] (${bytes} bytes)`, kind: 'chunk', chunkBytes: [bytes] };
|
|
3294
|
+
};
|
|
3295
|
+
export const schemaSetupLeg = (key, data) => ({
|
|
3296
|
+
calls: [data], gas: SETUP_LEG_GAS.schema, label: `param schema '${key}'`, kind: 'schema', schemaKey: key,
|
|
3297
|
+
});
|
|
3298
|
+
export const depsSetupLeg = (calls) => ({
|
|
3299
|
+
calls, gas: calls.length * SETUP_LEG_GAS.dependency, label: `${calls.length} dependency declaration(s)`, kind: 'deps',
|
|
3300
|
+
});
|
|
3301
|
+
export const uriSetupLeg = (calls) => ({
|
|
3302
|
+
calls, gas: calls.length * SETUP_LEG_GAS.onchainUri, label: 'on-chain URI wiring', kind: 'uri',
|
|
3303
|
+
});
|
|
3304
|
+
export const mintSetupLeg = (data) => ({ calls: [data], gas: SETUP_LEG_GAS.mint, label: 'reserve mint', kind: 'mint' });
|
|
3305
|
+
/** `SetupLegsCore` (resume.ts's id-agnostic four leg groups, shared by both 721 and EditionCode) →
|
|
3306
|
+
* the gas-batching shape above — schemas individually (resume diffs them per-key too), deps and
|
|
3307
|
+
* on-chain-URI as one atomic leg apiece (resume diffs them as one group too — see resume.ts's
|
|
3308
|
+
* `planCoreLegs`). Mints are NOT included here: their shape differs by lane (721 whole-total vs
|
|
3309
|
+
* EditionCode per-id vs a `--resume` shortfall) — every caller builds its own mint legs. */
|
|
3310
|
+
export function coreSetupLegs(legs) {
|
|
3311
|
+
return {
|
|
3312
|
+
chunks: legs.chunks.map((c) => chunkSetupLeg(c.index, c.hex, c.data)),
|
|
3313
|
+
config: [
|
|
3314
|
+
...legs.schemas.map((s) => schemaSetupLeg(s.key, s.data)),
|
|
3315
|
+
...(legs.deps.calls.length ? [depsSetupLeg(legs.deps.calls)] : []),
|
|
3316
|
+
...(legs.uri.calls.length ? [uriSetupLeg(legs.uri.calls)] : []),
|
|
3317
|
+
],
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* Split three ordered leg groups into gas-bounded batches — chunks, then config, then mints, NEVER
|
|
3322
|
+
* mixed (see the module note above). Refuses BEFORE building anything if a single leg's own gas
|
|
3323
|
+
* estimate alone exceeds the binding `eth_estimateGas` ceiling: batching only helps when the problem
|
|
3324
|
+
* is too MANY legs, not one leg too big, and that case must never reach a raw "gas limit too high".
|
|
3325
|
+
* Deterministic in the legs' `.gas` values alone (never their `.calls`), so calling this once early
|
|
3326
|
+
* (to size `approvals`, before an owner is even known) and again later with the real calldata
|
|
3327
|
+
* produces the IDENTICAL batch shape both times — no drift between what's previewed and what's sent.
|
|
3328
|
+
*/
|
|
3329
|
+
export function planCodeSetupBatches(groups) {
|
|
3330
|
+
const all = [...groups.chunks, ...groups.config, ...groups.mints];
|
|
3331
|
+
if (all.length === 0)
|
|
3332
|
+
return [];
|
|
3333
|
+
const oversized = all.find((leg) => leg.gas > MEASURED_ESTIMATE_GAS_ALLOWANCE);
|
|
3334
|
+
if (oversized) {
|
|
3335
|
+
throw new Error(`setup leg — ${oversized.label} — needs ~${oversized.gas.toLocaleString()} gas on its own, which exceeds the ` +
|
|
3336
|
+
`~${MEASURED_ESTIMATE_GAS_ALLOWANCE.toLocaleString()} eth_estimateGas ceiling every transaction is bound by ` +
|
|
3337
|
+
`(that RPC allowance is the binding constraint — not the higher block gas limit). No amount of batching can ` +
|
|
3338
|
+
`make ONE leg fit a transaction by itself — shrink it before deploying (a smaller chunk size, fewer items in ` +
|
|
3339
|
+
`this leg), then re-run.`);
|
|
3340
|
+
}
|
|
3341
|
+
const total = all.reduce((g, l) => g + l.gas, 0);
|
|
3342
|
+
if (total <= DEFAULT_TX_GAS_BUDGET)
|
|
3343
|
+
return [all]; // fits one tx — unchanged common-case behavior
|
|
3344
|
+
return [...packCallsByGas(groups.chunks), ...packCallsByGas(groups.config), ...packCallsByGas(groups.mints)].filter((b) => b.length > 0);
|
|
3345
|
+
}
|
|
3346
|
+
/** Turn ordered setup-leg batches into the actual `prepareCodeSetup` transactions — rebuilding each
|
|
3347
|
+
* batch's chunk/schema/dependency/on-chain-URI labeling from its OWN legs, since a batch born from
|
|
3348
|
+
* the split may carry only part of a group (see {@link planCodeSetupBatches}). */
|
|
3349
|
+
export function codeSetupTxsFromBatches(batches, args) {
|
|
3350
|
+
return batches.map((batch) => prepareCodeSetup({
|
|
3351
|
+
contract: args.contract,
|
|
3352
|
+
chainId: args.chainId,
|
|
3353
|
+
calls: batch.flatMap((l) => l.calls),
|
|
3354
|
+
chunkCount: batch.filter((l) => l.kind === 'chunk').length,
|
|
3355
|
+
chunkBytes: batch.filter((l) => l.kind === 'chunk').flatMap((l) => l.chunkBytes ?? []),
|
|
3356
|
+
schemaKeys: batch.filter((l) => l.kind === 'schema').map((l) => l.schemaKey),
|
|
3357
|
+
deps: batch.some((l) => l.kind === 'deps') ? args.deps : [],
|
|
3358
|
+
onchainUri: batch.some((l) => l.kind === 'uri'),
|
|
3359
|
+
}));
|
|
3360
|
+
}
|
|
3248
3361
|
export async function cmdDeployCode(flags) {
|
|
3249
3362
|
// `--resume` targets an EXISTING contract — its standard (721 or edition) was fixed at THAT
|
|
3250
3363
|
// contract's own deploy, so `--copies` (a creation-time choice) is nonsensical alongside it.
|
|
@@ -3686,7 +3799,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3686
3799
|
if (flags['image-renderer'] || flags['attributes-renderer']) {
|
|
3687
3800
|
warn(`${bold('renderer check — yours to verify')} (the CLI confirms code at the address, NOT that render() behaves): an IAbxFieldRenderer must ` +
|
|
3688
3801
|
`${bold('NEVER revert')} for any token/param state incl. the collection surface (tokenId = type(uint256).max) — a revert bricks the WHOLE tokenURI (no try/catch) — ` +
|
|
3689
|
-
`and must return the right content-type (image → ${bold('image/svg+xml')} · attributes → a JSON array). Review the render function + forge-test it before shipping. Invariants: reference/code
|
|
3802
|
+
`and must return the right content-type (image → ${bold('image/svg+xml')} · attributes → a JSON array). Review the render function + forge-test it before shipping. Invariants: reference/code.md.`);
|
|
3690
3803
|
}
|
|
3691
3804
|
// ── Surfaces disposition — resolve EVERY product dimension explicitly, up front ──────────────
|
|
3692
3805
|
// A code project has four surfaces that each land somewhere (or nowhere). A real session shipped
|
|
@@ -3790,10 +3903,50 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3790
3903
|
// one whenever a dependency leg rode the multicall alongside a renderer-only (`foldIntoInit`) fold.
|
|
3791
3904
|
const depLegs = deps.length + (depRegistry ? 1 : 0);
|
|
3792
3905
|
const setupLen = scriptChunks.length + schemas.length + depLegs + (onchainUriLegs?.length ?? 0) + setupMintCount;
|
|
3793
|
-
|
|
3794
|
-
//
|
|
3795
|
-
//
|
|
3796
|
-
//
|
|
3906
|
+
// The setup legs, GROUPED. The normal deploy flattens them; `--resume` diffs them against chain
|
|
3907
|
+
// state and sends only what is missing (see resume.ts). One builder for both, so a resume can never
|
|
3908
|
+
// drift from what a fresh deploy would have written — a second implementation of this sequence is
|
|
3909
|
+
// the failure mode a repair verb most easily introduces. Moved up here (it used to sit right before
|
|
3910
|
+
// `preparedFor`, far below) so `approvals`/`planSetupLegs`/the dry-run's tx-count line can all size
|
|
3911
|
+
// themselves off the SAME gas-bounded batch plan `preparedFor` actually sends — see
|
|
3912
|
+
// `planCodeSetupBatches`'s own doc for why calling it here with a placeholder owner and again below
|
|
3913
|
+
// with the real one can never disagree (it's a pure function of each leg's `.gas`, never its
|
|
3914
|
+
// `.calls`, and owner only ever appears in a mint call's calldata, never its gas).
|
|
3915
|
+
const setupLegGroups = (owner) => ({
|
|
3916
|
+
chunks: scriptChunks.map((chunk, i) => ({
|
|
3917
|
+
index: i,
|
|
3918
|
+
hex: chunk,
|
|
3919
|
+
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setScriptChunk', args: [BigInt(i), chunk] }),
|
|
3920
|
+
})),
|
|
3921
|
+
schemas: schemas.map(({ key, paramType, auth, authAddress, lockAfter, min, max, selectOptions }) => ({
|
|
3922
|
+
key,
|
|
3923
|
+
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setParamSchema', args: [encodeTag(key), paramType, auth, authAddress, lockAfter, min, max, selectOptions] }),
|
|
3924
|
+
})),
|
|
3925
|
+
deps: { count: deps.length, registry: depRegistry ?? null, calls: dependencySetupCalls(deps, depRegistry) },
|
|
3926
|
+
// program lane only: animation_url field · the URI renderers (before the mints)
|
|
3927
|
+
uri: { calls: onchainUriLegs ?? [], animationField: onchainUriLegs?.length ? F.animationUrl : null },
|
|
3928
|
+
// The INTENDED TOTAL, not a count to add — a resume mints the shortfall. `mintCount` covers both
|
|
3929
|
+
// lanes: folded-into-init (renderer-only) and setup-carried, since either way it is what the
|
|
3930
|
+
// creator asked for.
|
|
3931
|
+
mints: { intendedTotal: mintCount, data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'mint', args: [owner] }) },
|
|
3932
|
+
});
|
|
3933
|
+
// Gas-bounded setup-transaction batches for a FRESH deploy — chunks, then config, then mints, never
|
|
3934
|
+
// mixed (see `planCodeSetupBatches`). `owner` is irrelevant to the batch SHAPE (only a mint call's
|
|
3935
|
+
// calldata embeds it, never its gas), so `zeroAddress` here is a placeholder that's never sent —
|
|
3936
|
+
// it exists purely so `approvals`/`planSetupLegs` (and the dry-run prose) can report the REAL
|
|
3937
|
+
// transaction count before a deployer is even known, with zero risk of drifting from what
|
|
3938
|
+
// `preparedFor` below actually builds and sends.
|
|
3939
|
+
const freshDeployBatches = planCodeSetupBatches({
|
|
3940
|
+
...coreSetupLegs(setupLegGroups(zeroAddress)),
|
|
3941
|
+
mints: Array.from({ length: setupMintCount }, () => mintSetupLeg('0x')),
|
|
3942
|
+
});
|
|
3943
|
+
const approvals = 1 + freshDeployBatches.length; // deploy tx + however many gas-bounded setup batches
|
|
3944
|
+
// Ordered legs riding the setup (if any), for the plan object's `transactions.legs` — GROUP names,
|
|
3945
|
+
// not a literal per-transaction count (a group can span more than one batch — see deploy-plan.ts's
|
|
3946
|
+
// own doc comment on `legs`). The SAME booleans `setupLen`/`setupBits` (the dry-run's own
|
|
3947
|
+
// `transactions:` line, below) already use, so this can't drift from the tx(es) the wallet actually
|
|
3948
|
+
// signs. Always ends with `'deploy'` when non-null — the setup batch(es) (if any) ride AHEAD of the
|
|
3949
|
+
// deploy tx, never instead of it.
|
|
3797
3950
|
const planSetupLegs = setupLen > 0
|
|
3798
3951
|
? [
|
|
3799
3952
|
scriptChunks.length ? 'chunks' : null,
|
|
@@ -3801,6 +3954,7 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3801
3954
|
depLegs ? 'dependencies' : null,
|
|
3802
3955
|
onchainUriLegs ? 'onchain-uri' : null,
|
|
3803
3956
|
setupMintCount ? 'mints' : null,
|
|
3957
|
+
'deploy',
|
|
3804
3958
|
].filter((leg) => leg !== null)
|
|
3805
3959
|
: null;
|
|
3806
3960
|
// opt-in --confirm: one y/N before ANY upload or send (no-op without --confirm; never blocks scripts).
|
|
@@ -3918,8 +4072,12 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
3918
4072
|
onchainUriLegs ? 'on-chain-uri' : '',
|
|
3919
4073
|
setupMintCount ? 'mints' : '',
|
|
3920
4074
|
].filter(Boolean).join('/');
|
|
3921
|
-
info(`transactions: ${
|
|
3922
|
-
(
|
|
4075
|
+
info(`transactions: ${approvals} (deploy${foldIntoInit ? ' — on-chain-uri wiring + mint fold into it' : ''}` +
|
|
4076
|
+
(freshDeployBatches.length > 0
|
|
4077
|
+
? ` + ${freshDeployBatches.length} setup ${freshDeployBatches.length === 1 ? 'transaction' : 'transactions (gas-bounded)'}: ${setupBits}`
|
|
4078
|
+
: '') +
|
|
4079
|
+
')' +
|
|
4080
|
+
(foldIntoInit && freshDeployBatches.length === 0 ? ` ${dim('— single transaction, fully self-contained')}` : ''));
|
|
3923
4081
|
info(`approvals ${approvals} wallet approval(s)`); // TX signatures only; the same number the tx-count line above implies
|
|
3924
4082
|
// Best-effort cost guidance (owner: a bonus in dry-run, never a blocker — silently skip if the
|
|
3925
4083
|
// RPC can't price gas; see cli-ux-decisions). On-chain byte storage dominates a template drop,
|
|
@@ -4105,6 +4263,9 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4105
4263
|
backend: dirUpload ? dirUpload.backend.id : null,
|
|
4106
4264
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
4107
4265
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
4266
|
+
renderer: onChainUri ? metadataRenderer : null,
|
|
4267
|
+
// This lane has no single `--image` file — see `surfaces.thumbnail` below instead.
|
|
4268
|
+
image: null,
|
|
4108
4269
|
},
|
|
4109
4270
|
mint: { deferred: mintCount === 0, count: mintCount, amountPerId: null, recipient: mintCount > 0 ? deployer : null },
|
|
4110
4271
|
estimate: planEstimate ?? { ethApprox: null, gasApprox: null },
|
|
@@ -4168,47 +4329,21 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4168
4329
|
tokenFields: [],
|
|
4169
4330
|
contractFields,
|
|
4170
4331
|
});
|
|
4171
|
-
//
|
|
4172
|
-
//
|
|
4173
|
-
//
|
|
4174
|
-
//
|
|
4175
|
-
const setupLegGroups = (owner) => ({
|
|
4176
|
-
chunks: scriptChunks.map((chunk, i) => ({
|
|
4177
|
-
index: i,
|
|
4178
|
-
hex: chunk,
|
|
4179
|
-
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setScriptChunk', args: [BigInt(i), chunk] }),
|
|
4180
|
-
})),
|
|
4181
|
-
schemas: schemas.map(({ key, paramType, auth, authAddress, lockAfter, min, max, selectOptions }) => ({
|
|
4182
|
-
key,
|
|
4183
|
-
data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setParamSchema', args: [encodeTag(key), paramType, auth, authAddress, lockAfter, min, max, selectOptions] }),
|
|
4184
|
-
})),
|
|
4185
|
-
deps: { count: deps.length, registry: depRegistry ?? null, calls: dependencySetupCalls(deps, depRegistry) },
|
|
4186
|
-
// program lane only: animation_url field · the URI renderers (before the mints)
|
|
4187
|
-
uri: { calls: onchainUriLegs ?? [], animationField: onchainUriLegs?.length ? F.animationUrl : null },
|
|
4188
|
-
// The INTENDED TOTAL, not a count to add — a resume mints the shortfall. `mintCount` covers both
|
|
4189
|
-
// lanes: folded-into-init (renderer-only) and setup-carried, since either way it is what the
|
|
4190
|
-
// creator asked for.
|
|
4191
|
-
mints: { intendedTotal: mintCount, data: encodeFunctionData({ abi: seriesCodeAbi, functionName: 'mint', args: [owner] }) },
|
|
4192
|
-
});
|
|
4193
|
-
const setupCalls = (owner) => {
|
|
4194
|
-
const g = setupLegGroups(owner);
|
|
4195
|
-
return [
|
|
4196
|
-
...g.chunks.map((c) => c.data),
|
|
4197
|
-
...g.schemas.map((x) => x.data),
|
|
4198
|
-
...g.deps.calls,
|
|
4199
|
-
...g.uri.calls,
|
|
4200
|
-
// folded mints ride init, not here — so this uses setupMintCount, not the intended total.
|
|
4201
|
-
...Array.from({ length: setupMintCount }, () => g.mints.data),
|
|
4202
|
-
];
|
|
4203
|
-
};
|
|
4332
|
+
// `setupLegGroups` (the shared, resume-compatible builder) and the fresh-deploy batch shape
|
|
4333
|
+
// (`freshDeployBatches`) are hoisted above — see the comment there for why. Build the REAL
|
|
4334
|
+
// transactions for the actual signer from the SAME batch shape (identical `.gas` sequence ⇒
|
|
4335
|
+
// identical split — see `planCodeSetupBatches`'s doc), this time with real mint calldata.
|
|
4204
4336
|
const cid = resolveChain(CHAIN).id;
|
|
4205
4337
|
const preparedFor = async (owner) => {
|
|
4206
4338
|
const salt = parseSaltFlag(flags.salt) ?? saltFor(owner);
|
|
4207
4339
|
const clone = (await publicClient.readContract({ address: factory, abi: seriesCodeFactoryAbi, functionName: 'predictDeterministicAddress', args: [salt] }));
|
|
4208
|
-
const
|
|
4209
|
-
const
|
|
4210
|
-
|
|
4211
|
-
|
|
4340
|
+
const g = setupLegGroups(owner);
|
|
4341
|
+
const batches = planCodeSetupBatches({
|
|
4342
|
+
...coreSetupLegs(g),
|
|
4343
|
+
mints: Array.from({ length: setupMintCount }, () => mintSetupLeg(g.mints.data)),
|
|
4344
|
+
});
|
|
4345
|
+
const setupTxs = codeSetupTxsFromBatches(batches, { contract: clone, chainId: cid, deps: deps.map((d) => d.display) });
|
|
4346
|
+
const txs = [prepareDeploySeriesCode({ factory, params: initParamsFor(owner), salt, chainId: cid, clone }), ...setupTxs];
|
|
4212
4347
|
return { clone, txs };
|
|
4213
4348
|
};
|
|
4214
4349
|
// ── --resume: finish an EXISTING contract, deploy nothing ──────────────────────────────────
|
|
@@ -4337,7 +4472,9 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4337
4472
|
lane,
|
|
4338
4473
|
roles: { signer: owner, owner, royaltyReceiver: null, primaryPayee: null, minter: null },
|
|
4339
4474
|
royalty: null,
|
|
4340
|
-
|
|
4475
|
+
// renderer/image: null — both are InitParams facts fixed at the ORIGINAL deploy (like
|
|
4476
|
+
// royalty/mint above); a resume doesn't re-read or re-decide either here.
|
|
4477
|
+
custody: { onChainUri, imageOnChain: null, backend: null, tokenUriBase: null, contractUriBase: null, renderer: null, image: null },
|
|
4341
4478
|
mint: null,
|
|
4342
4479
|
warnings: planWarnings.slice(),
|
|
4343
4480
|
// Still reflects what THIS invocation's flags say about every marketplace-facing surface —
|
|
@@ -4364,19 +4501,46 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4364
4501
|
console.log('');
|
|
4365
4502
|
return;
|
|
4366
4503
|
}
|
|
4367
|
-
|
|
4504
|
+
// Reconstruct `plan.calls` (one flat, ORDERED array) back into the four leg groups it was built
|
|
4505
|
+
// from — chunks, schemas, deps, uri, in that exact order (resume.ts's `planCoreLegs` guarantees
|
|
4506
|
+
// it; see its module doc) — using the counts `plan.sending` already reports, so this can't drift
|
|
4507
|
+
// from what the diff actually decided to send. Mints (whatever is left) are appended by
|
|
4508
|
+
// `planResume`/`planEditionResume` AFTER these four, also guaranteed by resume.ts.
|
|
4509
|
+
let resumeCallPtr = 0;
|
|
4510
|
+
const takeResumeCalls = (n) => {
|
|
4511
|
+
const slice = plan.calls.slice(resumeCallPtr, resumeCallPtr + n);
|
|
4512
|
+
resumeCallPtr += n;
|
|
4513
|
+
return slice;
|
|
4514
|
+
};
|
|
4515
|
+
const missingChunkCalls = takeResumeCalls(plan.sending.chunkIndices.length);
|
|
4516
|
+
const missingSchemaCalls = takeResumeCalls(plan.sending.schemaKeys.length);
|
|
4517
|
+
const missingDepsCalls = takeResumeCalls(plan.sending.deps ? legs.deps.calls.length : 0);
|
|
4518
|
+
const missingUriCalls = takeResumeCalls(plan.sending.uri ? legs.uri.calls.length : 0);
|
|
4519
|
+
const missingMintCalls = takeResumeCalls(plan.sending.mints);
|
|
4520
|
+
// Same gas-bounded batching a fresh deploy uses (`planCodeSetupBatches`) — a `--resume` whose
|
|
4521
|
+
// setup never landed AT ALL is missing exactly as much as a fresh deploy would have sent, so it
|
|
4522
|
+
// needs the identical split to clear the same `eth_estimateGas` ceiling.
|
|
4523
|
+
const resumeBatches = planCodeSetupBatches({
|
|
4524
|
+
chunks: missingChunkCalls.map((data, i) => {
|
|
4525
|
+
const bytes = plan.sending.chunkBytes[i];
|
|
4526
|
+
return { calls: [data], gas: estimateChunkGasForBytes(bytes), label: `script chunk [${plan.sending.chunkIndices[i]}] (${bytes} bytes)`, kind: 'chunk', chunkBytes: [bytes] };
|
|
4527
|
+
}),
|
|
4528
|
+
config: [
|
|
4529
|
+
...missingSchemaCalls.map((data, i) => schemaSetupLeg(plan.sending.schemaKeys[i], data)),
|
|
4530
|
+
...(missingDepsCalls.length ? [depsSetupLeg(missingDepsCalls)] : []),
|
|
4531
|
+
...(missingUriCalls.length ? [uriSetupLeg(missingUriCalls)] : []),
|
|
4532
|
+
],
|
|
4533
|
+
mints: missingMintCalls.map(mintSetupLeg),
|
|
4534
|
+
});
|
|
4535
|
+
const setupTxs = codeSetupTxsFromBatches(resumeBatches, {
|
|
4368
4536
|
contract: target,
|
|
4369
|
-
calls: plan.calls,
|
|
4370
4537
|
chainId: resolveChain(CHAIN).id,
|
|
4371
|
-
chunkCount: plan.sending.chunkIndices.length,
|
|
4372
|
-
chunkBytes: plan.sending.chunkBytes,
|
|
4373
|
-
schemaKeys: plan.sending.schemaKeys,
|
|
4374
4538
|
deps: plan.sending.deps ? deps.map((d) => d.display) : [],
|
|
4375
|
-
onchainUri: plan.sending.uri,
|
|
4376
4539
|
});
|
|
4377
|
-
//
|
|
4378
|
-
//
|
|
4379
|
-
//
|
|
4540
|
+
// `transactions.legs` names WHICH groups ride the setup, from the same `plan.sending` flags that
|
|
4541
|
+
// build the batches above (never re-derived) — GROUP names, not a literal per-transaction count
|
|
4542
|
+
// (see deploy-plan.ts's own doc comment on `legs`; a `--resume` may now send more than one setup
|
|
4543
|
+
// transaction, same as a fresh deploy).
|
|
4380
4544
|
const resumeLegs = [
|
|
4381
4545
|
plan.sending.chunkIndices.length ? 'chunks' : null,
|
|
4382
4546
|
plan.sending.schemaKeys.length ? 'param-schemas' : null,
|
|
@@ -4388,39 +4552,49 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4388
4552
|
// This used to be its own hand-rolled copy of that exact shape (a dry-run print then confirmSend
|
|
4389
4553
|
// then signTx, duplicated from ownerops.ts's `runWrite`); a deploy-family write is a write like
|
|
4390
4554
|
// any other, so it takes the identical gate (the preview also gains the `to`/`owner` lines
|
|
4391
|
-
// `runWrite`'s preview always printed).
|
|
4555
|
+
// `runWrite`'s preview always printed) — called once per gas-bounded batch, in order.
|
|
4392
4556
|
if (isDryRun(flags)) {
|
|
4393
4557
|
emit(jsonSafe({
|
|
4394
4558
|
command: 'deploy-code', kind: isEdition ? 'edition-code' : 'code', resumed: target, chain: CHAIN, chainId: resolveChain(CHAIN).id, dryRun: true, sent: false, complete: false, sentLegs: plan.calls.length,
|
|
4395
4559
|
plan: {
|
|
4396
4560
|
...resumePlanBase,
|
|
4397
|
-
transactions: { approvals:
|
|
4561
|
+
transactions: { approvals: setupTxs.length, legs: resumeLegs },
|
|
4398
4562
|
estimate: { ethApprox: null, gasApprox: null },
|
|
4399
4563
|
resume: { target, sentLegs: plan.calls.length, complete: false },
|
|
4400
4564
|
},
|
|
4401
4565
|
}));
|
|
4402
4566
|
}
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4567
|
+
let lastResult = null;
|
|
4568
|
+
for (const tx of setupTxs) {
|
|
4569
|
+
const result = await gatedSend(() => tx, flags, { chainKey: CHAIN, expectedSigner: owner });
|
|
4570
|
+
if (result) {
|
|
4571
|
+
ok(`${tx.summary}`);
|
|
4572
|
+
lastResult = result;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
if (isDryRun(flags))
|
|
4576
|
+
return;
|
|
4577
|
+
if (!lastResult) {
|
|
4578
|
+
// The cold ('unsigned') lane: every batch printed its own unsigned tx above (in order — they
|
|
4579
|
+
// target the SAME already-existing contract, so unlike a fresh deploy's deploy→setup dependency
|
|
4580
|
+
// there's no reason to withhold the later ones). Nothing was broadcast, so there is no block to
|
|
4581
|
+
// scan from and no indexing to do yet.
|
|
4582
|
+
console.log(`\n${dim(` unsigned — broadcast ${setupTxs.length > 1 ? 'these, in order,' : 'it'} then re-run \`abx verify ${target}\` to confirm the setup completed.`)}\n`);
|
|
4408
4583
|
emit(jsonSafe({
|
|
4409
4584
|
command: 'deploy-code', kind: isEdition ? 'edition-code' : 'code', resumed: target, chain: CHAIN, chainId: resolveChain(CHAIN).id, sent: false, complete: false, sentLegs: plan.calls.length,
|
|
4410
4585
|
plan: {
|
|
4411
4586
|
...resumePlanBase,
|
|
4412
|
-
transactions: { approvals:
|
|
4587
|
+
transactions: { approvals: setupTxs.length, legs: resumeLegs },
|
|
4413
4588
|
estimate: { ethApprox: null, gasApprox: null },
|
|
4414
4589
|
resume: { target, sentLegs: plan.calls.length, complete: false },
|
|
4415
4590
|
},
|
|
4416
4591
|
}));
|
|
4417
4592
|
return;
|
|
4418
4593
|
}
|
|
4419
|
-
ok(`${tx.summary}`);
|
|
4420
4594
|
// Locals, not the outer clone/deployBlock: this branch never falls through to the deploy path, and
|
|
4421
4595
|
// the authoritative scan floor is still the clone's CREATION block (getCode search), not this
|
|
4422
4596
|
// repair tx's block — flooring the resolver above the deploy would hide the `code` field again.
|
|
4423
|
-
const resumedFloor = (await discoverDeployBlock(publicClient, target)) ??
|
|
4597
|
+
const resumedFloor = (await discoverDeployBlock(publicClient, target)) ?? lastResult.blockNumber;
|
|
4424
4598
|
emit(jsonSafe({
|
|
4425
4599
|
command: 'deploy-code',
|
|
4426
4600
|
kind: isEdition ? 'edition-code' : 'code',
|
|
@@ -4432,10 +4606,10 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4432
4606
|
sent: true,
|
|
4433
4607
|
complete: true,
|
|
4434
4608
|
sentLegs: plan.calls.length,
|
|
4435
|
-
txHash:
|
|
4609
|
+
txHash: lastResult.txHash,
|
|
4436
4610
|
plan: {
|
|
4437
4611
|
...resumePlanBase,
|
|
4438
|
-
transactions: { approvals:
|
|
4612
|
+
transactions: { approvals: setupTxs.length, legs: resumeLegs },
|
|
4439
4613
|
estimate: { ethApprox: null, gasApprox: null },
|
|
4440
4614
|
resume: { target, sentLegs: plan.calls.length, complete: true },
|
|
4441
4615
|
},
|
|
@@ -4563,6 +4737,8 @@ export async function cmdDeployCodeBody(flags, emit) {
|
|
|
4563
4737
|
backend: dirUpload ? dirUpload.backend.id : null,
|
|
4564
4738
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
4565
4739
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
4740
|
+
renderer: onChainUri ? metadataRenderer : null,
|
|
4741
|
+
image: null, // see `surfaces.thumbnail`
|
|
4566
4742
|
},
|
|
4567
4743
|
mint: { deferred: mintCount === 0, count: mintCount, amountPerId: null, recipient: mintCount > 0 ? deployerAddr ?? null : null },
|
|
4568
4744
|
// Unlike the dry run, the sent path computes no cost estimate (the gas-guidance try/catch is
|
|
@@ -5004,10 +5180,11 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
5004
5180
|
const minter = flags.minter ?? zeroAddress;
|
|
5005
5181
|
const primaryPayee = flags['primary-payee'] ?? zeroAddress;
|
|
5006
5182
|
const explicitSalt = parseSaltFlag(flags.salt);
|
|
5007
|
-
//
|
|
5008
|
-
// lane's "fold reserve mints + renderers into init" micro-optimization (only reachable
|
|
5009
|
-
// a renderer-only, no-program drop, which this function refuses anyway — every
|
|
5010
|
-
// deploy has a program).
|
|
5183
|
+
// Normally TWO transactions (deploy + one gas-bounded setup batch) — this leaner lane skips the
|
|
5184
|
+
// 721 code lane's "fold reserve mints + renderers into init" micro-optimization (only reachable
|
|
5185
|
+
// there for a renderer-only, no-program drop, which this function refuses anyway — every
|
|
5186
|
+
// edition-code deploy has a program). A setup large enough to miss the `eth_estimateGas` ceiling
|
|
5187
|
+
// (a big script) splits into MORE than one setup transaction — see `planCodeSetupBatches`.
|
|
5011
5188
|
const initParamsFor = (owner) => ({
|
|
5012
5189
|
owner,
|
|
5013
5190
|
name,
|
|
@@ -5033,55 +5210,42 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
5033
5210
|
tokenFields: [],
|
|
5034
5211
|
contractFields,
|
|
5035
5212
|
});
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
for (let id = 0; id < mintCount; id++) {
|
|
5056
|
-
calls.push(encodeFunctionData({ abi: oneOfOneEditionAbi, functionName: 'mint', args: [owner, BigInt(id), mintAmount] }));
|
|
5057
|
-
}
|
|
5058
|
-
return calls;
|
|
5059
|
-
};
|
|
5213
|
+
// Gas-bounded setup-transaction legs — script chunks, schemas, deps, on-chain-URI wiring are all
|
|
5214
|
+
// OWNER-INDEPENDENT (only a mint call's calldata embeds the owner+id+amount; its gas is a flat
|
|
5215
|
+
// constant regardless), so they're built once, up front, and reused both to size
|
|
5216
|
+
// `approvals`/`planSetupLegs` now and to build the real transactions in `preparedFor` below —
|
|
5217
|
+
// identical `.gas` sequence ⇒ identical split (see `planCodeSetupBatches`'s own doc for why that
|
|
5218
|
+
// can never drift).
|
|
5219
|
+
const chunkLegs = scriptChunks.map((chunk, i) => chunkSetupLeg(i, chunk, encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setScriptChunk', args: [BigInt(i), chunk] })));
|
|
5220
|
+
const depCallsForEdition = dependencySetupCalls(deps, depRegistry);
|
|
5221
|
+
const configLegs = [
|
|
5222
|
+
...schemas.map((s) => schemaSetupLeg(s.key, encodeFunctionData({ abi: seriesCodeAbi, functionName: 'setParamSchema', args: [encodeTag(s.key), s.paramType, s.auth, s.authAddress, s.lockAfter, s.min, s.max, s.selectOptions] }))),
|
|
5223
|
+
...(depCallsForEdition.length ? [depsSetupLeg(depCallsForEdition)] : []),
|
|
5224
|
+
...(onchainUriLegs.length ? [uriSetupLeg(onchainUriLegs)] : []),
|
|
5225
|
+
];
|
|
5226
|
+
// Reserve mints: one `mint(to, id, amount)` per premint id — the edition twin of the 721 lane's N
|
|
5227
|
+
// identical `mint(owner)` calls, one id finer (a specific amount of copies, not just "one").
|
|
5228
|
+
const mintLegsFor = (owner) => Array.from({ length: mintCount }, (_, id) => mintSetupLeg(encodeFunctionData({ abi: oneOfOneEditionAbi, functionName: 'mint', args: [owner, BigInt(id), mintAmount] })));
|
|
5229
|
+
// `owner` is irrelevant to the batch SHAPE (see the comment above), so `zeroAddress` here is a
|
|
5230
|
+
// placeholder used purely to size `approvals` before a deployer is known — never sent.
|
|
5231
|
+
const editionSetupBatches = planCodeSetupBatches({ chunks: chunkLegs, config: configLegs, mints: mintLegsFor(zeroAddress) });
|
|
5060
5232
|
const preparedFor = async (owner) => {
|
|
5061
5233
|
const salt = explicitSalt ?? saltFor(owner);
|
|
5062
5234
|
const clone = await predictClone(publicClient, { factory, salt });
|
|
5063
|
-
const
|
|
5064
|
-
const txs = [prepareDeployEditionCode({ factory, params: initParamsFor(owner), salt, chainId, clone })];
|
|
5065
|
-
if (calls.length) {
|
|
5066
|
-
txs.push(prepareCodeSetup({
|
|
5067
|
-
contract: clone,
|
|
5068
|
-
calls,
|
|
5069
|
-
chainId,
|
|
5070
|
-
chunkCount: scriptChunks.length,
|
|
5071
|
-
chunkBytes: scriptChunks.map((h) => (h.length - 2) / 2),
|
|
5072
|
-
schemaKeys: schemas.map((s) => s.key),
|
|
5073
|
-
onchainUri: onChainUri,
|
|
5074
|
-
}));
|
|
5075
|
-
}
|
|
5235
|
+
const setupTxs = codeSetupTxsFromBatches(planCodeSetupBatches({ chunks: chunkLegs, config: configLegs, mints: mintLegsFor(owner) }), { contract: clone, chainId, deps: deps.map((d) => d.display) });
|
|
5236
|
+
const txs = [prepareDeployEditionCode({ factory, params: initParamsFor(owner), salt, chainId, clone }), ...setupTxs];
|
|
5076
5237
|
return { clone, txs };
|
|
5077
5238
|
};
|
|
5078
|
-
// deps ride the same
|
|
5079
|
-
// but they must be COUNTED in the "is there a setup
|
|
5080
|
-
// (a script with no schemas and no premints) would
|
|
5081
|
-
|
|
5082
|
-
const
|
|
5083
|
-
|
|
5084
|
-
//
|
|
5239
|
+
// deps ride the same setup batch as chunks/schemas/mints when everything fits one transaction, so
|
|
5240
|
+
// they don't add an approval on their own — but they must be COUNTED in the "is there a setup
|
|
5241
|
+
// transaction at all" test, or a deps-only project (a script with no schemas and no premints) would
|
|
5242
|
+
// report 1 approval and send 2 transactions.
|
|
5243
|
+
const depLegCount = depCallsForEdition.length;
|
|
5244
|
+
const approvals = 1 + editionSetupBatches.length; // deploy tx + however many gas-bounded setup batches
|
|
5245
|
+
// Ordered legs riding the setup (if any) — GROUP names, not a literal per-transaction count (see
|
|
5246
|
+
// deploy-plan.ts's own doc comment on `legs`); same booleans `approvals` above is built from, so the
|
|
5247
|
+
// plan object's `transactions.legs` can never drift from the tx(es) the wallet actually signs.
|
|
5248
|
+
// Always ends with `'deploy'` when non-null.
|
|
5085
5249
|
const planSetupLegs = approvals > 1
|
|
5086
5250
|
? [
|
|
5087
5251
|
scriptChunks.length ? 'chunks' : null,
|
|
@@ -5089,6 +5253,7 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
5089
5253
|
depLegCount ? 'dependencies' : null,
|
|
5090
5254
|
onchainUriLegs.length ? 'onchain-uri' : null,
|
|
5091
5255
|
mintCount ? 'mints' : null,
|
|
5256
|
+
'deploy',
|
|
5092
5257
|
].filter((leg) => leg !== null)
|
|
5093
5258
|
: null;
|
|
5094
5259
|
if (!dryRun) {
|
|
@@ -5129,7 +5294,8 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
5129
5294
|
printSeedSourcePlan(seedSource, chainId);
|
|
5130
5295
|
info(`PostParam schema(s): ${schemas.length ? schemas.map((s) => describeSchema(s)).join(', ') : 'none'}`);
|
|
5131
5296
|
info(`mint: ${mintCount > 0 ? `${mintCount} id(s) × ${mintAmount} cop${mintAmount === 1n ? 'y' : 'ies'} → ${deployer ?? 'your wallet'} at deploy` : 'deferred (mint later / external minter)'}`);
|
|
5132
|
-
info(`transactions:
|
|
5297
|
+
info(`transactions: ${approvals} — deploy + ${editionSetupBatches.length} setup ${editionSetupBatches.length === 1 ? 'transaction' : 'transactions (gas-bounded)'} ` +
|
|
5298
|
+
`(${[scriptChunks.length ? 'script chunks' : null, schemas.length ? 'schemas' : null, onChainUri ? 'on-chain URI wiring' : null, mintCount > 0 ? 'reserve mints' : null].filter(Boolean).join(' + ') || 'nothing else'})`);
|
|
5133
5299
|
info(`approvals ${approvals} wallet approval(s)`); // parity with the 721 code twin — it had this only in the --confirm sentence
|
|
5134
5300
|
if (!explicitSalt && deployer) {
|
|
5135
5301
|
console.log(`\n ${bold('salt')} ${g(salt)}`);
|
|
@@ -5155,6 +5321,8 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
5155
5321
|
backend: dirUpload ? dirUpload.backend.id : null,
|
|
5156
5322
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
5157
5323
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
5324
|
+
renderer: onChainUri ? metadataRenderer : null,
|
|
5325
|
+
image: null, // no single --image file on this lane (see custody.image's own doc comment)
|
|
5158
5326
|
},
|
|
5159
5327
|
mint: {
|
|
5160
5328
|
deferred: mintCount === 0,
|
|
@@ -5254,6 +5422,8 @@ export async function cmdDeployEditionCodeBody(flags, emit) {
|
|
|
5254
5422
|
backend: dirUpload ? dirUpload.backend.id : null,
|
|
5255
5423
|
tokenUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/t`,
|
|
5256
5424
|
contractUriBase: onChainUri && !hasPublicUrl ? null : `${baseUrl}/c`,
|
|
5425
|
+
renderer: onChainUri ? metadataRenderer : null,
|
|
5426
|
+
image: null,
|
|
5257
5427
|
},
|
|
5258
5428
|
mint: {
|
|
5259
5429
|
deferred: mintCount === 0,
|