@artblocks/abx-cli 0.1.0-alpha.15 → 0.1.0-alpha.17

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.
@@ -6,7 +6,7 @@
6
6
  * served state reflects the change. The agent picks the lane; the human only
7
7
  * approves (wallet lane) or it's the env key (hot lane).
8
8
  */
9
- import { planContentTxs, type OnChainParamSchema, type SendStagingTx, type Address, type OnChainFieldInput } from '@artblocks/abx-sdk';
9
+ import { planContentTxs, type OnChainParamSchema, type SendStagingTx, type Address, type Hex, type OnChainFieldInput } from '@artblocks/abx-sdk';
10
10
  import { type Lane, type WalletSession } from './signer.js';
11
11
  import { type ParsedSchema } from './schema.js';
12
12
  type Flags = Record<string, string | undefined>;
@@ -20,6 +20,33 @@ export declare function laneFromFlags(flags: Flags): Lane;
20
20
  /** Throw a clear, actionable error when `address` has no contract on the active chain — the common
21
21
  * cause of an owner-op's raw `returned no data ("0x")`. No-op when code is present or unknowable. */
22
22
  export declare function assertContractExists(address: Address): Promise<void>;
23
+ /**
24
+ * `abx configure-param <address> <tokenId> <key> <value>` — set a schema-governed
25
+ * PostParam through the typed path, any lane. Reads the key's on-chain schema first
26
+ * and canonically ENCODES the human input per its type (`#rrggbb`, decimals ×1e10,
27
+ * Select by label, …); `String`/`Bytes` schemas take the value as UTF-8 (or
28
+ * `--file <path>` for bytes) via the data path. The signer must satisfy the schema's
29
+ * auth (Artist = contract owner, TokenOwner — delegate.xyz honored — or the named
30
+ * address); the chain enforces it either way.
31
+ */
32
+ /** Positional args only — drops `--flags` AND the single token each value-taking flag consumes
33
+ * (mirrors parseFlags). `rest.filter(r => !r.startsWith('--'))` was NOT enough: a flag's VALUE
34
+ * (e.g. the URL after `--remote`, or the path after `--file`) isn't `--`-prefixed, so it leaked
35
+ * into the positional value (`configure-param … #ff0000 --remote http://h` → value "#ff0000 http://h"). */
36
+ /**
37
+ * Encode a command-line value for a payload-typed param (`String` / `Bytes`).
38
+ *
39
+ * `String` is UTF-8 text — the literal characters are the value, which is what a user means.
40
+ *
41
+ * `Bytes` is NOT text, and the old code UTF-8'd whatever string it was handed. A tester passed
42
+ * base64 (the encoding the params docs mention — which describes the *canonical decode* a program
43
+ * receives, not what you type here), and 128 packed bytes were stored as 172 bytes of base64 ASCII.
44
+ * Nothing errored; the in-chain renderer read ASCII where it expected bytes and drew garbage. So a
45
+ * `Bytes` value must state its encoding: `0x…` hex, or `--file` for real binary. A bare string is
46
+ * refused rather than guessed at — there is no safe guess between "these characters" and "these
47
+ * bytes", and the failure is invisible until an artwork renders wrong.
48
+ */
49
+ export declare function encodePayloadParam(typeName: 'String' | 'Bytes', valueInput: string, key: string): Hex;
23
50
  export declare function cmdConfigureParam(address: string | undefined, rest: string[], flags: Flags): Promise<void>;
24
51
  declare const HOOK_ROLES: readonly ["configure", "augment", "transfer"];
25
52
  type HookRole = (typeof HOOK_ROLES)[number];
@@ -195,6 +222,20 @@ export declare function cmdSetField(address: string | undefined, flags: Flags):
195
222
  * - computed keys (`artifacts`, `abx_provenance`) are refused — they're the manifest, not inputs.
196
223
  * Scope: token (`--token`, default 0) or `--collection`. Any signing lane. Owner-only.
197
224
  */
225
+ /**
226
+ * Attach one or more artifacts. Several `<key> <uri>` pairs in one invocation become **one
227
+ * transaction**.
228
+ *
229
+ * That batching is a safety fix, not a convenience (backlog B20). The CLI's most-documented flow —
230
+ * mint, then attach each artifact, then refresh — sent one transaction per step with no all-or-nothing
231
+ * boundary, so a failure partway through left a permanently half-written token that cannot be
232
+ * un-minted. An integrator hit exactly this and folded 8 operations into 1 transaction using the SDK's
233
+ * `batchOps`, which the CLI already shipped and did not use. Now it does: N pairs are one
234
+ * `multicall`, so either every artifact lands or none does.
235
+ *
236
+ * A single pair passes through `batchOps` untouched, so the one-artifact case sends the identical
237
+ * transaction it always did.
238
+ */
198
239
  export declare function cmdAttach(rest: string[], flags: Flags): Promise<void>;
199
240
  export declare function cmdLockField(address: string | undefined, flags: Flags): Promise<void>;
