@artblocks/abx-cli 0.1.0-alpha.14 → 0.1.0-alpha.16

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];
@@ -80,6 +107,31 @@ export declare function cmdSetContractUri(address: string | undefined, flags: Fl
80
107
  * ABI read). Exported for the regression test. */
81
108
  export declare function parseRoyaltyBps(raw: string): number;
82
109
  export declare function cmdSetRoyalty(address: string | undefined, flags: Flags): Promise<void>;
110
+ /**
111
+ * Parse a transfer-validator choice — the shared grammar of the deploy flag (`--721c`) and the
112
+ * owner op (`abx set-transfer-validator`):
113
+ * - `recommended` (or a bare `--721c`) → the per-chain recommended validator, refusing on a
114
+ * chain the manifest has no entry for (naming the chains that do — never guess one);
115
+ * - a `0x…` address → checksum-validated (EIP-55) and returned as its canonical form;
116
+ * - `none`/zero → `allowNone` decides: the owner op suspends with it; the DEPLOY flag refuses
117
+ * it (a zero validator at deploy never enrolls — that is already the default, so someone
118
+ * passing it either wants plain ERC-721 (drop the flag) or mistakenly believes "enrolled but
119
+ * suspended" is a deploy-time state — it isn't).
120
+ * Pure (no RPC) — callers do the has-code precheck themselves. Exported for the regression test.
121
+ */
122
+ export declare function parseTransferValidatorValue(raw: string, opts: {
123
+ chainId: number;
124
+ chainLabel: string;
125
+ allowNone?: boolean;
126
+ }): Address;
127
+ /**
128
+ * `abx set-transfer-validator <address> <0x…|none|recommended>` — re-point an ENROLLED (ERC-721C)
129
+ * collection's transfer validator, or suspend enforcement with `none` (address(0); the token
130
+ * STAYS enrolled). Refuses up front, before any signing: a plain ERC-721 (enrollment is
131
+ * deploy-time-only — the contract would revert `NotCreatorToken()`), and a codeless validator
132
+ * (would revert `InvalidTransferValidator()`). Owner-only, any lane, guards `--dry-run`.
133
+ */
134
+ export declare function cmdSetTransferValidator(address: string | undefined, rest: string[], flags: Flags): Promise<void>;
83
135
  export type Compress = 'none' | 'fastlz' | 'gzip';
84
136
  export declare function parseCompress(v: string | undefined): Compress;
85
137
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ownerops.d.ts","sourceRoot":"","sources":["../src/ownerops.ts"],"names":[],"mappings":"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"}
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, prepareTransfer, prepareTransferOwnership, 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, 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';
@@ -20,7 +20,7 @@ import { formatEther, getAddress, isAddress, parseEther, toHex, zeroAddress } fr
20
20
  import { fixedPriceMinterAddress } from './config.js';
21
21
  import { openWalletSession, signTx } from './signer.js';
22
22
  import { resolveRemote, serviceClient } from './remote.js';
23
- import { unknownFlags } from './flags.js';
23
+ import { positionalArgs, unknownFlags } from './flags.js';
24
24
  import { parseSchemaSpecs, describeSchema } from './schema.js';
25
25
  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
26
  // ── ANSI (local) ─────────────────────────────────────────────────────────────