200
241
  /** Point a token's URI resolution at an on-chain renderer (or clear it with `--off`).
@@ -1 +1 @@
1
- {"version":3,"file":"ownerops.d.ts","sourceRoot":"","sources":["../src/ownerops.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAeL,cAAc,EAmBd,KAAK,kBAAkB,EAoBvB,KAAK,aAAa,EAIlB,KAAK,OAAO,EAEZ,KAAK,iBAAiB,EAEvB,MAAM,oBAAoB,CAAC;AA+B5B,OAAO,EAA4B,KAAK,IAAI,EAAmB,KAAK,aAAa,EAAC,MAAM,aAAa,CAAC;AAGtG,OAAO,EAAmC,KAAK,YAAY,EAAC,MAAM,aAAa,CAAC;AAWhF,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAWhD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAevD;AAWD;;gGAEgG;AAChG,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMnE;AAMD,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAIhD;AA2GD;sGACsG;AACtG,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1E;AA8BD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA2FhH;AAGD,QAAA,MAAM,UAAU,+CAAgD,CAAC;AACjE,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAQ5C,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAMnE;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE/F;AAOD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB/G;AAED;uEACuE;AACvE,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAItG;AAED;+FAC+F;AAC/F,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAevH;AAED,uGAAuG;AACvG,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAKlG;AAOD,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBtF;AAMD;2EAC2E;AAC3E,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3F;AAED,oGAAoG;AACpG,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnG;AAED,iFAAiF;AACjF,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF;AAED,oEAAoE;AACpE,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAGD;;;;mFAImF;AACnF,eAAO,MAAM,wBAAwB,EAAE,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAK1F,CAAC;AAEF;sFACsF;AACtF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,iBAAiB,EAAE,CAI1E;AAED,+EAA+E;AAC/E,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjG;AAmBD,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BzF;AAGD,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAO1F;AAMD,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAW7F;AAID,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhG;AAGD;;mDAEmD;AACnD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5F;AAaD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAC,GAC/D,OAAO,CAkCT;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDtH;AAGD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAI7D;AA4BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAahD;AAED;+FAC+F;AAC/F,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,CAc1E;AAED;;;;;;;;;;;GAWG;AACH;;;;;;GAMG;AAKH,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,0BAA0B,QAAa,CAAC;AAIrD,gHAAgH;AAChH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAK9E;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,QAAQ,GACjB;IACD,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC;CACxB,CAQA;AA8CD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CASjF;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC,CAOnD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,aAAa,EACnB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAC,CAAC,CAgBxD;AAyBD,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK;AACZ,0DAA0D;AAC1D,KAAK,GAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAsE,GAC3G,OAAO,CAAC,IAAI,CAAC,CAoBf;AAQD,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAgF1F;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAkG3E;AAGD,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAU3F;AAGD;;;6EAG6E;AAC7E,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB7F;AAGD;;mEAEmE;AACnE,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAazF;AAGD,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1F;AAyGD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DjG;AAED;yFACyF;AACzF,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC5F;AAED;oGACoG;AACpG,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB3F;AAUD;+FAC+F;AAC/F,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE,CAiBxF;AAED;iEACiE;AACjE,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE3F;AAgBD,2FAA2F;AAC3F,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB7G"}
1
+ {"version":3,"file":"ownerops.d.ts","sourceRoot":"","sources":["../src/ownerops.ts"],"names":[],"mappings":"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"}
package/dist/ownerops.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * served state reflects the change. The agent picks the lane; the human only
7
7
  * approves (wallet lane) or it's the env key (hot lane).
8
8
  */