@@ -220,18 +220,35 @@ export async function assertContractExists(address) {
220
220
  * (mirrors parseFlags). `rest.filter(r => !r.startsWith('--'))` was NOT enough: a flag's VALUE
221
221
  * (e.g. the URL after `--remote`, or the path after `--file`) isn't `--`-prefixed, so it leaked
222
222
  * 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;
223
+ /**
224
+ * Encode a command-line value for a payload-typed param (`String` / `Bytes`).
225
+ *
226
+ * `String` is UTF-8 text — the literal characters are the value, which is what a user means.
227
+ *
228
+ * `Bytes` is NOT text, and the old code UTF-8'd whatever string it was handed. A tester passed
229
+ * base64 (the encoding the params docs mention — which describes the *canonical decode* a program
230
+ * receives, not what you type here), and 128 packed bytes were stored as 172 bytes of base64 ASCII.
231
+ * Nothing errored; the in-chain renderer read ASCII where it expected bytes and drew garbage. So a
232
+ * `Bytes` value must state its encoding: `0x…` hex, or `--file` for real binary. A bare string is
233
+ * refused rather than guessed at — there is no safe guess between "these characters" and "these
234
+ * bytes", and the failure is invisible until an artwork renders wrong.
235
+ */
236
+ export function encodePayloadParam(typeName, valueInput, key) {
237
+ if (typeName === 'String')
238
+ return toHexSdk(new TextEncoder().encode(valueInput));
239
+ const v = valueInput.trim();
240
+ if (/^0x[0-9a-fA-F]*$/.test(v)) {
241
+ if (v.length % 2 !== 0) {
242
+ throw new Error(`--${key} hex value has an odd number of digits (${v.length - 2}) — a byte is two hex digits.`);
231
243
  }
232
- out.push(a);
244
+ return v;
233
245
  }
234
- return out;
246
+ throw new Error(`"${key}" is a Bytes param, so its value must say what its bytes ARE — this looks like text.\n` +
247
+ ` Pass 0x-prefixed hex: abx configure-param <addr> <id> ${key} 0x00112233…\n` +
248
+ ` Or the bytes in a file: abx configure-param <addr> <id> ${key} --file ./payload.bin\n` +
249
+ ` (Base64 is how a Bytes param is DECODED for your program — not how you write it here. ` +
250
+ `Storing base64 text would put ASCII on-chain where a renderer expects bytes, silently. ` +
251
+ `To store these literal characters on purpose, declare the key as String instead.)`);
235
252
  }
236
253
  export async function cmdConfigureParam(address, rest, flags) {
237
254
  const usage = 'abx configure-param <address> <tokenId|-> <key> <value> [--file <path>] [--remote [url]] [--sign|--unsigned] (tokenId "-" = contract scope, schema-less keys only)';
@@ -289,7 +306,12 @@ export async function cmdConfigureParam(address, rest, flags) {
289
306
  if (typeName === 'String' || typeName === 'Bytes') {
290
307
  const data = flags.file
291
308
  ? toHexSdk(new Uint8Array(readFileSync(flags.file)))
292
- : toHexSdk(new TextEncoder().encode(valueInput));
309
+ : encodePayloadParam(typeName, valueInput, key);
310
+ const bytes = (data.length - 2) / 2;
311
+ // Echo the DECODED byte count. The old code echoed the string's length, which is precisely how a
312
+ // wrong encoding announced itself and was missed: 128 packed bytes passed as base64 printed
313
+ // "172 bytes".
314
+ console.log(` ${key} (${typeName}) ← ${bytes} bytes ${dim(flags.file ? '(file contents, verbatim)' : typeName === 'Bytes' ? '(decoded from hex)' : '(UTF-8 text)')}`);
293
315
  sent = await runWrite(contract, prepareConfigureTokenParamData({ contract, tokenId, key, data, chainId: chainId() }), flags);
294
316
  }
295
317
  else {
@@ -661,6 +683,112 @@ export async function cmdSetRoyalty(address, flags) {
661
683
  }
662
684
  await runWrite(contract, prepareSetRoyalty({ contract, receiver, bps, chainId: chainId() }), flags, owner);
663
685
  }
686
+ // ── ERC-721C (creator token) — the transfer-validator surface ─────────────────
687
+ // Enrollment is a DEPLOY-TIME decision (`--721c` on the deploy commands) and permanent in both
688
+ // directions: an unenrolled token can never gain a validator, an enrolled one never sheds the
689
+ // standard. Within an enrolled token the owner re-points or suspends (zero) the validator freely.
690
+ /** The chain keys the manifest recommends a transfer validator for — for refusal messages. */
691
+ function chainsWithRecommendedValidator() {
692
+ const ids = new Set(Object.keys(RECOMMENDED_TRANSFER_VALIDATOR).map(Number));
693
+ return KNOWN_CHAIN_KEYS.filter((k) => ids.has(resolveChain(k).id)).join(', ');
694
+ }
695
+ /**
696
+ * Parse a transfer-validator choice — the shared grammar of the deploy flag (`--721c`) and the
697
+ * owner op (`abx set-transfer-validator`):
698
+ * - `recommended` (or a bare `--721c`) → the per-chain recommended validator, refusing on a
699
+ * chain the manifest has no entry for (naming the chains that do — never guess one);
700
+ * - a `0x…` address → checksum-validated (EIP-55) and returned as its canonical form;
701
+ * - `none`/zero → `allowNone` decides: the owner op suspends with it; the DEPLOY flag refuses
702
+ * it (a zero validator at deploy never enrolls — that is already the default, so someone
703
+ * passing it either wants plain ERC-721 (drop the flag) or mistakenly believes "enrolled but
704
+ * suspended" is a deploy-time state — it isn't).
705
+ * Pure (no RPC) — callers do the has-code precheck themselves. Exported for the regression test.
706
+ */
707
+ export function parseTransferValidatorValue(raw, opts) {
708
+ const s = raw.trim().toLowerCase();
709
+ if (s === 'true' || s === '' || s === 'recommended') {
710
+ const rec = resolveRecommendedTransferValidator(opts.chainId);
711
+ if (!rec) {
712
+ throw new Error(`no recommended transfer validator is known for '${opts.chainLabel}' (chainId ${opts.chainId}) — ` +
713
+ `chains with one: ${chainsWithRecommendedValidator() || '(none shipped)'}. ` +
714
+ `Pass an explicit validator address instead (it must be a deployed contract on this chain).`);
715
+ }
716
+ return rec;
717
+ }
718
+ if (s === 'none' || s === 'zero' || s === '0' || s === '0x0' || s === zeroAddress) {
719
+ if (opts.allowNone)
720
+ return zeroAddress;
721
+ throw new Error(`a zero transfer validator never enrolls — plain ERC-721 is already the default, so drop --721c. ` +
722
+ `("enrolled but suspended" is not a deploy-time state: enroll with a real validator, then suspend ` +
723
+ `with \`abx set-transfer-validator <address> none\`.)`);
724
+ }
725
+ const trimmed = raw.trim();
726
+ if (!/^0x[0-9a-fA-F]{40}$/.test(trimmed)) {
727
+ throw new Error(`transfer validator must be 'recommended'${opts.allowNone ? ", 'none'," : ''} or a 0x address (0x + 40 hex); got '${raw}'`);
728
+ }
729
+ // `isAddress` strict-validates the EIP-55 checksum of a mixed-case address (all-lowercase carries
730
+ // no checksum and passes); `getAddress` alone only NORMALIZES — it would silently accept a
731
+ // mis-cased paste, which is exactly the transposition this check exists to catch.
732
+ if (!isAddress(trimmed, { strict: true })) {
733
+ throw new Error(`transfer validator address failed its EIP-55 checksum: '${raw}' — paste it exactly (or all-lowercase).`);
734
+ }
735
+ return getAddress(trimmed);
736
+ }
737
+ /**
738
+ * `abx set-transfer-validator <address> <0x…|none|recommended>` — re-point an ENROLLED (ERC-721C)
739
+ * collection's transfer validator, or suspend enforcement with `none` (address(0); the token
740
+ * STAYS enrolled). Refuses up front, before any signing: a plain ERC-721 (enrollment is
741
+ * deploy-time-only — the contract would revert `NotCreatorToken()`), and a codeless validator
742
+ * (would revert `InvalidTransferValidator()`). Owner-only, any lane, guards `--dry-run`.
743
+ */
744
+ export async function cmdSetTransferValidator(address, rest, flags) {
745
+ const usage = 'abx set-transfer-validator <address> <0x…|none|recommended> [--sign|--unsigned] [--dry-run]';
746
+ const contract = requireAddress(address, usage);
747
+ const [raw] = positionalArgs(rest);
748
+ if (!raw) {
749
+ console.error(`usage: ${usage}\n`);
750
+ process.exit(1);
751
+ }
752
+ const cid = chainId();
753
+ const validator = parseTransferValidatorValue(raw, { chainId: cid, chainLabel: CHAIN, allowNone: true });
754
+ const publicClient = makePublicClient({ chainKey: CHAIN });
755
+ // Enrollment guard FIRST — a read, not a send. An unenrolled token would revert
756
+ // `NotCreatorToken()`; refuse with the real story instead of letting the chain say it in hex.
757
+ // (readCreatorTokenStatus is defensive, so check the contract exists first — a typo'd address
758
+ // must not read as "plain ERC-721".)
759
+ await assertContractExists(contract);
760
+ const status = await readCreatorTokenStatus(publicClient, contract);
761
+ if (!status.enrolled) {
762
+ throw new Error(`${contract} is a plain ERC-721 — 721C is a deploy-time decision, and this collection didn't enroll. ` +
763
+ `Enrollment can never be added to a live collection; if enforcement is required, redeploy with ` +
764
+ `--721c recommended (or --721c 0x…) on the deploy command.`);
765
+ }
766
+ // Codeless-validator guard: the contract refuses a non-zero validator with no code
767
+ // (`InvalidTransferValidator()`) — surface it before gas is spent.
768
+ if (validator !== zeroAddress) {
769
+ let code;
770
+ try {
771
+ code = await publicClient.getCode({ address: validator });
772
+ }
773
+ catch (err) {
774
+ throw new Error(`couldn't verify the validator has code at ${validator} (${err.message}) — refusing to re-point blind; retry when the RPC answers.`);
775
+ }
776
+ if (!code || code === '0x') {
777
+ const rec = resolveRecommendedTransferValidator(cid);
778
+ throw new Error(`no contract code at ${validator} on ${CHAIN} — the token would revert InvalidTransferValidator(). ` +
779
+ `A transfer validator must be a DEPLOYED contract on this chain` +
780
+ (rec ? ` (the recommended one: \`abx set-transfer-validator ${contract} recommended\` → ${rec})` : '') + `.`);
781
+ }
782
+ }
783
+ if (eqAddr(status.validator, validator)) {
784
+ console.log(dim(` no change — the validator is already ${validator === zeroAddress ? 'suspended (0x0)' : validator}. Nothing sent.`));
785
+ return;
786
+ }
787
+ const label = (a) => (eqAddr(a, zeroAddress) ? 'suspended (0x0)' : a);
788
+ console.log(dim(` transfer validator: ${label(status.validator)} → ${label(validator)}${validator === zeroAddress ? ' — transfers go unvalidated until one is set again (the token stays enrolled)' : ''}`));
789
+ const owner = await read(contract, 'owner');
790
+ await runWrite(contract, prepareSetTransferValidator({ contract, validator, chainId: cid }), flags, owner);
791
+ }
664
792
  export function parseCompress(v) {
665
793
  const c = (v ?? 'none').toLowerCase();
666
794
  if (c === 'none' || c === 'fastlz' || c === 'gzip')
@@ -1074,10 +1202,50 @@ export async function cmdAttach(rest, flags) {
1074
1202
  }
1075
1203
  }
1076
1204
  // ── lock-field ───────────────────────────────────────────────────────────────
1205
+ /**
1206
+ * Refuse `lock-field` on a name that is a declared PARAM key.
1207
+ *
1208
+ * Fields and params are two separate namespaces that may share a name, and `lock-field` only ever
1209
+ * locks the *field*. A tester welded `grid` — a `Bytes` param holding the artwork — with
1210
+ * `lock-field --field grid`, got "permanent", got `tokenFieldLocked(0,"grid") == true`, and then
1211
+ * overwrote the artwork with `configure-param` on the next call. Every individual statement the CLI
1212
+ * made was true; together they promised a protection that did not exist. Permanence is the pitch, so
1213
+ * this refuses rather than warns, and names the mechanism that actually welds a param.
1214
+ */
1215
+ async function refuseIfParamKey(contract, field) {
1216
+ let exists = false;
1217
+ try {
1218
+ const schema = (await makePublicClient({ chainKey: CHAIN }).readContract({
1219
+ address: contract,
1220
+ abi: seriesCodeAbi,
1221
+ functionName: 'paramSchema',
1222
+ args: [encodeTagSdk(field)],
1223
+ }));
1224
+ exists = !!schema[0];
1225
+ }
1226
+ catch {
1227
+ // No param surface at all (a 1/1 or plain Series) — nothing to confuse the name with.
1228
+ return;
1229
+ }
1230
+ if (!exists)
1231
+ return;
1232
+ throw new Error(`"${field}" is a declared PostParam key on ${contract}, and lock-field does NOT lock params — ` +
1233
+ `it locks the metadata FIELD of the same name. They are separate namespaces, so this would have ` +
1234
+ `reported "permanent" while configure-param stayed free to overwrite the value.\n` +
1235
+ ` To weld the param, lock its schema instead:\n` +
1236
+ ` abx set-schema ${contract} --schema ${field}:<Type>:<Auth>:lock=now\n` +
1237
+ ` (after that every configure-param on "${field}" reverts ParamLockExpired — check with ` +
1238
+ `\`abx inspect ${contract}\`.)\n` +
1239
+ ` If you really did mean the metadata field "${field}" and not the param, re-run with --force-field.`);
1240
+ }
1077
1241
  export async function cmdLockField(address, flags) {
1078
1242
  const contract = requireAddress(address, 'abx lock-field <address> --field <name> [--collection | --token 0] [--sign|--unsigned]');
1079
1243
  const field = requireFlag(flags, 'field', 'abx lock-field <address> --field <name>');
1080
1244
  const collection = !!flags.collection;
1245
+ // The metadata field and a same-named param are different things; only an explicit --force-field
1246
+ // says "yes, I mean the field". See refuseIfParamKey.
1247
+ if (flags['force-field'] === undefined)
1248
+ await refuseIfParamKey(contract, field);
1081
1249
  const owner = await read(contract, 'owner');
1082
1250
  console.log(dim(` note: locking the '${field}' field is permanent and irreversible (freezes all its representations).`));
1083
1251
  const tx = collection