9
- import { assertChainId, ensureChunkStore as sdkEnsureChunkStore, predictFixedPriceMinter, encodeReader, makePublicClient, makeWalletClient, oneOfOneImageAbi, seriesImageAbi, abxFixedPriceMinterAbi, deployFixedPriceMinter, prepareConfigureSale, preparePurchase, planChunks, planContentTxs, prepareLockContractField, prepareLockContractURI, prepareLockTokenField, prepareLockTokenURI, prepareMint, prepareSeriesMintMany, prepareSetMinter, prepareSetMaxInvocations, prepareSetPrimaryPayee, prepareSetPaused, prepareSetContractField, prepareSetContractURIBase, prepareSetContractURIOverride, prepareSetContractURIRenderer, prepareSetParamHooks, prepareSetParamSchema, prepareRetireParam, readParamSchema, PARAM_TYPES, prepareSetRoyalty, prepareSetTokenField, prepareSetTokenURIBase, prepareSetTokenURIOverride, prepareSetTokenURIRenderer, prepareSetTransferValidator, prepareTransfer, prepareTransferOwnership, readCreatorTokenStatus, resolveRecommendedTransferValidator, RECOMMENDED_TRANSFER_VALIDATOR, KNOWN_CHAIN_KEYS, resolveChain, resolveRpcUrl, redactRpcUrl, DEFAULT_CHAIN_KEY, stageContent, encodeTag, METADATA_FIELD as F, METADATA_REPRESENTATION as R, } from '@artblocks/abx-sdk';
9
+ import { assertChainId, ensureChunkStore as sdkEnsureChunkStore, predictFixedPriceMinter, encodeReader, makePublicClient, makeWalletClient, oneOfOneImageAbi, seriesImageAbi, abxFixedPriceMinterAbi, deployFixedPriceMinter, prepareConfigureSale, preparePurchase, planChunks, planContentTxs, prepareLockContractField, prepareLockContractURI, prepareLockTokenField, prepareLockTokenURI, prepareMint, prepareSeriesMintMany, prepareSetMinter, prepareSetMaxInvocations, prepareSetPrimaryPayee, prepareSetPaused, prepareSetContractField, prepareSetContractURIBase, prepareSetContractURIOverride, prepareSetContractURIRenderer, prepareSetParamHooks, prepareSetParamSchema, prepareRetireParam, readParamSchema, PARAM_TYPES, prepareSetRoyalty, prepareSetTokenField, batchOps, prepareSetTokenURIBase, prepareSetTokenURIOverride, prepareSetTokenURIRenderer, prepareSetTransferValidator, prepareTransfer, prepareTransferOwnership, readCreatorTokenStatus, resolveRecommendedTransferValidator, RECOMMENDED_TRANSFER_VALIDATOR, KNOWN_CHAIN_KEYS, resolveChain, resolveRpcUrl, redactRpcUrl, DEFAULT_CHAIN_KEY, stageContent, encodeTag, METADATA_FIELD as F, METADATA_REPRESENTATION as R, } from '@artblocks/abx-sdk';
10
10
  import { SelfHostIndexer } from '@artblocks/abx-indexer';
11
11
  import { encodeScalarParam, encodeTag as encodeTagSdk, isAccepted, prepareConfigureTokenParam, prepareConfigureTokenParamData, prepareSetContractParam, prepareSetContractParamData, seriesCodeAbi, } from '@artblocks/abx-sdk';
12
12
  import { hasParamEnumeration } from './onchain-uri.js';
@@ -16,11 +16,12 @@ import { toHex as toHexSdk } from 'viem';
16
16
  import { readFileSync } from 'node:fs';
17
17
  import { basename, resolve as resolvePath } from 'node:path';
18
18
  import { gzipSync } from 'node:zlib';
19
- import { formatEther, getAddress, isAddress, parseEther, toHex, zeroAddress } from 'viem';
19
+ import { decodeEventLog, formatEther, getAddress, isAddress, parseEther, toHex, zeroAddress } from 'viem';
20
20
  import { fixedPriceMinterAddress } from './config.js';
21
21
  import { openWalletSession, signTx } from './signer.js';
22
+ import { withJson } from './jsonout.js';
22
23
  import { resolveRemote, serviceClient } from './remote.js';
23
- import { unknownFlags } from './flags.js';
24
+ import { positionalArgs, unknownFlags } from './flags.js';
24
25
  import { parseSchemaSpecs, describeSchema } from './schema.js';
25
26
  const CHAIN = process.env.ABX_CHAIN ?? DEFAULT_CHAIN_KEY; // base-sepolia default — MUST match main.ts/config.ts (a stale 'sepolia' here silently ran every owner-op on the wrong chain)
26
27
  // ── ANSI (local) ─────────────────────────────────────────────────────────────
@@ -99,6 +100,15 @@ function requireFlag(flags, name, usage) {
99
100
  }
100
101
  /** Run a prepared write through the chosen lane, then re-index if the project is known.
101
102
  * Returns whether anything was BROADCAST (false on the cold lane — the tx was only printed). */
103
+ /**
104
+ * Every owner-op's send choke point. Returns the {@link SignResult} when a tx was broadcast, else
105
+ * `null` (dry run, or the cold lane which prints a tx instead of sending one).
106
+ *
107
+ * It used to return a bare boolean. It returns the result now because a caller sometimes needs the
108
+ * *receipt* — `abx mint --json` has to report which token id was actually minted, and the only
109
+ * authoritative answer is the Transfer log the mint emitted. Callers that only asked "did it send?"
110
+ * keep working: `null` is falsy, and a result object is truthy.
111
+ */
102
112
  async function runWrite(address, provider, flags, expectedSigner) {
103
113
  // --dry-run: preview the exact tx and send NOTHING. This is the shared choke point for every
104
114
  // owner-op, so --dry-run is uniform across them — it must never fall through to a real send just
@@ -113,7 +123,7 @@ async function runWrite(address, provider, flags, expectedSigner) {
113
123
  if (expectedSigner)
114
124
  console.log(` ${dim('owner'.padEnd(12))} ${expectedSigner}`);
115
125
  console.log(dim(`\n Re-run without --dry-run to send (lane: ${laneFromFlags(flags)}).\n`));
116
- return false;
126
+ return null;
117
127
  }
118
128
  await assertChainId(CHAIN); // verify the RPC really is CHAIN before any irreversible write
119
129
  const result = await signTx(provider, {
@@ -125,9 +135,9 @@ async function runWrite(address, provider, flags, expectedSigner) {
125
135
  signUrlFile: flags['sign-url-file'],
126
136
  });
127
137
  if (!result)
128
- return false; // cold lane — nothing broadcast
138
+ return null; // cold lane — nothing broadcast
129
139
  await reindexIfKnown(address, flags);
130
- return true;
140
+ return result;
131
141
  }
132
142
  async function reindexIfKnown(address, flags) {
133
143
  const indexer = new SelfHostIndexer();
@@ -220,18 +230,35 @@ export async function assertContractExists(address) {
220
230
  * (mirrors parseFlags). `rest.filter(r => !r.startsWith('--'))` was NOT enough: a flag's VALUE
221
231
  * (e.g. the URL after `--remote`, or the path after `--file`) isn't `--`-prefixed, so it leaked
222
232
  * into the positional value (`configure-param … #ff0000 --remote http://h` → value "#ff0000 http://h"). */
223
- function positionalArgs(args) {
224
- const out = [];
225
- for (let i = 0; i < args.length; i++) {
226
- const a = args[i];
227
- if (a.startsWith('--')) {
228
- if (a.indexOf('=') === -1 && args[i + 1] && !args[i + 1].startsWith('--'))
229
- i++; // this flag consumes the next token
230
- continue;
233
+ /**
234
+ * Encode a command-line value for a payload-typed param (`String` / `Bytes`).
235
+ *
236
+ * `String` is UTF-8 text — the literal characters are the value, which is what a user means.
237
+ *
238
+ * `Bytes` is NOT text, and the old code UTF-8'd whatever string it was handed. A tester passed
239
+ * base64 (the encoding the params docs mention — which describes the *canonical decode* a program
240
+ * receives, not what you type here), and 128 packed bytes were stored as 172 bytes of base64 ASCII.
241
+ * Nothing errored; the in-chain renderer read ASCII where it expected bytes and drew garbage. So a
242
+ * `Bytes` value must state its encoding: `0x…` hex, or `--file` for real binary. A bare string is
243
+ * refused rather than guessed at — there is no safe guess between "these characters" and "these
244
+ * bytes", and the failure is invisible until an artwork renders wrong.
245
+ */
246
+ export function encodePayloadParam(typeName, valueInput, key) {
247
+ if (typeName === 'String')
248
+ return toHexSdk(new TextEncoder().encode(valueInput));
249
+ const v = valueInput.trim();
250
+ if (/^0x[0-9a-fA-F]*$/.test(v)) {
251
+ if (v.length % 2 !== 0) {
252
+ throw new Error(`--${key} hex value has an odd number of digits (${v.length - 2}) — a byte is two hex digits.`);
231
253
  }
232
- out.push(a);
254
+ return v;
233
255
  }
234
- return out;
256
+ throw new Error(`"${key}" is a Bytes param, so its value must say what its bytes ARE — this looks like text.\n` +
257
+ ` Pass 0x-prefixed hex: abx configure-param <addr> <id> ${key} 0x00112233…\n` +
258
+ ` Or the bytes in a file: abx configure-param <addr> <id> ${key} --file ./payload.bin\n` +
259
+ ` (Base64 is how a Bytes param is DECODED for your program — not how you write it here. ` +
260
+ `Storing base64 text would put ASCII on-chain where a renderer expects bytes, silently. ` +
261
+ `To store these literal characters on purpose, declare the key as String instead.)`);
235
262
  }
236
263
  export async function cmdConfigureParam(address, rest, flags) {
237
264
  const usage = 'abx configure-param <address> <tokenId|-> <key> <value> [--file <path>] [--remote [url]] [--sign|--unsigned] (tokenId "-" = contract scope, schema-less keys only)';
@@ -289,7 +316,12 @@ export async function cmdConfigureParam(address, rest, flags) {
289
316
  if (typeName === 'String' || typeName === 'Bytes') {
290
317
  const data = flags.file
291
318
  ? toHexSdk(new Uint8Array(readFileSync(flags.file)))
292
- : toHexSdk(new TextEncoder().encode(valueInput));
319
+ : encodePayloadParam(typeName, valueInput, key);
320
+ const bytes = (data.length - 2) / 2;
321
+ // Echo the DECODED byte count. The old code echoed the string's length, which is precisely how a
322
+ // wrong encoding announced itself and was missed: 128 packed bytes passed as base64 printed
323
+ // "172 bytes".
324
+ console.log(` ${key} (${typeName}) ← ${bytes} bytes ${dim(flags.file ? '(file contents, verbatim)' : typeName === 'Bytes' ? '(decoded from hex)' : '(UTF-8 text)')}`);
293
325
  sent = await runWrite(contract, prepareConfigureTokenParamData({ contract, tokenId, key, data, chainId: chainId() }), flags);
294
326
  }
295
327
  else {
@@ -465,24 +497,84 @@ export async function cmdLockDependencies(address, flags) {
465
497
  // it mints the next sequential token id (metadata = token id): bare (one), or `--count <n>`
466
498
  // (n in order). Owner or an authorized minter signs.
467
499
  export async function cmdMint(address, flags) {
468
- const usage = 'abx mint <address> [--to 0x…] [--count <n>] [--sign|--unsigned]';
500
+ const usage = 'abx mint <address> [--to 0x…] [--count <n>] [--json] [--sign|--unsigned]';
469
501
  const contract = requireAddress(address, usage);
470
- const owner = await read(contract, 'owner');
471
- const to = flags.to ?? owner; // default: pre-mint to the admin
472
- let tx;
473
- if (flags.count !== undefined && flags.count !== 'true') {
474
- const count = BigInt(flags.count);
475
- console.log(dim(` minting ${count} Series tokens in order → ${to}`));
476
- tx = prepareSeriesMintMany({ contract, to, count, chainId: chainId() });
502
+ // `--json`: the TOKEN ID is the value a program came for, and it was previously only obtainable by
503
+ // regex-scraping coloured prose (B19). It is read from the mint's own Transfer logs rather than by
504
+ // re-reading `nextTokenId` afterwards — a concurrent mint would make that answer wrong, and a
505
+ // number that is usually right is worse than no number.
506
+ return withJson(flags, async () => {
507
+ const owner = await read(contract, 'owner');
508
+ const to = flags.to ?? owner; // default: pre-mint to the admin
509
+ let tx;
510
+ let count = 1n;
511
+ if (flags.count !== undefined && flags.count !== 'true') {
512
+ count = BigInt(flags.count);
513
+ console.log(dim(` minting ${count} Series tokens in order → ${to}`));
514
+ tx = prepareSeriesMintMany({ contract, to, count, chainId: chainId() });
515
+ }
516
+ else {
517
+ // Bare mint: the `mint(address)` selector is shared by the 1/1 (token #0) and a
518
+ // Series (next sequential token) — one path serves both.
519
+ console.log(dim(` minting token → ${to}${flags.to ? '' : ' (owner — pass --to for a buyer)'}`));
520
+ tx = prepareMint({ contract, to, chainId: chainId() });
521
+ }
522
+ const result = await runWrite(contract, tx, flags, owner);
523
+ const tokenIds = result ? await mintedTokenIds(contract, result.txHash) : [];
524
+ if (result && tokenIds.length) {
525
+ console.log(` ${green('✓')} minted token${tokenIds.length > 1 ? 's' : ''} ${bold(tokenIds.map((t) => '#' + t).join(', '))} → ${to}`);
526
+ }
527
+ console.log(dim(` next: \`abx refresh ${contract}\` so marketplaces pick up the new token.`));
528
+ return {
529
+ contract,
530
+ chainId: chainId(),
531
+ to,
532
+ // `sent: false` is the dry-run and cold-lane answer — an empty tokenIds with no explanation
533
+ // would read as a failed mint.
534
+ sent: !!result,
535
+ txHash: result?.txHash ?? null,
536
+ blockNumber: result ? String(result.blockNumber) : null,
537
+ tokenIds,
538
+ requestedCount: Number(count),
539
+ };
540
+ });
541
+ }
542
+ /**
543
+ * The token ids a mint actually created, decoded from its receipt's `Transfer(from=0x0)` logs.
544
+ *
545
+ * Authoritative by construction: the ids come from the transaction that minted them, so a
546
+ * `--count N` batch reports all N and a concurrent mint elsewhere cannot skew the answer. Returns
547
+ * `[]` rather than throwing if the receipt can't be read — a mint that landed on-chain must not be
548
+ * reported as failed because a follow-up read hiccuped.
549
+ */
550
+ async function mintedTokenIds(contract, txHash) {
551
+ try {
552
+ const publicClient = makePublicClient({ chainKey: CHAIN });
553
+ const receipt = await publicClient.getTransactionReceipt({ hash: txHash });
554
+ const ids = [];
555
+ for (const log of receipt.logs) {
556
+ if (log.address.toLowerCase() !== contract.toLowerCase())
557
+ continue;
558
+ try {
559
+ const decoded = decodeEventLog({ abi: oneOfOneImageAbi, data: log.data, topics: log.topics });
560
+ if (decoded.eventName !== 'Transfer')
561
+ continue;
562
+ const args = decoded.args;
563
+ if (args.from && args.from !== zeroAddress)
564
+ continue; // a transfer, not a mint
565
+ const id = args.id ?? args.tokenId;
566
+ if (id !== undefined)
567
+ ids.push(id.toString());
568
+ }
569
+ catch {
570
+ continue; // a log from another interface on the same contract
571
+ }
572
+ }
573
+ return ids.sort((a, b) => Number(BigInt(a) - BigInt(b)));
477
574
  }
478
- else {
479
- // Bare mint: the `mint(address)` selector is shared by the 1/1 (token #0) and a
480
- // Series (next sequential token) — one path serves both.
481
- console.log(dim(` minting token → ${to}${flags.to ? '' : ' (owner — pass --to for a buyer)'}`));
482
- tx = prepareMint({ contract, to, chainId: chainId() });
575
+ catch {
576
+ return [];
483
577
  }
484
- await runWrite(contract, tx, flags, owner);
485
- console.log(dim(` next: \`abx refresh ${contract}\` so marketplaces pick up the new token.`));
486
578
  }
487
579
  // ── series owner ops (minter set · supply cap · primary payee) ────────────────
488
580
  // The multi-token knobs. All owner-only; each runs through the same signing lane +
@@ -1097,9 +1189,25 @@ export async function cmdSetField(address, flags) {
1097
1189
  * - computed keys (`artifacts`, `abx_provenance`) are refused — they're the manifest, not inputs.
1098
1190
  * Scope: token (`--token`, default 0) or `--collection`. Any signing lane. Owner-only.
1099
1191
  */
1192
+ /**
1193
+ * Attach one or more artifacts. Several `<key> <uri>` pairs in one invocation become **one
1194
+ * transaction**.
1195
+ *
1196
+ * That batching is a safety fix, not a convenience (backlog B20). The CLI's most-documented flow —
1197
+ * mint, then attach each artifact, then refresh — sent one transaction per step with no all-or-nothing
1198
+ * boundary, so a failure partway through left a permanently half-written token that cannot be
1199
+ * un-minted. An integrator hit exactly this and folded 8 operations into 1 transaction using the SDK's
1200
+ * `batchOps`, which the CLI already shipped and did not use. Now it does: N pairs are one
1201
+ * `multicall`, so either every artifact lands or none does.
1202
+ *
1203
+ * A single pair passes through `batchOps` untouched, so the one-artifact case sends the identical
1204
+ * transaction it always did.
1205
+ */
1100
1206
  export async function cmdAttach(rest, flags) {
1101
- const usage = 'abx attach <address> <key> <ipfs://… | ar://… | https://…> [--file <path>] [--collection | --token 0] [--sign|--unsigned] [--dry-run]';
1102
- const [address, key, uri] = positionalArgs(rest);
1207
+ const usage = 'abx attach <address> <key> <ipfs://… | ar://… | https://…> [<key> <uri> …] [--file <path>] [--collection | --token 0] [--sign|--unsigned] [--dry-run]';
1208
+ const [address, ...pairArgs] = positionalArgs(rest);
1209
+ const key = pairArgs[0];
1210
+ const uri = pairArgs[1];
1103
1211
  // Warn (never throw) on an unrecognized flag — a typo or a hopeful `--mime-type` otherwise no-ops
1104
1212
  // INVISIBLY (round-1: an agent passed `--mime-type` and it was silently swallowed). mimeType is
1105
1213
  // declared from the URL extension, not a flag; say so.
@@ -1134,19 +1242,44 @@ export async function cmdAttach(rest, flags) {
1134
1242
  // Locator path (the common case): auto-detect the representation, refuse an unrecognized scheme
1135
1243
  // loudly (never silently store a bad value) — fail fast before any RPC. Derive the declared
1136
1244
  // mimeType from the extension.
1137
- const representation = representationForLocator(uri);
1138
- if (!representation) {
1139
- throw new Error(`"${uri}" isn't a recognized file locator. Use ipfs://… (pinned/IPFS), ar://… (Arweave), or https://… . ` +
1140
- 'To store literal text or raw bytes on-chain instead, use `abx set-field`.');
1245
+ // Every `<key> <uri>` pair, validated BEFORE anything is sent — a batch that would revert partway
1246
+ // is exactly what this command now exists to prevent, so a bad locator in pair 5 must stop pair 1.
1247
+ if (pairArgs.length % 2 !== 0) {
1248
+ throw new Error(`attach takes <key> <uri> PAIRS; got ${pairArgs.length} positional argument(s) after the address. ` +
1249
+ `Last one seen: "${pairArgs[pairArgs.length - 1]}".`);
1250
+ }
1251
+ const pairs = [];
1252
+ for (let i = 0; i < pairArgs.length; i += 2) {
1253
+ const k = pairArgs[i];
1254
+ const u = pairArgs[i + 1];
1255
+ assertSettableField(k);
1256
+ const rep = representationForLocator(u);
1257
+ if (!rep) {
1258
+ throw new Error(`"${u}" isn't a recognized file locator. Use ipfs://… (pinned/IPFS), ar://… (Arweave), or https://… . ` +
1259
+ 'To store literal text or raw bytes on-chain instead, use `abx set-field`.');
1260
+ }
1261
+ pairs.push({ key: k, uri: u, representation: rep, mimeType: contentTypeFromPath(u) });
1262
+ }
1263
+ // A key repeated within one batch would have the later write silently win — the same
1264
+ // full-column-upsert hazard that bit `register` and `lock-field`. Refuse instead.
1265
+ const dupes = pairs.map((p) => p.key).filter((k, i, a) => a.indexOf(k) !== i);
1266
+ if (dupes.length) {
1267
+ throw new Error(`the same key appears twice in one batch: ${[...new Set(dupes)].join(', ')}. ` +
1268
+ 'Each field holds ONE active value, so the last write would silently win — attach them separately if that is really what you want.');
1141
1269
  }
1270
+ const representation = pairs[0].representation;
1142
1271
  const owner = await read(contract, 'owner');
1143
- const mimeType = contentTypeFromPath(uri);
1144
- if (mimeType === 'application/octet-stream') {
1145
- console.log(yellow(' ⚠ ') +
1146
- dim(`no file extension in "${uri.slice(0, 64)}" → declared type will be application/octet-stream. `) +
1147
- dim('Point the URI at the file itself (…/master.tiff, …/coa.pdf) so collectors get the right type.'));
1272
+ for (const p of pairs) {
1273
+ if (p.mimeType === 'application/octet-stream') {
1274
+ console.log(yellow(' ⚠ ') +
1275
+ dim(`no file extension in "${p.uri.slice(0, 64)}" → declared type will be application/octet-stream. `) +
1276
+ dim('Point the URI at the file itself (…/master.tiff, …/coa.pdf) so collectors get the right type.'));
1277
+ }
1278
+ }
1279
+ const mimeType = pairs[0].mimeType;
1280
+ for (const p of pairs) {
1281
+ console.log(` attaching ${bold(p.key)} ${dim(`(${p.mimeType}, ${p.representation})`)} to ${scope}: ${dim(p.uri)}`);
1148
1282
  }
1149
- console.log(` attaching ${bold(key)} ${dim(`(${mimeType}, ${representation})`)} to ${scope}: ${dim(uri)}`);
1150
1283
  // Where it surfaces. This used to be one dim line, and it read as a footnote rather than as a
1151
1284
  // dependency: an integrator attached five audio stems to a fully-on-chain token, paid to store
1152
1285
  // them, and found `tokenURI` listed none of them — "paid for, stored on-chain, and invisible".
@@ -1157,33 +1290,85 @@ export async function cmdAttach(rest, flags) {
1157
1290
  const uriBase = await read(contract, 'tokenURIBase').catch(() => '');
1158
1291
  const artifactsPath = `/t/${chainId()}/${contract}/${flags.token ?? '0'}`;
1159
1292
  if (uriBase && uriBase.trim() !== '') {
1160
- console.log(dim(` → listed in this project's resolver artifacts (${artifactsPath} and /data/${key}); a bare on-chain tokenURI carries reserved fields plus abx_params only.`));
1293
+ console.log(dim(` → listed in this project's resolver artifacts (${artifactsPath} and /data/<key>); a bare on-chain tokenURI carries reserved fields plus abx_params only.`));
1161
1294
  }
1162
1295
  else {
1163
1296
  console.log(yellow(' ⚠ ') +
1164
1297
  `this project resolves ON-CHAIN (no resolver base baked in), and the on-chain document carries reserved fields plus the computed ${bold('abx_params')} block only — ` +
1165
- `so ${bold(key)} will NOT appear in ${bold('tokenURI')}. The bytes are stored and provable, but nothing surfaces them to a marketplace or wallet. ` +
1298
+ `so ${pairs.map((p) => bold(p.key)).join(', ')} will NOT appear in ${bold('tokenURI')}. The bytes are stored and provable, but nothing surfaces them to a marketplace or wallet. ` +
1166
1299
  dim('(Configured params DO appear on-chain — attachments are the surface that needs a resolver.)'));
1167
1300
  console.log(dim(` to make attached artifacts visible, point the project at a resolver (${bold('abx deploy-resolver')}, or a managed one via ${bold('abx add <addr> --remote <name>')}) — it serves the listing at ${artifactsPath}.`));
1168
1301
  }
1169
- const buildTx = () => collection
1170
- ? prepareSetContractField({ contract, field: key, representation, value: toHex(uri), chainId: chainId() })
1171
- : prepareSetTokenField({ contract, tokenId: BigInt(flags.token ?? '0'), field: key, representation, value: toHex(uri), chainId: chainId() });
1172
- const sent = await runWrite(contract, buildTx(), flags, owner);
1302
+ // ONE transaction for the whole set. `batchOps` folds a same-target run into a `multicall` and
1303
+ // passes a lone op through untouched, so a single attach is byte-identical to before.
1304
+ const ops = pairs.map((p) => collection
1305
+ ? prepareSetContractField({ contract, field: p.key, representation: p.representation, value: toHex(p.uri), chainId: chainId() })
1306
+ : prepareSetTokenField({ contract, tokenId: BigInt(flags.token ?? '0'), field: p.key, representation: p.representation, value: toHex(p.uri), chainId: chainId() }));
1307
+ const batched = batchOps(ops);
1308
+ if (batched.length !== 1) {
1309
+ // Defensive: every op here targets the same contract and carries no value, so batchOps must
1310
+ // return exactly one tx. If that ever changes, fail loudly rather than send a partial set.
1311
+ throw new Error(`attach expected to batch ${ops.length} op(s) into one transaction, got ${batched.length}`);
1312
+ }
1313
+ if (pairs.length > 1) {
1314
+ console.log(dim(` ${pairs.length} artifacts → ONE transaction (all-or-nothing: a revert lands none of them, so no half-written token).`));
1315
+ }
1316
+ const sent = await runWrite(contract, batched[0], flags, owner);
1173
1317
  if (sent) {
1174
1318
  const id = flags.token ?? '0';
1175
- console.log(` ${green('✓')} attached — ${bold(key)} joins ${scope}'s ${bold('artifacts')} manifest (stored on-chain, anchored).\n` +
1319
+ const names = pairs.map((p) => bold(p.key)).join(', ');
1320
+ console.log(` ${green('✓')} attached — ${names} join${pairs.length > 1 ? '' : 's'} ${scope}'s ${bold('artifacts')} manifest (stored on-chain, anchored).\n` +
1176
1321
  dim(` verify (a resolver serves the complete listing): `) +
1177
- `curl <your-resolver>/t/${chainId()}/${contract}/${id}` +
1178
- dim(` ${collection ? '' : `→ artifacts[].key "${key}"; /data/${key} fetches it`}\n`) +
1322
+ `abx tokenuri ${contract}${id === '0' ? '' : ` --token ${id}`} --fetch` +
1323
+ dim(` ${collection ? '' : `→ artifacts[].key ${pairs.map((p) => `"${p.key}"`).join(', ')}; /data/<key> fetches each`}\n`) +
1179
1324
  dim(` (The complete file listing is a resolver surface — the bare on-chain tokenURI enumerates reserved fields plus abx_params. Params never need a resolver; attachments do.)\n`));
1180
1325
  }
1181
1326
  }
1182
1327
  // ── lock-field ───────────────────────────────────────────────────────────────
1328
+ /**
1329
+ * Refuse `lock-field` on a name that is a declared PARAM key.
1330
+ *
1331
+ * Fields and params are two separate namespaces that may share a name, and `lock-field` only ever
1332
+ * locks the *field*. A tester welded `grid` — a `Bytes` param holding the artwork — with
1333
+ * `lock-field --field grid`, got "permanent", got `tokenFieldLocked(0,"grid") == true`, and then
1334
+ * overwrote the artwork with `configure-param` on the next call. Every individual statement the CLI
1335
+ * made was true; together they promised a protection that did not exist. Permanence is the pitch, so
1336
+ * this refuses rather than warns, and names the mechanism that actually welds a param.
1337
+ */
1338
+ async function refuseIfParamKey(contract, field) {
1339
+ let exists = false;
1340
+ try {
1341
+ const schema = (await makePublicClient({ chainKey: CHAIN }).readContract({
1342
+ address: contract,
1343
+ abi: seriesCodeAbi,
1344
+ functionName: 'paramSchema',
1345
+ args: [encodeTagSdk(field)],
1346
+ }));
1347
+ exists = !!schema[0];
1348
+ }
1349
+ catch {
1350
+ // No param surface at all (a 1/1 or plain Series) — nothing to confuse the name with.
1351
+ return;
1352
+ }
1353
+ if (!exists)
1354
+ return;
1355
+ throw new Error(`"${field}" is a declared PostParam key on ${contract}, and lock-field does NOT lock params — ` +
1356
+ `it locks the metadata FIELD of the same name. They are separate namespaces, so this would have ` +
1357
+ `reported "permanent" while configure-param stayed free to overwrite the value.\n` +
1358
+ ` To weld the param, lock its schema instead:\n` +
1359
+ ` abx set-schema ${contract} --schema ${field}:<Type>:<Auth>:lock=now\n` +
1360
+ ` (after that every configure-param on "${field}" reverts ParamLockExpired — check with ` +
1361
+ `\`abx inspect ${contract}\`.)\n` +
1362
+ ` If you really did mean the metadata field "${field}" and not the param, re-run with --force-field.`);
1363
+ }
1183
1364
  export async function cmdLockField(address, flags) {
1184
1365
  const contract = requireAddress(address, 'abx lock-field <address> --field <name> [--collection | --token 0] [--sign|--unsigned]');
1185
1366
  const field = requireFlag(flags, 'field', 'abx lock-field <address> --field <name>');
1186
1367
  const collection = !!flags.collection;
1368
+ // The metadata field and a same-named param are different things; only an explicit --force-field
1369
+ // says "yes, I mean the field". See refuseIfParamKey.
1370
+ if (flags['force-field'] === undefined)
1371
+ await refuseIfParamKey(contract, field);
1187
1372
  const owner = await read(contract, 'owner');
1188
1373
  console.log(dim(` note: locking the '${field}' field is permanent and irreversible (freezes all its representations).`));
1189
1374
  const tx = collection